ufutx-pc-website/src/components/Marquee.vue

114 lines
3.0 KiB
Vue
Raw Normal View History

2025-07-04 11:29:23 +08:00
<template>
<div ref="containerRef" :class="['marquee-container relative overflow-hidden', containerClass]">
<div
ref="contentRef"
class="marquee-content flex gap-[50px] whitespace-nowrap"
:style="{
'--marquee-gap': '50px',
'--initial-offset': initialOffset + 'px' // 传递初始偏移变量
}"
>
<div ref="originalRef" class="original-content flex gap-[50px]">
<slot />
</div>
<div class="clone-content flex gap-[50px]">
<slot />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watchEffect } from 'vue'
// 接收初始偏移量props
const props = withDefaults(
defineProps<{
duration: number
reverse?: boolean
repeatCount?: number | string
pauseOnHover?: boolean
containerClass?: string
initialOffset?: number // 新增初始偏移量px负数向左偏
}>(),
{
reverse: false,
repeatCount: 3,
pauseOnHover: true,
initialOffset: 0, // 默认无偏移
containerClass: '' // 添加默认值为空字符串
}
)
const containerRef = ref<HTMLDivElement | null>(null)
const contentRef = ref<HTMLDivElement | null>(null)
const originalRef = ref<HTMLDivElement | null>(null)
const originalWidth = ref(0)
// 计算单份内容宽度
const calculateWidth = () => {
if (originalRef.value) {
originalWidth.value = originalRef.value.offsetWidth
}
}
// 动态生成动画(包含初始偏移)
const updateAnimation = () => {
if (!contentRef.value || !originalRef.value) return
// 动画方向
const direction = props.reverse ? 'reverse' : ''
contentRef.value.style.animation = `marquee ${props.duration}s linear infinite ${direction}`
// 关键帧:从初始偏移位置开始滚动
const style = document.createElement('style')
style.id = 'marquee-keyframes'
style.textContent = `
@keyframes marquee {
0% { transform: translateX(var(--initial-offset)); }
100% { transform: translateX(calc(var(--initial-offset) - ${originalWidth.value}px)); }
}
`
// 替换旧关键帧
const existingStyle = document.head.querySelector('#marquee-keyframes')
if (existingStyle) document.head.removeChild(existingStyle)
document.head.appendChild(style)
}
// 初始化与监听
onMounted(() => {
calculateWidth()
updateAnimation()
})
watchEffect(() => {
calculateWidth()
updateAnimation()
})
// 悬停暂停逻辑
watchEffect(() => {
if (containerRef.value && props.pauseOnHover) {
containerRef.value.addEventListener('mouseenter', () => {
if (contentRef.value) contentRef.value.style.animationPlayState = 'paused'
})
containerRef.value.addEventListener('mouseleave', () => {
if (contentRef.value) contentRef.value.style.animationPlayState = 'running'
})
}
})
</script>
<style scoped lang="less">
.marquee-content {
display: flex;
flex-wrap: nowrap;
transition: transform 0.3s ease;
}
.original-content,
.clone-content {
display: flex;
flex-wrap: nowrap;
}
</style>