效果图
代码
<template>
<div class="number-scroller">
<div
class="viewport"
:style="{ width: width + 'px', height: height + 'px' }"
>
<div class="number-scroller-box" ref="num">
<div v-for="number in numbers" :key="number" class="number-item">
{{ number }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
const props = defineProps({
width: {
type: Number,
default: 60
},
height: {
type: Number,
default: 60
},
// 目标数字
targetNumber: {
type: Number,
default: 5
},
// 动画持续时间
animationDuration: {
type: Number,
default: 10
},
// 动画缓动函数
easing: {
type: String,
default: 'cubic-bezier(.5,.13,.01,.35)'
},
// 动画延迟时间
delay: {
type: Number,
default: 1
},
// 旋转方向
direction: {
type: String,
default: 'vertical', // 默认横向旋转
validator: value => ['horizontal', 'vertical'].includes(value)
}
});
const numbers = ref([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
const num = ref();
/**
* rotateToTarget 旋转到目标数字
*/
function rotateToTarget (targetNumber, direction) {
const angle = 360 / numbers.value.length;
const targetIndex = numbers.value.indexOf(targetNumber);
if (targetIndex === -1) return; // 如果目标数字不存在,直接返回
const randomCircles = Math.floor(Math.random() * 3) + 1; // 随机生成1到3圈
const totalAngle = 360 * randomCircles - targetIndex * angle;
const rotateAxis = direction === 'horizontal' ? 'Y' : 'X';
document.body.getBoundingClientRect();
num.value.style.transition = `transform ${props.animationDuration}s ${props.easing} ${props.delay}s`;
num.value.style.transform = `rotate${rotateAxis}(${totalAngle}deg)`;
}
/**
* layout 页面布局
*/
const layout = () => {
const angle = 360 / numbers.value.length;
const radius = props.width / (2 * Math.sin(Math.PI / numbers.value.length)); // 旋转半径计算
numbers.value.forEach((_, index) => {
const item = num.value.children[index];
const rotateAxis = props.direction === 'horizontal' ? 'Y' : 'X';
item.style.transform = `rotate${rotateAxis}(${index * angle}deg) translateZ(${radius}px)`;
});
}
onMounted(() => {
layout()
rotateToTarget(props.targetNumber, props.direction);
});
</script>
<style lang="less" scoped>
.number-scroller {
display: flex;
align-items: center;
position: relative;
perspective: 1000px;
.viewport {
overflow: hidden;
position: relative;
}
.number-scroller-box {
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform-style: preserve-3d;
box-sizing: border-box;
transition: all 3s;
.number-item {
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
width: 100%;
height: 100%;
line-height: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
backface-visibility: hidden;
}
}
}
</style>