Vue3 数字翻转动画组件:打造酷炫的数字计数器
今天我们来分享一个有趣的Vue3组件开发教程------一个数字翻转动画效果。这种效果常见于老式计数器或倒计时器,给用户带来一种复古又炫酷的视觉体验。
效果预览
首先,让我们看看最终效果:一个由数字组成的计数器,每个数字会优雅地翻转到下一个数字,就像老式数字显示装置一样。这种效果特别适合用于倒计时、计数器、进度显示等场景。
项目准备
首先,我们需要创建一个Vue3项目。如果你还没有安装Vue CLI,可以通过以下命令安装:
bash
npm install -g @vue/cli
然后创建新项目:
bash
vue create digital-flip-counter
选择"Manually select features"并勾选"Vue 3"选项。安装完成后,我们就可以开始开发了。
组件开发
基础结构
在src/components
目录下创建DigitalFlipCounter.vue
文件,先添加基础结构:
vue
<template>
<div class="flip-counter-container">
<div class="flip-counter">
<div class="flip-counter-segment" v-for="(digit, index) in digits" :key="index">
<div class="flip-counter-digit" :class="{ 'flip': isFlipping[index] }">
<div class="flip-counter-front">{{ digit }}</div>
<div class="flip-counter-back">{{ nextDigits[index] }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
value: {
type: Number,
required: true
}
});
const digits = computed(() => {
return props.value.toString().padStart(2, '0').split('');
});
const nextDigits = computed(() => {
const nextValue = props.value + 1;
return nextValue.toString().padStart(2, '0').split('');
});
const isFlipping = ref(Array(digits.value.length).fill(false));
</script>
<style scoped>
.flip-counter-container {
perspective: 1000px;
margin: 20px;
}
.flip-counter {
display: flex;
justify-content: center;
gap: 5px;
}
.flip-counter-segment {
position: relative;
width: 40px;
height: 60px;
background: #f0f0f0;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.flip-counter-digit {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.5s;
}
.flip-counter-digit.flip {
transform: rotateX(-180deg);
}
.flip-counter-front, .flip-counter-back {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: bold;
backface-visibility: hidden;
}
.flip-counter-front {
background: #f0f0f0;
color: #333;
}
.flip-counter-back {
background: #e0e0e0;
color: #333;
transform: rotateX(180deg);
}
</style>
添加翻转逻辑
现在我们需要添加翻转逻辑,让数字在变化时产生翻转效果:
vue
<script setup>
import { ref, computed, watch } from 'vue';
const props = defineProps({
value: {
type: Number,
required: true
}
});
const emit = defineEmits(['update:value']);
const digits = computed(() => {
return props.value.toString().padStart(2, '0').split('');
});
const nextDigits = computed(() => {
const nextValue = props.value + 1;
return nextValue.toString().padStart(2, '0').split('');
});
const isFlipping = ref(Array(digits.value.length).fill(false));
watch(() => props.value, (newValue, oldValue) => {
if (newValue !== oldValue) {
isFlipping.value = Array(digits.value.length).fill(true);


// 翻转完成后更新值
setTimeout(() => {
isFlipping.value = Array(digits.value.length).fill(false);
emit('update:value', newValue);
}, 500); // 与CSS过渡时间匹配
}
}, { immediate: true });
</style>
完整组件代码
让我们完善一下组件,添加更多功能:
vue
<template>
<div class="flip-counter-container">
<div class="flip-counter">
<div class="flip-counter-segment" v-for="(digit, index) in digits" :key="index">
<div class="flip-counter-digit" :class="{ 'flip': isFlipping[index] }">
<div class="flip-counter-front">{{ digit }}</div>
<div class="flip-counter-back">{{ nextDigits[index] }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
const props = defineProps({
modelValue: {
type: Number,
required: true
},
increment: {
type: Function,
default: () => {}
}
});
const emit = defineEmits(['update:modelValue', 'increment']);
const digits = computed(() => {
return props.modelValue.toString().padStart(2, '0').split('');
});
const nextDigits = computed(() => {
const nextValue = props.modelValue + 1;
return nextValue.toString().padStart(2, '0').split('');
});
const isFlipping = ref(Array(digits.value.length).fill(false));
watch(() => props.modelValue, (newValue, oldValue) => {
if (newValue !== oldValue) {
isFlipping.value = Array(digits.value.length).fill(true);
// 翻转完成后更新值
setTimeout(() => {
isFlipping.value = Array(digits.value.length).fill(false);
emit('update:modelValue', newValue);
emit('increment', newValue);
}, 500); // 与CSS过渡时间匹配
}
}, { immediate: true });
const increment = () => {
emit('update:modelValue', props.modelValue + 1);
};
</script>
<style scoped>
.flip-counter-container {
perspective: 1000px;
margin: 20px;
}
.flip-counter {
display: flex;
justify-content: center;
gap: 5px;
}
.flip-counter-segment {
position: relative;
width: 40px;
height: 60px;
background: #f0f0f0;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.flip-counter-digit {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.5s;
}
.flip-counter-digit.flip {
transform: rotateX(-180deg);
}
.flip-counter-front, .flip-counter-back {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
font-weight: bold;
backface-visibility: hidden;
}
.flip-counter-front {
background: #f0f0f0;
color: #333;
}
.flip-counter-back {
background: #e0e0e0;
color: #333;
transform: rotateX(180deg);
}
/* 添加一些额外的样式 */
.flip-counter-segment::before,
.flip-counter-segment::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 1px;
background: #ddd;
}
.flip-counter-segment::before {
top: 50%;
}
.flip-counter-segment::after {
bottom: 50%;
}
</style>
使用组件
现在让我们在App.vue
中使用这个组件:
vue
<template>
<div id="app">
<h1>数字翻转计数器</h1>
<div class="counter-container">
<DigitalFlipCounter v-model="counter" @increment="handleIncrement" />
<div class="controls">
<button @click="decrement">-</button>
<button @click="increment">+</button>
<button @click="reset">重置</button>
</div>
</div>
<div class="info">
<p>当前值: {{ counter }}</p>
<p>已增加次数: {{ incrementCount }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import DigitalFlipCounter from './components/DigitalFlipCounter.vue';
const counter = ref(0);
const incrementCount = ref(0);
const increment = () => {
counter.value++;
incrementCount.value++;
};
const decrement = () => {
if (counter.value > 0) {
counter.value--;
}
};
const reset = () => {
counter.value = 0;
};
const handleIncrement = (value) => {
console.log('计数器增加到:', value);
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.counter-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.controls {
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background: #3aa876;
}
.info {
margin-top: 20px;
font-size: 18px;
}
</style>
高级定制
让我们添加一些高级功能,比如自定义颜色、大小和动画速度:
vue
<template>
<div class="flip-counter-container" :style="{ perspective: `${perspective}px` }">
<div class="flip-counter" :style="{ gap: `${gap}px` }">
<div
v-for="(digit, index) in digits"
:key="index"
class="flip-counter-segment"
:style="segmentStyle"
>
<div
class="flip-counter-digit"
:class="{ 'flip': isFlipping[index] }"
:style="digitStyle"
>
<div class="flip-counter-front" :style="frontStyle">{{ digit }}</div>
<div class="flip-counter-back" :style="backStyle">{{ nextDigits[index] }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
const props = defineProps({
modelValue: {
type: Number,
required: true
},
width: {
type: Number,
default: 40
},
height: {
type: Number,
default: 60
},
gap: {
type: Number,
default: 5
},
perspective: {
type: Number,
default: 1000
},
frontColor: {
type: String,
default: '#f0f0f0'
},
backColor: {
type: String,
default: '#e0e0e0'
},
textColor: {
type: String,
default: '#333'
},
fontSize: {
type: Number,
default: 36
},
animationDuration: {
type: Number,
default: 500
},
increment: {
type: Function,
default: () => {}
}
});
const emit = defineEmits(['update:modelValue', 'increment']);
const digits = computed(() => {
return props.modelValue.toString().padStart(2, '0').split('');
});
const nextDigits = computed(() => {
const nextValue = props.modelValue + 1;
return nextValue.toString().padStart(2, '0').split('');
});
const isFlipping = ref(Array(digits.value.length).fill(false));
const segmentStyle = computed(() => ({
width: `${props.width}px`,
height: `${props.height}px`,
backgroundColor: props.frontColor,
boxShadow: `0 4px 8px rgba(0, 0, 0, 0.1)`,
borderRadius: '5px',
overflow: 'hidden'
}));
const digitStyle = computed(() => ({
width: '100%',
height: '100%',
transformStyle: 'preserve-3d',
transition: `transform ${props.animationDuration}ms`
}));
const frontStyle = computed(() => ({
backgroundColor: props.frontColor,
color: props.textColor,
fontSize: `${props.fontSize}px`
}));
const backStyle = computed(() => ({
backgroundColor: props.backColor,
color: props.textColor,
fontSize: `${props.fontSize}px`,
transform: 'rotateX(180deg)'
}));
watch(() => props.modelValue, (newValue, oldValue) => {
if (newValue !== oldValue) {
isFlipping.value = Array(digits.value.length).fill(true);
// 翻转完成后更新值
setTimeout(() => {
isFlipping.value = Array(digits.value.length).fill(false);
emit('update:modelValue', newValue);
emit('increment', newValue);
}, props.animationDuration); // 与CSS过渡时间匹配
}
}, { immediate: true });
const increment = () => {
emit('update:modelValue', props.modelValue + 1);
};
</script>
<style scoped>
.flip-counter-container {
margin: 20px;
}
.flip-counter {
display: flex;
justify-content: center;
}
.flip-counter-digit.flip {
transform: rotateX(-180deg);
}
.flip-counter-front, .flip-counter-back {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
backface-visibility: hidden;
}
.flip-counter-segment::before,
.flip-counter-segment::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 1px;
background: #ddd;
}
.flip-counter-segment::before {
top: 50%;
}
.flip-counter-segment::after {
bottom: 50%;
}
</style>
完整使用示例
现在让我们看看如何使用这个高级版本的组件:
vue
<template>
<div id="app">
<h1>高级数字翻转计数器</h1>
<div class="counter-container">
<DigitalFlipCounter
v-model="counter"
@increment="handleIncrement"
:width="50"
:height="70"
:gap="10"
:frontColor="'#4a6baf'"
:backColor="'#3a5a9f'"
:textColor="'#ffffff'"
:fontSize="42"
:animationDuration="600"
/>
<div class="controls">
<button @click="decrement">-</button>
<button @click="increment">+</button>
<button @click="reset">重置</button>
</div>
</div>
<div class="info">
<p>当前值: {{ counter }}</p>
<p>已增加次数: {{ incrementCount }}</p>
</div>
<div class="color-picker">
<h3>自定义颜色</h3>
<div class="color-options">
<button
v-for="color in colorOptions"
:key="color.name"
@click="changeColor(color)"
:style="{ backgroundColor: color.value }"
>
{{ color.name }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import DigitalFlipCounter from './components/DigitalFlipCounter.vue';
const counter = ref(0);
const incrementCount = ref(0);
const colorOptions = [
{ name: '蓝色', value: '#4a6baf' },
{ name: '红色', value: '#d9534f' },
{ name: '绿色', value: '#5cb85c' },
{ name: '紫色', value: '#9b59b6' },
{ name: '橙色', value: '#f0ad4e' }
];
const currentColor = ref(colorOptions[0]);
const increment = () => {
counter.value++;
incrementCount.value++;
};
const decrement = () => {
if (counter.value > 0) {
counter.value--;
}
};
const reset = () => {
counter.value = 0;
};
const handleIncrement = (value) => {
console.log('计数器增加到:', value);
};
const changeColor = (color) => {
currentColor.value = color;
updateCounterStyle();
};
const updateCounterStyle = () => {
// 这里可以添加动态更新组件样式的逻辑
// 由于Vue的响应式系统,我们只需要更新props即可
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.counter-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.controls {
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
button:hover {
background: #3aa876;
}
.info {
margin-top: 20px;
font-size: 18px;
}
.color-picker {
margin-top: 40px;
}
.color-options {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 10px;
}
.color-options button {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid #ddd;
cursor: pointer;
transition: transform 0.2s, border 0.2s;
}
.color-options button:hover {
transform: scale(1.1);
border: 2px solid #333;
}
.color-options button.active {
border: 2px solid #333;
transform: scale(1.1);
}
</style>
总结
通过这个教程,我们创建了一个功能丰富的Vue3数字翻转计数器组件。这个组件不仅实现了基本的数字翻转效果,还提供了高度的可定制性,允许开发者调整颜色、大小、动画速度等参数。 在实际项目中,这种组件可以用于:
- 倒计时器
- 访问量统计
- 产品数量显示
- 游戏中的分数显示
- 任何需要数字变化的场景 希望这个教程对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言。如果你喜欢这个组件,别忘了点赞和分享哦!