子组件
<template><div class="single-scroll-container" ref="container" @mouseenter="pause" @mouseleave="resume"><divclass="single-scroll-content":style="{ transform: `translateX(${translateX}px)` }"ref="content"><div class="scroll-items" ref="items"><slot /></div><!-- 复制一份用于无缝衔接 --><div class="scroll-items"><slot /></div></div></div></template><script setup lang="ts">import { ref, onMounted, onBeforeUnmount } from 'vue';const props = defineProps({speed: { type: Number, default: 1 },direction: { type: String, default: 'left' }});const container = ref<HTMLElement | null>(null);const content = ref<HTMLElement | null>(null);const items = ref<HTMLElement | null>(null);const translateX = ref(0);let animationId: number | null = null;let itemWidth = 0;let paused = false;const pause = () => { paused = true; };const resume = () => { paused = false; };const animate = () => {if (!paused && itemWidth > 0) {if (props.direction === 'right') {translateX.value += props.speed;if (translateX.value >= 0) {translateX.value = -itemWidth;}} else {translateX.value -= props.speed;if (Math.abs(translateX.value) >= itemWidth) {translateX.value = 0;}}}animationId = requestAnimationFrame(animate);};onMounted(() => {setTimeout(() => {if (items.value) {itemWidth = items.value.offsetWidth;}animationId = requestAnimationFrame(animate);}, 100);});onBeforeUnmount(() => {if (animationId) cancelAnimationFrame(animationId);});</script><style scoped>.single-scroll-container {width: 100%;overflow: hidden;background: transparent;}.single-scroll-content {display: flex;white-space: nowrap;will-change: transform;}.scroll-items {display: flex;white-space: nowrap;}</style>
父组件
<!-- 向左滚动(默认) -->
<SingleLineInfiniteScroll :speed="1"><!-- ...内容... -->
</SingleLineInfiniteScroll><!-- 向右滚动 -->
<SingleLineInfiniteScroll :speed="1" direction="right"><!-- ...内容... -->
</SingleLineInfiniteScroll>