<template>
<div>
<VueNumberScroll
class="num"
:start="startNum"
:end="endNum"
:times="10"
:speed="100"
:format="num => padZeroLeft(num, numLength)"
></VueNumberScroll>
</div>
</template>
<script>
import VueNumberScroll from 'vue-number-scroll';
export default {
components: {
VueNumberScroll
},
props: {
number: {
type: Number,
default: 0,
},
numLength: {
type: Number,
default: 6,
},
defaultNumber: {
type: Number,
default: 0,
},
},
watch: {
number: {
handler(newValue, oldValue) {
this.startNum = oldValue || this.defaultNumber;
this.endNum = newValue;
},
immediate: true,
}
},
methods: {
padZeroLeft(num = 0, len = 0) {
return (`${num}`).padStart(len, '0');
},
},
data() {
return {
startNum: 0,
endNum: 0,
};
},
};
</script>
<style lang="scss" scoped>
.num {
font-size: 1rem;
display: flex;
justify-content: center;
}
</style>
<template>
<div>
<div class="digital-flip-box">
<div
v-for="(curDigital, index) in nowNumArr"
:key="index"
class="digital-single-box"
>
<div
v-for="curNumber in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
:key="curNumber"
:class="{
'pre': !increaseAni ? curNumber - 1 === +curDigital : false,
'active-pre': !increaseAni ? curNumber === +curDigital : false,
'active-next': increaseAni ? curNumber === +curDigital : false,
'next': increaseAni ? curNumber + 1 === +curDigital : false,
}"
class="digital-single-list"
>
<div class="digit-top">
<span class="digit-num">{{curNumber}}</span>
</div>
<div class="shadow-top"></div>
<div class="digit-bottom">
<span class="digit-num">{{curNumber}}</span>
</div>
<div class="shadow-bottom"></div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
number: {
type: Number,
default: 0,
},
numLength: {
type: Number,
default: 6,
},
defaultNumber: {
type: Number,
default: 0,
},
duration: {
type: Number,
default: 3000,
},
},
watch: {
number: {
handler(newValue, oldValue) {
this.startNum = oldValue;
this.endNum = newValue;
this.handleNumAni();
},
immediate: true,
}
},
data() {
return {
startNum: 0,
endNum: 0,
nowNumArr: [],
increaseAni: true,
decreaseTimer: null,
increaseTimer: null,
};
},
methods: {
handleNumAni() {
if (this.endNum === this.startNum) return;
const noEndNum = (!this.endNum && this.endNum !== 0) || this.endNum < 0;
if (noEndNum) {
this.nowNumArr = this.transformNumArr(this..defaultNumber, this.numLength);
return;
}
const noStartNum = (!this.startNum && this.startNum !== 0) || this.startNum < 0;
if (noStartNum) {
this.nowNumArr = this.transformNumArr(this..endNum, this.numLength);
return;
}
const doMethods = { increase: this.doIncreaseAni, decrease: this.doDecreaseAni };
this.nowNumArr = this.transformNumArr(this.startNum, this.numLength);
this.increaseAni = this.startNum < this.endNum;
doMethods[this.increaseAni ? 'increase' : 'decrease'];
},
doDecreaseAni(start, end) {
const difference = start - end;
const intervalTime = this.duration / difference;
console.log({'decrease', start, end, difference, intervalTime});
this.decreaseTimer = setInterval(() => {
if (start <= end) {
clearInterval(this.decreaseTimer);
this.decreaseTimer = null;
return;
}
start--;
this.nowNumArr = this.transformNumArr(start, this.numLength);
}, intervalTime);
},
doIncreaseAni(start, end) {
const difference = end - start;
const intervalTime = this.duration / difference;
console.log('increase', {start, end, difference, intervalTime});
this.increaseTimer = setInterval(() => {
if (start >= end) {
clearInterval(this.increaseTimer);
this.increaseTimer = null;
return;
}
start++;
this.nowNumArr = this.transformNumArr(start, this.numLength);
}, intervalTime);
},
transformNumArr(number, length) {
const newNumber = String(number).length <= length
? this.padZero(number, length, 'left')
: this.padZero(1, length + 1, 'left') - 1;
return String(newNumber).split('');
},
padZero(num = 0, len = 0, direction = 'left') {
return direction === 'left'
? (`${num}`).padStart(len, '0')
: (`${num}`).padEnd(len, '0');
},
},
};
</script>
<style lang="scss" scoped>
// 圆角
$borderRadius: 0.1rem;
// 总数字盒子宽度
$allNumberWidth: 7.5rem;
// 单个数字盒子宽度
$singleNumberWidth: 1rem;
// 字体大小
$fontSize: 1.2rem;
// 数字翻转盒子
.digital-flip-box {
font-size: $fontSize;
width: $allNumberWidth;
height: 1.5rem;
line-height: 1;
margin: 0 auto;
font-weight: bold;
color: #fedec2;
background: gold;
display: flex;
justify-content: center;
}
// 单个数字盒子
.digital-single-box {
position: relative;
width: $allNumberWidth;
height: 100%;
border: 1px solid black;
border-radius: $borderRadius;
margin: 0 0.1rem;
}
// 数字列 0~9
.digital-single-list {
position: absolute;
width: 100%;
height: 100%;
div {
position: absolute;
left: 0;
width: 100%;
height: 50%;
overflow: hidden;
}
// 顶部数字、数字阴影
.digit-top, .shadow-top {
background: #fedec2;
border-bottom: 1px solid #fedec2;
box-sizing: border-box;
top: 0;
z-index: 0;
border-radius: $borderRadius $borderRadius 0 0;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
border-radius: $borderRadius $borderRadius 0 0;
}
}
.shadow-top {
opacity: 0;
transition: opacity 0.3s ease-in;
}
// 底部数字、数字阴影
.digit-bottom, .shadow-bottom {
background: #fedec2;
left: 0;
bottom: 0;
z-index: 0;
border-radius: 0 0 $borderRadius $borderRadius;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
border-radius: 0 0 $borderRadius $borderRadius;
}
}
.shadow-bottom {
opacity: 0;
transition: opacity 0.3s ease-in;
}
// 实际数字
.digit-num {
line-height: 1;
display: block;
overflow: hidden;
display: flex;
justify-content: center;
}
.digit-bottom, .shadow-bottom {
.digit-num {
margin-top: -75%;
}
}
}
// 动效
.digital-single-list {
// 递减9~0
&.pre {
.digit-top, .shadow-top {
opacity: 1;
z-index: 2;
transform-origin: 50% 100%;
animation: flip-ani-1 0.3s ease-in both;
@keyframes flip-ani-1 {
0% {
transform: perspective(400px) rotateX(0deg);
}
100% {
transform: perspective(400px) rotateX(-90deg);
}
}
}
.digit-bottom, .shadow-bottom {
opacity: 1;
z-index: 1;
}
}
// 递减9~0
&.active-pre {
.digit-top {
z-index: 1;
}
.digit-bottom {
z-index: 2;
transform-origin: 50% 0%;
animation: flip-ani-2 0.3s ease-out both;
@keyframes flip-ani-2 {
0% {
transform: perspective(400px) rotateX(-90deg);
}
100% {
transform: perspective(400px) rotateX(0deg);
}
}
}
}
// 递增0~9
&.active-next {
.digit-top {
z-index: 2;
transform-origin: 50% 100%;
animation: flip-ani-3 0.3s ease-out both;
@keyframes flip-ani-3 {
0% {
transform: perspective(400px) rotateX(-90deg);
}
100% {
transform: perspective(400px) rotateX(0deg);
}
}
}
.digit-bottom {
z-index: 1;
}
}
// 递增0~9
&.next {
.digit-top, .shadow-top {
opacity: 1;
z-index: 1;
}
.digit-bottom, .shadow-bottom {
opacity: 1;
z-index: 2;
transform-origin: 50% 0%;
animation: flip-ani-4 0.3s ease-in both;
@keyframes flip-ani-4 {
0% {
transform: perspective(400px) rotateX(0deg);
}
100% {
transform: perspective(400px) rotateX(90deg);
}
}
}
}
}
</style>