效果动画图:
完整代码如下
<template>
<div class="number-flip">
<div class="box-item" v-for="(digit, index) in digits" :key="index">
<span class="item-num" ref="spanRefs">0123456789</span>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue'
const props = defineProps<{
number: number,
enableInitialAnimation?: boolean // 是否启用初始动画
}>()
const spanRefs = ref<HTMLElement[]>([])
const digits = ref<number[]>([])
// 将数字转换为数字数组
const updateDigits = (num: number) => {
digits.value = String(num).split('').map(Number)
}
// 更新数字位置
const updateDigitPositions = (withTransition = true) => {
requestAnimationFrame(() => {
spanRefs.value.forEach((span, index) => {
if (span) {
if (!withTransition) {
// 临时移除过渡效果
span.style.transition = 'none'
}
span.style.transform = `translate(-50%, -${digits.value[index] * 10}%)`
if (!withTransition) {
// 强制浏览器重绘
span.offsetHeight
// 恢复过渡效果
span.style.transition = ''
}
}
})
})
}
// 初始化
onMounted(() => {
updateDigits(props.number)
updateDigitPositions(props.enableInitialAnimation)
})
// 监听数字变化
watch(() => props.number, (newVal) => {
updateDigits(newVal)
updateDigitPositions(true) // 数字变化时总是启用动画
})
</script>
<style lang="less" scoped>
.number-flip {
display: inline-flex;
align-items: center;
.box-item {
width: 0.8em;
height: 1.4em;
border-radius: 2px;
position: relative;
margin: 0 1px;
overflow: hidden;
.item-num {
position: absolute;
top: 1%;
left: 40%;
font-weight: bold;
transform: translateX(-50%);
writing-mode: vertical-lr;
text-orientation: upright;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
line-height: 1.6;
letter-spacing: 0.2em;
font-size: 1.2em;
}
}
}
</style>
使用方式如下:
<NumberTransition :number="savedCnt" :enable-initial-animation="false" style="color:blue;font-weight:bold;vertical-align: middle;"/>

文章评论