vue2实现滚动条自动滚动

需求描述:大屏项目展示数据时,通常希望表格能自动滚动,本篇内容介绍了三种方式来实现。

1.利用requestAnimationFrame实现

  • 1.鼠标移入,停止滚动;鼠标移出,继续滚动;

  • 2.支持两种滚动效果:

    1)当滚动到底部时,重置scrollTop为0,重新开始滚动

    2)无缝滚动,一直往下滚动(最好隐藏滚动条)

  • 3.数据量过少时,不推荐使用,当表格内容高度刚刚超过容器高度会出现跳动现象;

xml 复制代码
<template>
  <div class="content">
    <span class="title">方案一</span>
    <ul id="main" class="main">
      <li v-for="(item, index) in data" :key="index" class="name">
        {{ item.name }} {{ item.value }}%
      </li>
    </ul>
  </div>
</template>

<script>
const scrollStateMap = new WeakMap()
export default {
  name: 'Example03Basic',
  data() {
    return {
      data: [
        { name: '百年孤独', value: '76' },
        { name: '时间简史', value: '34' },
        { name: '国富论', value: '89' },
        { name: '人类简史', value: '12' },
        { name: '追风筝的人', value: '53' },
        { name: '1984', value: '98' },
        { name: '傲慢与偏见', value: '21' },
        { name: '物种起源', value: '67' },
        { name: '悲惨世界', value: '45' },
        { name: '资本论', value: '3' },
        { name: '小王子', value: '82' },
        { name: '麦田里的守望者', value: '60' },
        { name: '局外人', value: '77' },
        { name: '存在与时间', value: '29' },
        { name: '不能承受的生命之轻', value: '91' },
        { name: '战争与和平', value: '14' },
        { name: '安娜·卡列尼娜', value: '55' },
        { name: '罪与罚', value: '38' },
        { name: '尤利西斯', value: '72' },
        { name: '洛丽塔', value: '9' },
        { name: '了不起的盖茨比', value: '64' },
        { name: '飘', value: '17' },
        { name: '简爱', value: '86' },
        { name: '老人与海', value: '41' },
        { name: '哈姆雷特', value: '95' },
        { name: '双城记', value: '7' },
        { name: '鲁滨逊漂流记', value: '50' },
        { name: '三体', value: '23' },
        { name: '白夜行', value: '68' },
        { name: '活着', value: '83' }
      ]
    }
  },
  mounted() {
    this.scrollUp('#main')
  },
  beforeDestroy() {
    // 清理所有关联 DOM 的状态
    if (scrollStateMap.length > 0) {
      scrollStateMap.forEach((state, dom) => {
        cancelAnimationFrame(state.animationFrameId)
        state.listeners.forEach(({ type, handler }) => {
          dom.removeEventListener(type, handler)
        })
        scrollStateMap.delete(dom)
      })
    }
  },
  methods: {
    scrollUp(tableRef) {
      const elements = document.querySelectorAll(tableRef)
      if (elements.length === 0) return
      const dom = elements[0]

      // 初始化或获取状态
      let state = scrollStateMap.get(dom)
      if (!state) {
        state = {
          isScrolling: true,
          animationFrameId: null,
          listeners: []
        }
        scrollStateMap.set(dom, state)
      }

      // 清除旧动画帧
      if (state.animationFrameId) {
        cancelAnimationFrame(state.animationFrameId)
      }

      // 定义动画
      const animate = () => {
        if (!state.isScrolling) return

        dom.scrollTop += 1
        if (dom.scrollTop >= dom.scrollHeight - dom.clientHeight) {
          // 当滚动到底部时,重置scrollTop为0,重新开始滚动
          // dom.scrollTop = 0
          // 无缝滚动,一直往下滚动(最好隐藏滚动条)
          if (dom.children[0]) {
            const firstItem = dom.children[0]
            dom.appendChild(firstItem.cloneNode(true))
            dom.removeChild(firstItem)
          }
        }

        state.animationFrameId = requestAnimationFrame(animate)
      }

      // 事件处理
      const onMouseOver = () => {
        state.isScrolling = false
      }
      const onMouseOut = () => {
        state.isScrolling = true
        animate()
      }

      // 绑定事件
      dom.addEventListener('mouseover', onMouseOver)
      dom.addEventListener('mouseout', onMouseOut)
      state.listeners.push(
        { type: 'mouseover', handler: onMouseOver },
        { type: 'mouseout', handler: onMouseOut }
      )

      // 启动滚动
      if (dom.scrollHeight > dom.clientHeight) {
        animate()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.content {
  padding: 2%;
  .title {
    font-weight: bold;
    padding: 0 0 0 12px;
    border-left: 4px solid;
    border-left-color: #409eff;
  }
  .main {
    width: 20%;
    height: 200px;
    overflow: auto;
    background: #08318c;
    border-radius: 5px;
    color: #fff;
    line-height: 30px;
  }

  .main::-webkit-scrollbar {
    width: 10px;
  }

  .main::-webkit-scrollbar-thumb {
    background-color: #2d69d4;
    border-radius: 10px;
  }
}
</style>

2.利用定时器(不推荐)

  • 1.鼠标移入,停止滚动;鼠标移出,继续滚动;

  • 2.支持两种滚动效果:

    1)当滚动到底部时,重置scrollTop为0,重新开始滚动

    2)无缝滚动,一直往下滚动(最好隐藏滚动条)

  • 3.数据量过少时,不推荐使用,当表格内容高度 刚刚超过容器高度会出现跳动现象;

  • 4.使用 setInterval(..., 100) 固定间隔滚动,非帧率同步。导致滚动不够平滑,且 scrollTop 的频繁修改可能触发重排。

javascript 复制代码
 scrollUp(tableRef) {
      clearInterval(this.timeInterval)
      this.timeInterval = null
      //  1. 安全获取 DOM 元素
      const elements = document.querySelectorAll(tableRef)
      if (elements.length === 0) {
        return
      }
      const dom = elements[0]
      // 滚动高度是否大于可视高度,即判断是否有滚动条
      if (dom && dom.scrollHeight > dom.clientHeight) {
        let tableScroll = true
        // 添加鼠标悬停和移出事件
        dom.addEventListener('mouseover', () => {
          tableScroll = false
        })
        dom.addEventListener('mouseout', () => {
          tableScroll = true
        })

        this.timeInterval = setInterval(() => {
          if (tableScroll) {
            dom.scrollTop += 1 // 减小步长提升平滑度
            if (dom.clientHeight + dom.scrollTop >= dom.scrollHeight) {
              // 当滚动到底部时,重置scrollTop为0,重新开始滚动
              // dom.scrollTop = 0
              // 无缝滚动,一直往下滚动(最好隐藏滚动条)
              if (dom.children[0]) {
                const firstItem = dom.children[0]
                dom.appendChild(firstItem.cloneNode(true))
                dom.removeChild(firstItem)
              }
            }
          }
        }, 100)
        this.$once('hook:beforeDestroy', () => {
          clearInterval(this.timeInterval)
          this.timeInterval = null
        })
      }
    },

3.借助vue 插件vue-seamless-scroll(推荐)

  • 支持设置滚动方向,滚动速度,单步停顿;
  • 支持配置鼠标悬停停止;
  • 支持switch控制切换;
  • 支持echart图表无缝滚动;

文档地址:chenxuan0000.github.io/vue-seamles...

使用注意项
  • 1.最外层容器需要手动设置width、height、overflow:hidden
  • 2.左右的无缝滚动需要给主内容区域(即默认slot插槽提供)设定合适的css width属性(否则无法正确计算实际宽度)。 也可以通过给他设置为display:flex;无需设置css width属性
  • 3.step值不建议太小,不然会有卡顿效果(如果设置了单步滚动,step需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~,比如单步设置的30,step不能为4)
  • 4.需要实现手动切换左右滚动的时候,必须设置autoPlay:false(1.1.17版本开始,只需要设置navigation:false),目前不支持环路
  • 5.提供了slot left-switch || right-switch可以自由定义需要的按钮样式 外层有div已经定位了位置居中,距离两边侧的距离可以通过switchOffset参数调整
  • 6.当按钮到达边界位置,会自动为无法点击状态按钮加上定义的switchDisabledClass: 'disabled',可以按需配置

真实使用效果:

xml 复制代码
<template>
  <div class="main">
    <span class="title">向右滚动</span>
    <vue-seamless-scroll
      :data="listData"
      :class-option="classOption1"
      class="warp"
    >
      <ul class="ul-item" style="display: flex">
        <li v-for="(item, index) in listData" :key="index" class="li-item">
          <div class="rect" :style="{ backgroundColor: item.color }" />
          <div>{{ item.name }}</div>
        </li>
      </ul>
    </vue-seamless-scroll>
    <span class="title">向下滚动</span>
    <vue-seamless-scroll
      :data="listData"
      :class-option="classOption2"
      class="warp"
    >
      <ul class="ul-item">
        <li v-for="(item, index) in listData" :key="index" class="li-item2">
          <div class="rect" :style="{ backgroundColor: item.color }" />
          <div>{{ item.name }}</div>
        </li>
      </ul>
    </vue-seamless-scroll>
  </div>
</template>

<script>
import vueSeamlessScroll from 'vue-seamless-scroll'
export default {
  name: 'Example03Basic',
  components: {
    vueSeamlessScroll
  },
  data() {
    return {
      listData: [
        { name: '百年孤独', value: '76', color: '#D25486' },
        { name: '时间简史', value: '34', color: '#EEBE3F' },
        { name: '国富论', value: '89', color: '#23B5FF' },
        { name: '人类简史', value: '12', color: '#0043CB' },
        { name: '追风筝的人', value: '53', color: '#00CFB5' },
        { name: '1984', value: '98', color: '#0EEDFE' },
        { name: '傲慢与偏见', value: '21', color: '#0E96FE' },
        { name: '物种起源', value: '67', color: '#345EBF' }
      ],
      classOption1: {
        direction: 3 //方向: 0 往下 1 往上 2 向左 3 向右
      },
      classOption2: {
        direction: 0
      }
    }
  },
  mounted() {

  },

  methods: {

  }
}
</script>

<style lang="scss" scoped>
.main {
  padding: 2%;
}
.title {
  font-weight: bold;
  padding: 0 0 0 12px;
  border-left: 4px solid;
  border-left-color: #409eff;
}
.warp {
  width: 500px;
  margin: 1% 0;
  overflow: hidden;
  border: 1px solid rgb(64, 158, 255);
  background: #08318c;
  color:#fff;
  height: 80px;

  ul {
    list-style: none;
    padding: 0;
    margin: 0 auto;
    &.ul-item {
      .li-item {
        height: 80px;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .li-item2 {
        display: flex;
      }
      .rect {
        width: 10px;
        height: 10px;
        border-radius: 2px;
        margin: 0 10px;
      }
    }
  }
}
</style>
相关推荐
庸俗今天不摸鱼17 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下24 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox35 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞38 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行38 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581039 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周42 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei1 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring