目录

记 Vue 滚动列表实现和踩坑

最终效果

  • 初始化时列表自动滚动
  • 鼠标 hover 出现滚动条,停止滚动
  • 到达底部时回到顶部

https://wumanhoblogimg.obs.cn-south-1.myhuaweicloud.com/images/scroll/scroll.gif

 

静态部分

静态部分代码就不详细说了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<template>
  <div id="app">
    <div class="box">
      <!--标题-->  
      <div class="header">
        <span class="title">滚动列表</span>
      </div>
        <!--内容部分-->
      <div class="content">
        <ul class="scroll-list" ref="scrollListRef">
            <!--内容卡片-->
          <li class="scroll-item"
              v-for="user in listData"
              :key="user.name"
          >
            <p>用户姓名: {{ user.name }}</p>
            <p>用户年龄: {{ user.age }}</p>
            <p>用户身高: {{ user.height }}cm</p>
            <p>用户体重: {{ user.width }}kg</p>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
@import "static/scss/base";

.box {
  width: 500px;
  height: 400px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;

  .header {
    width: 100%;
    height: 46px;
    display: flex;
    align-items: center;
    padding-left: 15px;
    color: #303133;
    background-color: #f5f7fa;
    border-bottom: 1px solid #dcdfe6;

    .title {
      font-size: 14px;
      font-weight: bold;
    }
  }

  .content {
    height: calc(100% - 46px);
    padding: 16px;

    .scroll-list {
      height: 100%;
      width: 100%;

      &::-webkit-scrollbar-track-piece {
        background: #eef2f3;
      }

      &::-webkit-scrollbar {
        width: 8px;
      }

      &::-webkit-scrollbar-thumb {
        background: #dcdfe6;
        border-radius: 20px;
      }

      overflow: hidden;
      &:hover {
        overflow: auto;
      }

      .scroll-item {
        border: 1px solid #dcdfe6;
        border-radius: 5px;
        color: #606266;
        background-color: #f5f7fa;
        letter-spacing: 2px;
        margin-bottom: 16px;
        padding: 10px 15px;
        font-size: 14px;
      }
    }
  }
}
</style>

 

滚动逻辑实现

核心逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
scrollFn() {
    //通过ref获取到ul的dom元素
      const scrollHeight = this.$refs.scrollListRef.scrollHeight
      const offsetHeight = this.$refs.scrollListRef.offsetHeight
      //设置定时器,获取到timer
      this.timer = setInterval(() => {
          //如果元素的 scrollTop + offsetHeight 等于 scrollHeight的话
          //意味着已经到底了,将 scrollTop 重置为 0
        if (this.$refs.scrollListRef.scrollTop + offsetHeight === scrollHeight) {
          this.$refs.scrollListRef.scrollTop = 0
        } else {
            //如果还未置底, scrollTop +=1 无限循环
          this.$refs.scrollListRef.scrollTop += 1
        }
      }, 30)
    }

⚠️ 这里有一个坑需要注意

当 Windows 用户的显示设置中的缩放不等于 100% 时,scrollTop 可能变成小数

https://wumanhoblogimg.obs.cn-south-1.myhuaweicloud.com/images/scroll/%E7%BC%A9%E6%94%BE.png

在控制台打印出来的 scrollTop 如下图:

https://wumanhoblogimg.obs.cn-south-1.myhuaweicloud.com/images/scroll/%E5%B0%8F%E6%95%B0.png

我们在定时器中设置的是 +=1 但是最终结果却是小数,这样就会导致到达底部时,scrollTop + offsetHeight 的值可能小于 scrollHeight,以至于无法回到顶部,附上 MDN 的解释。

在使用显示比例缩放的系统上,scrollTop 可能会提供一个小数。

解决的办法是将判断逻辑改为 scrollTop + offsetHeight +1,这样就可以保证到底之后可以回到顶部。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 //设置定时器,获取到timer
      this.timer = setInterval(() => {
          //修改判断逻辑
        if (this.$refs.scrollListRef.scrollTop + offsetHeight +1 >= scrollHeight) {
          this.$refs.scrollListRef.scrollTop = 0
        } else {
            //如果还未置底, scrollTop +=1 无限循环
          this.$refs.scrollListRef.scrollTop += 1
        }
      }, 30)

清除定时器

设置一个清除定时器的方法,在鼠标覆盖事件以及组件销毁时调用

1
2
3
clearTimer() {
   clearInterval(this.timer)
}

设置监听

到这里核心逻辑就完成了,将方法挂到 DOM 上,以及 Mounted 初始化时调用

1
2
3
4
5
6
<li class="scroll-item"
              v-for="user in listData"
              :key="user.name"
              @mouseleave="scrollFn"
              @mouseover="clearTimer"
          >
1
2
3
4
5
6
7
8
 //初始化调用滚动函数
mounted() {
  this.scrollFn()
},
//销毁清除定时器
beforeDestroy() {
  this.clearTimer()
},

<script> 完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<script>
export default {
  mounted() {
    this.scrollFn()
  },
  beforeDestroy() {
    this.clearTimer()
  },
  methods: {
    scrollFn() {
      const scrollHeight = this.$refs.scrollListRef.scrollHeight
      const offsetHeight = this.$refs.scrollListRef.offsetHeight
      this.timer = setInterval(() => {
        if (this.$refs.scrollListRef.scrollTop + offsetHeight +1 >= scrollHeight) {
          this.$refs.scrollListRef.scrollTop = 0
        } else {
          this.$refs.scrollListRef.scrollTop += 1
        }
      }, 30)
    },
    clearTimer() {
      clearInterval(this.timer)
    }
  },
  data() {
    return {
      listData: [
        {name: '张三', age: 18, height: 178, width: 77},
        {name: '李四', age: 19, height: 178, width: 77},
        {name: '王五', age: 20, height: 178, width: 77},
        {name: '赵六', age: 21, height: 178, width: 77},
        {name: '测试用户', age: 22, height: 178, width: 77},
        {name: '测试用户No.2', age: 23, height: 178, width: 77},
      ],
      timer: null
    }
  }
}
</script>

 

(完)