一.实现思路与效果

  1. 使用ref获取dom列表元素
  2. 使用v-if判断鼠标移入移出触发不同的列表显示
  3. 使用setInterval控制一个方法让列表不断滚动
  4. 结合scrollTop;scrollHeight和clientHeight判断滚动条是否滚动到底部,到底则删除最顶元素,并把最顶元素插入到最底元素,不断如此循环
  5. 实现鼠标移入停止滚动(使用clearTimeout,setInterval会返回一个id,这里使用timer接受该id )并且切换到可滑动的列表,移出则显示滚动的列表(@mousemove和@mouseleave表达鼠标移入移出事件)
  6. 组件销毁时清除定时器
    鼠标移入则列表可滑动,移出则自动滚动

二.具体代码

1.html

 <div class="rank">
        <div
          @mousemove="mousemove"
          ref="tableRef"
          style="height: 300px; overflow: hidden"
          v-if="mouseMend"
        >
          <li
            v-for="(item, index) of villageList"
            @click="focusVillage(item)"
            :class="{ active: activeVillage === item.id }"
            :key="item"
          >
            <span class="index">
              <img v-if="index === 0" src="../../../../assets/imageV2/奖牌-01.png" alt="" />
              <img v-if="index === 1" src="../../../../assets/imageV2/奖牌-02.png" alt="" />
              <img v-if="index === 2" src="../../../../assets/imageV2/奖牌-03.png" alt="" />
              <span class="round-number" v-if="index > 2">{{ index + 1 }}</span>
            </span>
            <span class="village-name">{{ item.name }}</span>
            <span class="event-count">{{ `${formatNumber(item.quota)}%` }}</span>
          </li>
        </div>
        <div @mouseleave="mouseleave" v-else>
          <li
            v-for="(item, index) of villageList"
            @click="focusVillage(item)"
            :class="{ active: activeVillage === item.id }"
            :key="item"
          >
            <span class="index">
              <img v-if="index === 0" src="../../../../assets/imageV2/奖牌-01.png" alt="" />
              <img v-if="index === 1" src="../../../../assets/imageV2/奖牌-02.png" alt="" />
              <img v-if="index === 2" src="../../../../assets/imageV2/奖牌-03.png" alt="" />
              <span class="round-number" v-if="index > 2">{{ index + 1 }}</span>
            </span>
            <span class="village-name">{{ item.name }}</span>
            <span class="event-count">{{ `${formatNumber(item.quota)}%` }}</span>
          </li>
        </div>
      </div>

2.js

//列表滚动ref绑定初始化
const timer = ref(null)
const tableRef = ref(null)
const mouseMend = ref(true) //判断鼠标移入移出排行榜表格
//等同于vue2中的destroyed
onUnmounted(() => {
  //清除定时器
  clearTimeout(timer.value)
})
const mousemove = () => {
  mouseMend.value = false
  //清除定时器,如果希望在setInterval之前终止其运行就可以使用clearTimeout()
  clearTimeout(timer.value)
}
const mouseleave = () => {
  mouseMend.value = true
  start()
}

//开启定时器方法
const start = () => {
  //定时器触发周期
  let speed = ref(40)
  //setInterval会返回一个id,clearTimeout拿到该id才可清除
  timer.value = setInterval(MarqueeTest, speed.value)
}

const MarqueeTest = () => {
  let scroll = tableRef.value
  //组件进行滚动
  scroll.scrollTop += 1
  // console.log(scroll.scrollTop,scroll.scrollHeight, scroll.clientHeight)
  //判断滚动条是否滚动到底部
  if (scroll.scrollTop == scroll.scrollHeight - scroll.clientHeight) {
    //获取组件第一个节点
    let firstNode = scroll.childNodes[0]
    // //删除节点
    scroll.removeChild(firstNode)
    //将该节点拼接到组件最后
    scroll.append(firstNode)
  }
}

//用nextTick 的原因是 需要等dom元素加载完毕后 再执行方法

nextTick(() => {
  start()
})

三.参考例子代码(可直接运行)

<template>
    <div>
        <div @mousemove="testMove" @mouseleave="testMend" >
            <div ref="roll" style="height: 110px;overflow: hidden;margin:20px;">
                <div v-for="item in listData" :key="item.id">
                    <span>{{item.name}}</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
    import {ref,reactive,onBeforeUnmount,onUnmounted,nextTick} from 'vue'
	//定时器初始化
    let timer = ref(null)
    //ref绑定初始化
    let roll = ref(null)
    //列表数据初始化
    const listData = reactive([
        {name:'我是dom第一个'},
        {name:'我是dom第二个'},
        {name:'我是dom第三个'},
        {name:'我是dom第四个'},
        {name:'我是dom第五个'},
        {name:'我是dom第六个'},
        {name:'我是dom第七个'},
        {name:'我是dom第八个'},
        {name:'我是dom第九个'},
        {name:'我是dom第十个'},
    ])

    //等同于vue2中的beforeDestroy
    onBeforeUnmount(()=>{
        //清除定时器
        clearTimeout(timer.value)
    })
    //等同于vue2中的destroyed
    onUnmounted(()=>{
        //清除定时器
        clearTimeout(timer.value)
    })
    
    /**
     * @Description: 鼠标移动事件
     * @Author: admin
     */    
    function testMove(){
        clearTimeout(timer.value)
    }
    /**
     * @Description: 鼠标离开事件
     * @Author: admin
     */    
    function testMend(){
        start()
    }
	//开启定时器方法
    function start(){
        //清除定时器
      clearTimeout(timer.value)
      //定时器触发周期
      let speed = ref(75)
      timer.value = setInterval(MarqueeTest, speed.value)
    }
    function MarqueeTest() {
      let test1 = roll.value
      //判断组件是否渲染完成
      if (test1.offsetHeight == 0) {
        test1 = roll.value
      } else {
        //如果列表数量过少不进行滚动
        if(test1.childNodes.length<6){
            clearTimeout(timer.value)
            return;
          }
        //组件进行滚动
        test1.scrollTop += 1
        //判断滚动条是否滚动到底部
        if (test1.scrollTop == (test1.scrollHeight - test1.clientHeight)) {
          //获取组件第一个节点
          let a = test1.childNodes[0]
          //删除节点
          test1.removeChild(a)
          //将该节点拼接到组件最后
          test1.append(a)
        }
      }
    }
    //vue2中在created中调用
    //vue3中 setup 包含beforeCreate和created
    //启动定时器
    start()

    //注
    //示例中 listData 写的静态数据 可以直接调用start()
    //如果是接口获取 listData 列表时 需在 nextTick 中调用 start();否则,
    //进入页面不会滚动 必须鼠标移入移出才会滚动
    //用nextTick 的原因是 需要等dom元素加载完毕后 再执行方法

    nextTick(()=>{
        start()
    })
</script>

四.知识点补充

  1. setInterval可以将一个函数,每隔一段时间执行一次

  2. clearTimeout()用于重置js定时器,如果你希望阻止setTimeout的运行,就可以使用clearTimeout方法。

  3. scrollHeight;scrollTop和clientHeight

    • scrollHeight:其中包括由于溢出而在屏幕上不可见的内容。(内容的实际高度+上下padding(如果没有限制div的height,即height是自适应的,一般是scrollHeight==clientHeight)

在这里插入图片描述

  • scrollTop:有滚动条的时候,scrollTop 值是从元素顶部到其最顶部可见内容的距离的度量。当元素的内容没有垂直滚动条时,则scrollTop=0

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EkOMFzb0-1654830818372)(C:\Users\flision1901\AppData\Roaming\Typora\typora-user-images\image-20220610084307479.png)]

  • clientHeight:样式的height+上下padding

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRdjGc38-1654830818372)(C:\Users\flision1901\AppData\Roaming\Typora\typora-user-images\image-20220610084419862.png)]

  • 所以从以上三个概念可以得出,判断内部元素滚动是否到底的表达式是:

    scrollHeight - scrollTop === clientHeight

参考链接

  1. 参考例子实现方法
  2. scrollHeight;scrollTop和clientHeight介绍
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐