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