前端在写一些页面时,比如可视化大屏,需要完成自动滚屏的功能,当然你可以去找现成的node依赖包搞定,但作为技术,我们需要了解且自己能完成这样的功能代码,因为部分公司严格管理package文件,不是你想引就能引。
网上也有很多此类的代码,但99%的帖子会有下面这样的跳动问题,在视觉上不够丝滑,让人不爽。
滚屏抖动
他们实现逻辑大概为下图样子,一个列表滚到顶部就切回顶部,这样就出现了跳动。
那么该如何解决呢,网上剩余的1%帖子,给出了方案,如下图,复制一次原列表,在复制列表顶部到达可视区域的顶部时切回原列表的顶部,这样就达成了丝滑的无限的滚动功能。
滚屏丝滑
滚动视图组件template内容。
<template>
<el-scrollbar ref="scrollbarRef"
:height="height"
@scroll="onScroll"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave">
<slot></slot>
</el-scrollbar>
</template>
slot插槽动态渲染列表,但什么情况下复制渲染列表呢,你们肯定知道在scrollHeight滚动高度超过clientHeight可视区域,就需要复制列表,否则不用复制,因为人家就没超过,不用滚动。
但动态渲染,会让你无法在nextTick内获取到高度,这该怎么办呢?上面代码里的scroll事件,想必大家都看到了,当触发滚动事件时触发scroll事件,这样就能知道列表是否超出可视区域需要滚动了。
直接放出完整代码,代码里有两种滚动方法requestAnimationFrame和setScrollTop,选择你高兴的留下。
<template>
<el-scrollbar ref="scrollbarRef"
:height="height"
@scroll="onScroll"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave">
<slot></slot>
<slot v-if="isDouble"></slot>
</el-scrollbar>
</template>
<script setup lang="ts">
const props = defineProps({
loop: {
type: Boolean,
default: false
},
speed: {
type: Number,
default: 50
},
height: {
type: [String, Number],
default: 'auto'
},
});
const scrollbarRef = ref(null);
// 定时器
let interval = undefined;
// 滚动条组件内部节点
let wrap: any;
// 设置滚动条每次滚动距离
const scrollbarDistance = ref(0);
onMounted(() => {
if (scrollbarRef.value && props.loop) {
wrap = scrollbarRef.value!.wrapRef;
autoScroll();
// scroll();
}
nextTick(() => {
});
});
onBeforeUnmount(() => {
clear();
});
// 丝滑滚动代码
const isDouble =ref(false);
const onScroll = () => {
isDouble.value = true;
};
// requestAnimationFrame动画
const flag = ref(true)
const scroll = () => {
if (flag.value) {
wrap.scrollTop += 1; // 设置滚动速度
if (wrap.clientHeight + wrap.scrollTop >= wrap.scrollHeight) {
wrap.scrollTop = 0; // 滚动到底部后跳回顶部
}
}
setTimeout(() => {
requestAnimationFrame(scroll); // 设置延迟,让滚动更缓慢
}, props.speed);
};
// scroll滾動事件
const autoScroll = () => {
if (interval) return;
interval = setInterval(() => {
if (scrollbarRef.value) {
// 当滚动条滚动到底部时自动返回到顶部
let f = isDouble.value ? (wrap!.scrollTop >= wrap!.scrollHeight / 2) : (wrap!.scrollTop + wrap!.clientHeight >= wrap!.scrollHeight);
// if (wrap!.scrollTop + wrap!.clientHeight >= wrap!.scrollHeight) {
// if (wrap!.scrollTop >= wrap!.scrollHeight / 2) {
if (f) {
scrollbarDistance.value = 0;
} else {
scrollbarDistance.value ++;
}
// 重置滚动条到顶部的距离
scrollbarRef.value.setScrollTop(scrollbarDistance.value);
}
}, props.speed)
};
//鼠标进入滚动区域时要清除计时器,停止自动滚动
const onMouseEnter = () => {
clear();
flag.value = false;
};
//鼠标离开滚动区域时从当前位置开始自动滚动
const onMouseLeave = () => {
scrollbarDistance.value = wrap!.scrollTop;
autoScroll();
flag.value = true;
};
// 清楚定时
const clear = () => {
clearInterval(interval);
interval = 0;
};
</script>
<style lang="scss" scoped></style>