2 changed files with 122 additions and 5 deletions
@ -0,0 +1,115 @@ |
|||||
|
<template> |
||||
|
<div ref="wrapElRef" class="relative h-full w-full overflow-hidden p-0"> |
||||
|
<div ref="contentElRef" class="absolute h-full whitespace-nowrap" :class="state.animationClass" :style="contentStyle" @animationend="animationend"> |
||||
|
<slot></slot> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue' |
||||
|
|
||||
|
const props = defineProps(['content', 'delay', 'speed']) |
||||
|
|
||||
|
const emit = defineEmits(['end']) |
||||
|
const state = reactive({ |
||||
|
wrapWidth: 0, //父盒子宽度 |
||||
|
firstRound: true, //判断是否第一次动画 |
||||
|
duration: 0, //一次动画需要的时间 |
||||
|
offsetWidth: 0, //子盒子的宽度 |
||||
|
animationClass: '' //添加animate动画 |
||||
|
}) |
||||
|
|
||||
|
const wrapElRef = ref(null) |
||||
|
const contentElRef = ref(null) |
||||
|
|
||||
|
const contentStyle = computed(() => ({ |
||||
|
//第一次从头开始,第二次动画的时候需要从最右边出来所以宽度需要多出父盒子的宽度 |
||||
|
paddingLeft: (state.firstRound ? 0 : state.wrapWidth) + 'px', |
||||
|
//只有第一次的时候需要延迟 |
||||
|
animationDelay: (state.firstRound ? props.delay || 0.8 : 0) + 's', |
||||
|
animationDuration: state.duration + 's' |
||||
|
})) |
||||
|
|
||||
|
function animationend() { |
||||
|
state.firstRound = false |
||||
|
emit('end') |
||||
|
state.duration = (state.offsetWidth + state.wrapWidth) / (props.speed || 40) |
||||
|
state.animationClass = 'animate-infinite' |
||||
|
} |
||||
|
|
||||
|
let timer |
||||
|
watch( |
||||
|
() => props.content, |
||||
|
() => { |
||||
|
state.firstRound = true |
||||
|
state.animationClass = '' |
||||
|
state.duration = 0 |
||||
|
clearTimeout(timer) |
||||
|
timer = setTimeout(() => { |
||||
|
clearTimeout(timer) |
||||
|
const wrapWidth = wrapElRef.value.getBoundingClientRect().width |
||||
|
const offsetWidth = contentElRef.value.getBoundingClientRect().width |
||||
|
state.wrapWidth = wrapWidth |
||||
|
state.offsetWidth = offsetWidth |
||||
|
if (state.offsetWidth > state.wrapWidth) { |
||||
|
state.duration = offsetWidth / props.speed |
||||
|
state.animationClass = 'animate' |
||||
|
} else { |
||||
|
emit('end') |
||||
|
state.animationClass = '' |
||||
|
state.duration = 0 |
||||
|
} |
||||
|
}, 1000) |
||||
|
}, |
||||
|
{ |
||||
|
immediate: true |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
onBeforeUnmount(() => { |
||||
|
clearTimeout(timer) |
||||
|
timer = null |
||||
|
}) |
||||
|
</script> |
||||
|
<style scoped> |
||||
|
.relative { |
||||
|
position: relative; |
||||
|
} |
||||
|
.absolute { |
||||
|
position: absolute; |
||||
|
} |
||||
|
.w-full { |
||||
|
width: 100%; |
||||
|
} |
||||
|
.h-full { |
||||
|
height: 100%; |
||||
|
} |
||||
|
.animate { |
||||
|
animation: paomadeng linear; |
||||
|
} |
||||
|
.overflow-hidden { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.p-0 { |
||||
|
padding: 0; |
||||
|
} |
||||
|
.whitespace-nowrap { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
.animate-infinite { |
||||
|
animation: paomadeng-infinite linear infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes paomadeng { |
||||
|
to { |
||||
|
transform: translate3d(-100%, 0, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes paomadeng-infinite { |
||||
|
to { |
||||
|
transform: translate3d(-100%, 0, 0); |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue