效果如图, 上边无缝滚动

组件代码:
<template>
<div class="table-scroll-wrapper" ref="wrapperRef" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
<div class="table-scroll-content" ref="contentRef">
<!-- 原始内容 -->
<div ref="originalContentRef">
<slot></slot>
</div>
<!-- 克隆内容用于无缝滚动 -->
<div v-if="shouldScroll" ref="cloneContentRef">
<slot></slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
const props = defineProps({
// 滚动速度(像素/帧)
speed: {
type: Number,
default: 0.5
},
// 鼠标悬停是否暂停
hoverPause: {
type: Boolean,
default: true
},
// 是否启用滚动
enabled: {
type: Boolean,
default: true
}
});
// 引用DOM元素
const wrapperRef = ref<HTMLElement>();
const contentRef = ref<HTMLElement>();
const originalContentRef = ref<HTMLElement>();
const cloneContentRef = ref<HTMLElement>();
// 滚动状态
const animationId = ref<number>();
const currentTop = ref(0);
const isPaused = ref(false);
const contentHeight = ref(0);
// 计算属性:是否需要滚动
const shouldScroll = computed(() => {
return props.enabled && contentHeight.value > (wrapperRef.value?.offsetHeight || 0);
});
// 滚动动画函数
const scroll = () => {
if (!shouldScroll.value || isPaused.value || !wrapperRef.value || !contentRef.value) {
return;
}
// 向上滚动
currentTop.value -= props.speed;
contentRef.value.style.transform = `translateY(${currentTop.value}px)`;
// 当滚动过原始内容高度时,重置位置实现无缝滚动
if (Math.abs(currentTop.value) >= contentHeight.value) {
currentTop.value = 0;
}
// 继续下一帧动画
animationId.value = requestAnimationFrame(scroll);
};
// 开始滚动
const startScroll = () => {
if (animationId.value) {
cancelAnimationFrame(animationId.value);
}
if (shouldScroll.value) {
scroll();
}
};
// 停止滚动
const stopScroll = () => {
if (animationId.value) {
cancelAnimationFrame(animationId.value);
animationId.value = undefined;
}
};
// 鼠标进入处理
const handleMouseEnter = () => {
if (props.hoverPause) {
isPaused.value = true;
stopScroll();
}
};
// 鼠标离开处理
const handleMouseLeave = () => {
if (props.hoverPause) {
isPaused.value = false;
startScroll();
}
};
// 更新内容高度
const updateContentHeight = () => {
if (originalContentRef.value) {
contentHeight.value = originalContentRef.value.offsetHeight;
}
};
// 监听窗口大小变化
const handleResize = () => {
updateContentHeight();
// 如果条件变化,重新控制滚动
if (shouldScroll.value && !isPaused.value && props.enabled) {
startScroll();
} else if (!shouldScroll.value) {
stopScroll();
currentTop.value = 0;
if (contentRef.value) {
contentRef.value.style.transform = 'translateY(0)';
}
}
};
// 生命周期钩子
onMounted(async () => {
// 等待DOM完全渲染
await nextTick();
updateContentHeight();
// 开始滚动
if (shouldScroll.value && props.enabled) {
startScroll();
}
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
stopScroll();
window.removeEventListener('resize', handleResize);
});
</script>
<style scoped>
.table-scroll-wrapper {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
.table-scroll-content {
width: 100%;
position: absolute;
top: 0;
left: 0;
will-change: transform;
}
/* 确保表格行正确显示 */
:deep(.el-row) {
width: 100%;
box-sizing: border-box;
}
</style>
页面中的使用
<template>
<div style="height: 20.3rem; overflow: hidden; margin-top: .4rem;">
<simple-scroll :speed="0.5" :hoverPause="true" v-if="MaintainList && MaintainList.length > 0">
<el-row class="screen_ltable_cont" :key="index" v-for="(item, index) in MaintainList" :gutter="10">
<el-col :span="6">
<el-tooltip class="box-item" effect="dark" :content="item.deviceAdress" placement="top">
<div class="textellipsis">{{ index }}</div>
</el-tooltip>
</el-col>
<el-col :span="5">
<span class="screen_tag screen_warning" v-if="item.status == '1'">维修中</span>
<span class="screen_tag screen_primary" v-if="item.status == '2'">维修完成</span>
</el-col>
</el-row>
</simple-scroll>
</div>
</template>
<script setup>
import SimpleScroll from '../mycomponents/simpleScroll/index.vue';
</script>