翻牌器
ScrollCom.vue
<template>
<ul class="flex">
<ScrollNum
v-for="(num, idx) of numArr"
:key="idx"
as="li"
:i="num"
:delay="idx + 1"
/>
</ul>
</template>
<script>
import ScrollNum from '/ScrollNum.vue'
export default {
name: 'ScrollCom',
components: { ScrollNum },
data: () => ({ num: 886 }),
computed: {
numArr () {
const str = String(this.num)
let arr = []
for (let i = 0; i < str.length; i++) {
arr.push(parseInt(str[i]))
}
return arr
}
},
mounted () {
setInterval(() => this.num++, 10000)
}
}
</script>
<style scoped>
.flex {
display: flex;
}
ul {
padding: 0;
margin: 0;
list-style: none;
}
</style>
ScrollNum.vue
<template> <component :is="as" class="scroll-num" :class="{ 'border-animate': showAnimate }" :style="{ '--i': i, '--delay': delay }" @animationend="showAnimate = false" > <ul ref="ul" :class="{ animate: showAnimate }"> <li>0</li> <li v-for="item in 9" :key="item">{{ item }}</li> <li>0</li> </ul> <svg width="0" height="0"> <filter id="blur"> <feGaussianBlur in="SourceGraphic" :stdDeviation="`0 ${blur}`" /> </filter> </svg> </component> </template> <script> export default { name: 'ScrollNum', props: { as: { type: String, default: 'div' }, i: { type: Number, default: 0, validator: v => v < 10 && v >= 0 && Number.isInteger(v) }, delay: { type: Number, default: 1 }, blur: { type: Number, default: 2 } }, data: () => ({ timer: null, showAnimate: true }), watch: { i () { this.showAnimate = true } }, mounted () { const ua = navigator.userAgent.toLowerCase() const testUA = regexp => regexp.test(ua) const isSafari = testUA(/safari/g) && !testUA(/chrome/g) // Safari浏览器的兼容代码 isSafari && (this.timer = setTimeout(() => { this.$refs.ul.setAttribute('style', ` animation: none; transform: translateY(calc(var(--i) * -9.09%)) `) }, this.delay * 1000)) }, beforeUnmount () { clearTimeout(this.timer) } } </script> <style scoped> .scroll-num { var(--width, 20px); /* var(自定义值, 默认值) */ height: var(--height, calc(var(--width, 20px) * 1.8)); color: var(--color, #333); font-size: var(--height, calc(var(--width, 20px) * 1.1)); line-height: var(--height, calc(var(--width, 20px) * 1.8)); text-align: center; overflow: hidden; } .animate { animation: move 0.3s linear infinite, bounce-in-down 1s calc(var(--delay) * 1s) forwards; } .border-animate { animation: enhance-bounce-in-down 1s calc(var(--delay) * 1s) forwards; } ul { padding: 0; margin: 0; list-style: none; transform: translateY(calc(var(--i) * -9.09%)); } @keyframes move { from { transform: translateY(-90%); filter: url(#blur); } to { transform: translateY(1%); filter: url(#blur); } } @keyframes bounce-in-down { from { transform: translateY(calc(var(--i) * -9.09% - 7%)); filter: none; } 25% { transform: translateY(calc(var(--i) * -9.09% + 3%)); } 50% { transform: translateY(calc(var(--i) * -9.09% - 1%)); } 70% { transform: translateY(calc(var(--i) * -9.09% + 0.6%)); } 85% { transform: translateY(calc(var(--i) * -9.09% - 0.3%)); } to { transform: translateY(calc(var(--i) * -9.09%)); } } @keyframes enhance-bounce-in-down { 25% { transform: translateY(8%); } 50% { transform: translateY(-4%); } 70% { transform: translateY(2%); } 85% { transform: translateY(-1%); } to { transform: translateY(0); } } </style>