vue3页面滚动及暂停按钮出现问题

vue3中自制了一个页面滚动功能和一个暂停、滚动按钮,目前出现的问题是暂停、滚动按钮有时好使有时失效,并且页面自动滚动偶尔会出现页面抖动的问题,求帮忙解决一下
以下是代码


<template>
  <div class="home" v-on:scroll="onScroll" ref="scrollContainer">
    <div class="header">仓库看板div>
    <div class="home-table">
      <table class="homeTable1" border="1" cellspacing="0">
        <thead class="table-head">
          <th class="bgPrimary">库区th>
          <th class="bgPrimary">层数th>
          <th class="bgPrimary" v-for="(item, index) in 22" :key="index">
            {{ (item = item < 10 ? "00" + item : "0" + item) }}
          th>
        thead>
        <tbody v-for="(item, index) in filteredList" :key="index">
          <tr v-for="(items, indexs) in item.list" :key="indexs">
            <th
              :rowspan="item.list.length"
              class="bgPrimary"
              v-if="indexs == 0"
            >
              {{ item.name }}
            th>
            <th class="bgPrimary">{{ item.list.length - indexs }}th>
            <td
              v-for="(itemss, indexss) in items.info"
              :key="indexss"
              :class="
                itemss.volmueType == 0
                  ? 'colorEmpty'
                  : itemss.volmueType > 0 && itemss.volmueType < 50
                  ? 'colorMore'
                  : itemss.volmueType >= 50 && itemss.volmueType <= 100
                  ? 'colorMore'
                  : itemss.volmueType > 100
                  ? 'colorMore'
                  : 'colorPrimary'
              "
              :style="itemss.volmueType == '-1' ? 'border:none' : ''"
            >
              {{ itemss.MatName }}
            td>
          tr>
          <div class="space">div>
        tbody>
      table>
    div>
    <div class="home-table">
      <table class="homeTable1" border="1" cellspacing="0">
        <thead>
          <th class="bgPrimary">库区th>
          <th class="bgPrimary">层数th>
          <th class="bgPrimary" v-for="(item, index) in 10" :key="index">
            {{ (item = item < 10 ? "00" + item : "0" + item) }}
          th>
        thead>
        <tbody v-for="(item, index) in filteredElseList" :key="index">
          <tr v-for="(items, indexs) in item.list" :key="indexs">
            <th
              :rowspan="item.list.length"
              class="bgPrimary"
              v-if="indexs == 0"
            >
              {{ item.name }}
            th>
            <th class="bgPrimary">{{ item.list.length - indexs }}th>
            <td
              v-for="(itemss, indexss) in items.info"
              :key="indexss"
              :class="
                itemss.volmueType == 0
                  ? 'colorEmpty'
                  : itemss.volmueType > 0 && itemss.volmueType < 50
                  ? 'colorMore'
                  : itemss.volmueType >= 50 && itemss.volmueType <= 100
                  ? 'colorMore'
                  : itemss.volmueType > 100
                  ? 'colorMore'
                  : 'colorPrimary'
              "
            >
              {{ itemss.MatName }}
            td>
          tr>
          <div class="space">div>
        tbody>
      table>
    div>
    <el-button type="primary" class="floatBtn" @click="handleOnscroll">{{
      isPaused ? "开始滚动" : "暂停滚动"
    }}el-button>
  div>
template>
<script>
import { onMounted, ref, onUnmounted, computed } from "vue";
import { ExecJSON2 } from "@/api/mainPage";
export default {
  name: "mainPage",
  setup() {
    const tableData = ref([]);
    const rowstyles = ref();
    const areaData = ref(new Array());
    const timer = ref();

    const scrolling = ref(false);
    const timeoutId = ref(null);
    const scrollContainer = ref(null);
    const intervalId = ref(null);
    const isPaused = ref(false);

    const filteredList = computed(() => {
      if (!tableData.value) {
        return [];
      }
      return tableData.value.filter((item) => item.list.length > 1);
    });

    const filteredElseList = computed(() => {
      return tableData.value.filter((item) => item.list.length == 1);
    });

    onMounted(() => {
      getAllData();
      onScroll();
      timer.value = setInterval(() => {
        getAllData();
        location.reload();
      }, 30 * 60 * 1000);
    });

    onUnmounted(() => {
      clearInterval(timer);
    });
    const getAllData = () => {
      const param = {
        Code: "KB_01",
        Data: [
          {
            WarehouseCode: "01",
          },
        ],
      };
      ExecJSON2(param)
        .then(async (res) => {
          if (res.Success == "01") {
            tableData.value = await bubbleSort(res.Data1);
          }
        })
        .catch((err) => {
          console.log(err, 19);
        });
    };
    const bubbleSort = async (array) => {
      for (let i = 0; i < array.length; i++) {
        array[i].info = JSON.parse(array[i].info);
        let newDataList = [];
        for (let j = 0; j < array[i].info.length; j++) {
          newDataList.push(array[i].info[j]);
          let curNum = parseInt(array[i].info[j].WarehouseLocation, 10);

          if (array[i].info[j + 1]) {
            let nextNum = parseInt(array[i].info[j + 1].WarehouseLocation, 10);

            if (nextNum - curNum > 1) {
              for (let j = 1; j < nextNum - curNum; j++) {
                newDataList.push({
                  MatName: "",
                  WarehouseLocation: (curNum + j).toString().padStart(3, "0"),
                  WarehouseLayers: array[i].info[j].WarehouseLayers,
                  WarehouseArea: array[i].info[j].WarehouseArea,
                  Volume: "0.00",
                  NewVolume: "0.00",
                  Quantity: "0",
                  SafetyStock: "0",
                  volmueType: "-1",
                });
              }
            }
          }

          array[i].info[j].volmuePersent =
            parseInt(array[i].info[j].NewVolume) == "0"
              ? " "
              : Math.round(
                  (parseFloat(array[i].info[j].Volume) /
                    parseFloat(array[i].info[j].NewVolume)) *
                    10000
                ) /
                  100 +
                "%";

          array[i].info[j].volmueType =
            parseInt(array[i].info[j].NewVolume) == "0"
              ? 0
              : Math.round(
                  (parseFloat(array[i].info[j].Volume) /
                    parseFloat(array[i].info[j].NewVolume)) *
                    10000
                ) / 100;
        }
        array[i].info = newDataList;
      }

      array = await groupArr(array, "WarehouseArea");

      return array;
    };

    const groupArr = (list, field) => {
      var obj = {};
      for (var i = 0; i < list.length; i++) {
        for (let item in list[i]) {
          if (item == field) {
            obj[list[i][item]] = {
              list: obj[list[i][field]] ? obj[list[i][field]].list : [],
              name: list[i][field],
            };
          }
        }
        obj[list[i][field]].list.push(list[i]);
      }
      var att = [];
      for (let item in obj) {
        att.push({
          list: obj[item].list,
          name: obj[item].name,
        });
      }
      return att;
    };
    const scrollDirection = ref("down");
    const isAutoScrolling = ref(true);

    const onScroll = () => {
      if (isPaused.value) return;

      // 停止自动滚动
      clearTimeout(timeoutId.value);

      // 设置滚动状态
      scrolling.value = true;

      // 滚动页面
      const windowHeight = window.innerHeight;
      const contentHeight = document.documentElement.scrollHeight;
      let position = window.pageYOffset;
      let reachedTop = false;

      intervalId.value = setInterval(() => {
        if (scrollDirection.value == "down") {
          position += 1; // 每次滚动1px
          window.scrollTo(0, position);
          if (position + windowHeight >= contentHeight) {
            clearInterval(intervalId.value);
            scrolling.value = false;
            scrollDirection.value = "up";
            // 两秒后重新开始滚动
            timeoutId.value = setTimeout(() => {
              onScroll();
            }, 2000);
          }
        } else {
          if (position === 0) {
            reachedTop = true;
          }

          if (!reachedTop) {
            position -= 1; // 每次滚动1px
            window.scrollTo(0, position);
          }

          if (reachedTop && position === 0) {
            clearInterval(intervalId);
            scrolling.value = false;
            scrollDirection.value = "down";
            reachedTop = false;
            // 两秒后重新开始滚动
            timeoutId.value = setTimeout(() => {
              onScroll();
            }, 2000);
          }
        }
      }, 20); // 每20毫秒滚动一次
    };

    const handleOnscroll = () => {
      if (isPaused.value) {
        // 继续滚动
        isPaused.value = false;
        onScroll();
      } else {
        // 暂停滚动
        isPaused.value = true;
        clearTimeout(timeoutId.value);
        clearInterval(intervalId.value);
      }

      console.log("isPaused.value:" + isPaused.value);
      console.log("scrolling.value:" + scrolling.value);
      console.log("intervalId.value:" + intervalId.value);
      console.log("timeoutId.value:" + timeoutId.value);
    };

    return {
      getAllData,
      bubbleSort,
      tableData,
      rowstyles,
      areaData,
      groupArr,
      filteredList,
      filteredElseList,
      timer,
      scrolling,
      timeoutId,
      onScroll,
      scrollContainer,
      scrollDirection,
      isAutoScrolling,
      handleOnscroll,
      intervalId,
      isPaused,
    };
  },
  mounted() {
    this.$refs.scrollContainer.addEventListener("scroll", this.onScroll);
  },

  beforeUnmount() {
    this.$refs.scrollContainer.removeEventListener("scroll", this.onScroll);
  },
};
script>
<style lang="less" scoped>
.home {
  width: 100%;
  .header {
    font-size: 1.2rem;
    color: #409eff;
    position: sticky;
    top: 0;
    background: #f2f6fc;
  }
  .home-table {
    display: flex;
    table {
      table-layout: fixed;
      height: 100%;
      width: 100%;
    }
    .table-head {
      position: sticky;
      top: 1.5rem;
    }
  }

  table,
  th {
    text-align: center;
    border: 0.5px solid #f5f6f9;
    border-collapse: collapse;
    color: #ffffff;
    font-size: 0.7rem;
    background: #f2f6fc;
  }

  .space {
    width: 100%;
    height: 3rem;
  }

  td {
    color: black;
    height: 3rem;
  }

  table td {
    margin: 0 auto;
  }

  .colorPrimary {
    background: #f2f6fc !important;
  }

  .bgPrimary {
    background: #409eff !important;
  }

  .colorEmpty {
    background: #ffffff !important;
  }
  .colorSimple {
    background: #e3f6f5 !important;
  }
  .colorMore {
    background: #bae8e8 !important;
  }
  .colorOverload {
    background: #2c698d !important;
  }

  .floatBtn {
    position: fixed;
    right: 1rem;
    bottom: 3rem;
  }
}
style>


v3 用v2写法??

根据你提供的代码,初步判断可能出现问题的地方有:

  1. 暂停、滚动按钮的状态管理不够清晰,可能会出现状态混乱的情况,导致按钮失效。建议使用 computed 计算属性来管理 isPaused 状态,这样方便在模板中直接引用,同时也不容易出现状态混乱的问题。

  2. 页面抖动的问题可能是因为滚动事件触发的频率过高,建议将 intervalId 的时间间隔调大一些,比如 50 毫秒。

另外,建议在开发环境下打开浏览器的 Console,查看控制台是否有报错信息,有可能也会影响页面的滚动效果。

1、div标签不能放在tbody标签中,移出去吧
2、没有定义事件处理函数,v-on:scroll="onScroll"
3、你用isPaused这个变量来控制暂停/滚动,但是没有看到你在事件处理函数中修改isPaused的值啊

  • 帮你找了个相似的问题, 你可以看下: https://ask.csdn.net/questions/7772538
  • 我还给你找了一篇非常好的博客,你可以看看是否有帮助,链接:vue3语法糖的使用和定义、语法糖中路由的使用、语法糖中状态管理的使用、语法糖中组件的传值
  • 除此之外, 这篇博客: Vue3 京东到家项目实战第一篇(首页及登录功能开发) 进阶式掌握vue3完整知识体系中的 通过代码拆分增加逻辑可维护性💥 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 现在我们把各种函数都放在了 setup 中,这样做肯定没有出错,但是这样会让我们的 setup 函数非常长,如果项目做到后面我们要在里面找某一个函数或者变量的时候,都很麻烦,如果把关于登录逻辑的数据和方法都放在 setup 外面的一个函数中,关于注册逻辑的数据和方法放在另一个函数中,这样再把这些函数在 setup 中接收,在 setup 中我们只关心整个页面的实现逻辑就行,这样整个代码的维护性和可读性都大大提高了。

    在登录页面我们把向后端发送登录请求的相关逻辑从 setup 中抽离出来:

    const useLoginEffect = () => {
      const router = useRouter()
      let username = ref('')
      let password = ref('')
      let handleLogin = async () => {
        if (username.value === '' || password.value === '') {
          alertmessage('输入内容不能为空', 'warning', 1500)
          return
        }
        try {
          const result = await post('/api/user/login', {
            username: username,
            password: password
          })
          if (result.data.errno === 0) {
            localStorage.isLogin = true
            alertmessage('登录成功欢迎您', 'success', 2000)
            setTimeout(() => {
              router.push({ name: 'home' })
            }, 2000)
          } else {
            alertmessage('登录失败', 'error', 2000)
          }
        } catch (e) {
          alertmessage('请求失败', 'error', 2000)
        }
      }
      return { username, password, handleLogin }
    }

    这里我们重新定义了一个useLoginEffect 函数,然后把需要用到的数据和方法都放进来,最后通过 return 把数据和方法返回出来,以便在 setup 中接收。

    我们再把点击注册这个函数的相关逻辑抽离出来:

    const useRegisterEffect = () => {
      const router = useRouter()
      let handleRegisterClick = () => {
        ElNotification({
          title: '尊敬的用户您好',
          message: h('i', { style: 'color: teal' }, '正在跳转到注册页面'),
          duration: 800
        })
        setTimeout(() => {
          router.push({ name: 'register' })
        }, 900)
      }
      return { handleRegisterClick }
    }
    

    这样我们就把登录页面相关的功能都从 setup 里面抽离了出来,现在再看 setup 里的代码就优雅了许多,浅显易懂:

    setup () {
        const { username, password, handleLogin } = useLoginEffect()
        const { handleRegisterClick } = useRegisterEffect()
        return {
          handleLogin,
          username,
          password,
          handleRegisterClick
        }
      }

    在 setup 里我们很清晰的知道这个页面的实现逻辑,如果想修改跳转登录这个函数就去对应的函数里修改就可以,方便了很多。

    在注册页面的代码拆分和登录页面的相同,这里就不过多阐述。在本文中我们暂时完成了项目首页和登陆注册页面的样式,实现了登陆注册时向后端发送请求获取数据的功能,最后通过代码拆分增加逻辑可维护性。下一篇文章我们会实现商家展示功能的开发,大家记得关注哦!

    项目代码地址:

    https://gitee.com/jie_shao1112/jingdong-homeicon-default.png?t=M4ADhttps://gitee.com/jie_shao1112/jingdong-home
     

  • 您还可以看一下 徐照兴老师的Vue全家桶从基础入门到进阶项目实战第三篇中级进阶实战篇课程中的 什么是单页面应用及实现前端路由的基本原理小节, 巩固相关知识点

检查按钮事件绑定是否正确:确保事件绑定的元素和按钮选择器匹配,事件处理函数没有出现语法错误。可以在控制台中打印出事件绑定的元素,检查选择器是否匹配。
检查页面元素是否出现变化:如果你在页面中动态添加或删除了元素,那么选择器可能需要相应的修改,否则事件绑定将会失败。你可以检查一下DOM结构,确认选择器是否仍然有效。
对于页面抖动的问题,可能是由于页面自动滚动的时候,页面高度发生变化,导致滚动条出现或消失,进而导致页面抖动。你可以尝试以下几个方面来解决这个问题:

确保页面高度不会发生变化:在页面自动滚动的时候,确保页面中的元素不会发生高度变化。例如,如果你在滚动过程中动态加载了图片,可以在图片加载前占位,避免页面高度的变化。
使用固定高度的容器:如果你使用了自定义滚动功能,可以使用一个固定高度的容器来实现滚动。这样可以避免滚动条的出现或消失,进而避免页面抖动。
使用CSS动画:如果你使用了自定义滚动功能,可以考虑使用CSS动画来实现页面的滚动,这样可以避免使用滚动条,进而避免页面抖动。

以下内容部分参考ChatGPT模型:


问题的出现可能是由于滚动和暂停按钮的事件绑定存在问题,或者滚动的逻辑实现不够完善。建议首先检查滚动和暂停按钮的事件绑定是否正确,可以尝试使用Vue提供的@click事件来替换原有的@click.native事件。另外,可以尝试优化滚动逻辑,例如使用requestAnimationFrame来代替setInterval实现更平滑的滚动效果,并添加一些滚动结束时的判断,避免页面抖动等问题的出现。以下是代码示例:

<template>
  <div class="home" ref="scrollContainer">
    ...
    <el-button type="primary" class="floatBtn" @click="handleScroll">{{ isPaused ? '开始滚动' : '暂停滚动' }}</el-button>
  </div>
</template>

<script>
import { onMounted, ref, onUnmounted, computed } from "vue";
import { ExecJSON2 } from "@/api/mainPage";

export default {
  name: "mainPage",
  setup() {
    const tableData = ref([]);
    const isPaused = ref(false);
    const scrollContainer = ref(null);
    let animationId;

    const filteredList = computed(() => {
      if (!tableData.value) {
        return [];
      }
      return tableData.value.filter((item) => item.list.length > 1);
    });

    onMounted(() => {
      getAllData();
      onScroll();
      animationId = requestAnimationFrame(animate);
    });

    onUnmounted(() => {
      cancelAnimationFrame(animationId);
    });

    const getAllData = () => {
      ...
    };

    const animate = () => {
      if (!isPaused.value) {
        scrollContainer.value.scrollTop += 1;
      }
      animationId = requestAnimationFrame(animate);
    };

    const handleScroll = () => {
      isPaused.value = !isPaused.value;
    };

    return {
      getAllData,
      tableData,
      filteredList,
      isPaused,
      scrollContainer,
      handleScroll,
    };
  },
};
</script>

在这个示例中,使用了requestAnimationFrame替换了setInterval来实现滚动效果,并且添加了一个animate函数来控制滚动的行为。另外,将滚动容器的引用绑定到了scrollContainer变量上,方便后续的操作。在handleScroll函数中,通过改变isPaused的值来控制滚动的暂停和继续。


如果我的建议对您有帮助、请点击采纳、祝您生活愉快

引用chatGPT作答,根据提供的代码和问题描述,可能有以下问题:

1.暂停/滚动按钮的失效 - 代码中的 handleOnscroll 方法没有提供,无法确定问题所在。但可能的原因包括方法未正确定义、按钮状态没有正确更新、按钮与滚动事件之间的同步问题等。建议检查 handleOnscroll 方法的定义并确保其正确更新按钮状态。

2.页面抖动 - 这可能是因为滚动事件触发了重新渲染组件,导致页面抖动。为了解决这个问题,可以使用 requestAnimationFrame 方法替代 setInterval 实现滚动,并使用 Vue 的异步更新机制来避免频繁的组件重新渲染。可以在 onMounted 生命周期钩子中定义一个滚动函数,在函数内使用 requestAnimationFrame 方法实现页面滚动,并使用 Vue.nextTick() 方法在下一次更新时异步更新组件。

3.页面滚动的同步问题 - 可能会出现滚动事件与数据更新之间的同步问题。当滚动事件触发时,页面应该滚动到相应的位置,以显示当前数据。因此,建议在 onMounted 生命周期钩子中为滚动容器添加一个 ref 引用,并在 onScroll 方法中使用该引用来确定当前滚动位置,并根据该位置更新数据。