翻牌器
先封装一个类:
export default class Flipper {
isFlipping = false;
flipNode: Element;
frontNode: HTMLElement | null;
backNode: HTMLElement | null;
duration = 600;
constructor(node: Element, currentTime: string, nextTime: string) {
this.flipNode = node;
this.frontNode = node.querySelector('.front');
this.backNode = node.querySelector('.back');
this.setFrontTime(currentTime);
this.setBackTime(nextTime);
}
setFrontTime(time: string) {
this.frontNode!.dataset.number = time;
}
setBackTime(time: string) {
this.backNode!.dataset.number = time;
}
flipDown(currentTime: string, nextTime: string) {
if (this.isFlipping) {
return false;
}
this.isFlipping = true;
this.setFrontTime(currentTime);
this.setBackTime(nextTime);
this.flipNode.classList.add('running');
setTimeout(() => {
this.flipNode.classList.remove('running');
this.isFlipping = false;
this.setFrontTime(nextTime);
}, this.duration);
}
}
源代码如下:
<template>
<div class="clock">
<div v-for="(item, index) in [...formatter].length" :key="index" class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
</div>
</template>
<script lang="ts">
import { onMounted, watch, toRefs, ref } from 'vue';
// 引用上面的类
import Flipper from '@/logic/flipper';
export default {
name: 'CardFlipper',
props: {
dayVolumeOfBusiness: {
type: String || Number,
default: '00000000',
},
},
setup(props: any) {
const { dayVolumeOfBusiness } = toRefs(props);
const formatter = ref('NNNNNNNN');
watch(dayVolumeOfBusiness, (newProps) => {
if (newProps) {
dayVolumeOfBusiness.value = newProps;
initFlipper();
}
});
const initFlipper = async () => {
let flips = document.querySelectorAll('.flip');
let result = [];
// 判断返回的字数是否有[...formatter.value].length个,否则就需要补充0
if (dayVolumeOfBusiness.value.toString().length < [...formatter.value].length) {
const data = dayVolumeOfBusiness.value.split('').reverse();
for (let i = 0; i < [...formatter.value].length; i += 1) {
if (!data[i]) data[i] = '0';
}
result = data.reverse().join('');
} else {
result = dayVolumeOfBusiness.value;
}
const nowTimeStr = result;
const nextTimeStr = result;
let flippers = Array.from(flips).map(
(flip, i) => new Flipper(flip, nowTimeStr[i], nextTimeStr[i])
);
for (let i = 0; i < flippers.length; i++) {
flippers[i].flipDown(nowTimeStr[i], nextTimeStr[i]);
}
};
onMounted(() => {
initFlipper();
});
return {
formatter,
};
},
};
</script>
<style lang="scss" scoped>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.clock {
display: flex;
.flip {
position: relative;
width: 36px;
height: 47px;
margin: 0 8px;
font-size: 46px;
line-height: 47px;
text-align: center;
// basic
.digital {
&::before,
&::after {
position: absolute;
content: attr(data-number);
left: 0;
right: 0;
color: rgb(255 197 91 / 100%);
background: url(@/assets/img/flipper.png) no-repeat;
overflow: hidden;
perspective: 160px;
font-family: DIN-Medium;
}
&::before {
top: 0;
bottom: 50%;
border-radius: 0 0 6px 6px;
}
&::after {
top: 50%;
bottom: 0;
line-height: 0;
border-radius: 6px 6px 0 0;
}
}
// stack
.back::before,
.front::after {
z-index: 1;
}
.front::before {
z-index: 3;
}
.back::before {
opacity: 0;
transition: all 0.6s;
}
// animation
.back::after {
transform-origin: center top;
transform: rotateX(0.5turn);
opacity: 0;
z-index: 2;
}
&.running {
.front::before {
transform-origin: center bottom;
animation: front-flip-down 0.6s ease-in-out;
box-shadow: 0 -2px 6px rgb(255 255 255 / 30%);
backface-visibility: hidden;
}
.back::before {
opacity: 1;
}
.back::after {
animation: back-flip-down 0.6s ease-in-out;
}
}
}
}
@keyframes front-flip-down {
to {
transform: rotateX(0.5turn);
}
}
@keyframes back-flip-down {
to {
transform: rotateX(0);
opacity: 1;
}
}
</style>
效果如下: