手摸手写一个时间翻页计时器animation动画

507 阅读5分钟
项目需要写了一个翻页的计时器,项目基于vue,里面内容涉及组件知识,样式如下图:


本组件分成两个组件,一个是单独的一个反转的数字,另一个是把这个翻页组成一个计时器。

先分析单独翻转的数据动画:(先抛代码)

countTurn.vue

<template>
  <div id="stage-1" class="stage stage-state-default">
  <div class="stage-content">
    <ul class="stage-1-icon">
      <li :class="'stage-1-icon-1'">
        <div class="stage-1-icon-top" :class="change?'turn-top':''" :style="{backgroundImage:'url('+pic1+')'}">
        </div>
        <div class="stage-1-icon-bottom" :style="{backgroundImage:'url('+pic1+')'}">
        </div>
      </li>
      <li :class="'stage-1-icon-2'">
        <div class="stage-1-icon-top" :style="{backgroundImage:'url('+pic2+')'}">
        </div>
        <div class="stage-1-icon-bottom" :class="change?'turn-bottom':''" :style="{backgroundImage:'url('+pic2+')'}">
        </div>
      </li>
    </ul>
  </div>
</div>
</template>
<script>
import pic1 from '../../assets/count/1.png'//图片素材
import pic2 from '../../assets/count/2.png'
import pic3 from '../../assets/count/3.png'
import pic4 from '../../assets/count/4.png'
import pic5 from '../../assets/count/5.png'
import pic6 from '../../assets/count/6.png'
import pic7 from '../../assets/count/7.png'
import pic8 from '../../assets/count/8.png'
import pic9 from '../../assets/count/9.png'
import pic0 from '../../assets/count/0.png'
const picList = [pic0, pic1, pic2, pic3, pic4, pic5, pic6, pic7, pic8, pic9]
export default {
  props: ['num'],
  data() {
    return {
      change: false,
      pic1: pic1,
      pic2: pic2,
    }
  },
  watch: {
    num(newVal, oldVal) { // 当数据改变时触发动画
      this.pic1 = picList[oldVal]
      this.pic2 = picList[newVal]
      this.change = false
      setTimeout(() => {
        this.change = true
      }, 100)
    },
  },
  mounted() {
    if (this.num) {// 数字初始化为0
      ;(this.pic1 = picList[this.num]), (this.pic2 = pic1[this.num + 1 == 10 ? 0 : this.num])
    } else {
      ;(this.pic1 = pic0), (this.pic2 = pic1)
    }
  },
}
</script>
<style lang="less" scoped>
ol,
ul {
  list-style: none;
}
 
#stage-1 .stage-content {
  margin: auto 0 auto;
}
 
.stage-1-icon {
  width: 20px; // 图片宽度
  height: 37px; // 图片高度
  margin: 0 auto 0;
  position: relative;
  -webkit-perspective: 900px;
}
 
.stage-1-icon-top,
.stage-1-icon-bottom {
  width: 20px; // 图片宽度
  position: absolute;
  left: 0;
  background-color: #0342aa;// 背景色
  background-repeat: no-repeat;
  -webkit-transform-style: preserve-3d;
}
 
.stage-1-icon-top {
  height: 18px; //上半截的高度
  padding: 0;
  top: 0;
  background-position: center 0;
  -webkit-transform-origin: center bottom;
  -moz-transform-origin: center bottom;
  -ms-transform-origin: center bottom;
  -o-transform-origin: center bottom;
  transform-origin: center bottom;// 做-webkit-、-moz-、-ms-、-o-兼容
}
 
.stage-1-icon-bottom {
  height: 18px; // 下半截的高度
  top: 18px; // 下半截的位移高度
  // padding: 0 10px 10px;
  background-position: center -18px; // 下半截的背景图位移高度
  // ……,做-webkit-、-moz-、-ms-、-o-兼容
  transform-origin: center top;
}
 
.stage-1-icon-1 .stage-1-icon-top,
.stage-1-icon-1 .stage-1-icon-bottom {
  z-index: 6; // 初始化更小数字的图片在上面
  background-size: 100% 200%;
}
 
.stage-1-icon-2 .stage-1-icon-top,
.stage-1-icon-2 .stage-1-icon-bottom {
  z-index: 5; // 初始化更大数字的图片在下面
  background-size: 100% 200%;
}
@-webkit-keyframes flipover-top {
  0% {
    z-index: 8;// 这是数字上半块的z-index
    background-color: #0342aa;
    -webkit-transform: rotateX(0);
  }
  50% {
    z-index: 8;
    background-color: rgba(0, 0, 0, 0.1);
    -webkit-transform: rotateX(-90deg);
  }
  50.1% {
    z-index: 7;// 注意z-index的改变
    opacity: 1;
  }
  100% {
    z-index: 7;
    background-color: #0342aa;
    opacity: 0;// 结束时直接透明啦
    -webkit-transform: rotateX(-180deg);
  }
}
@-moz-keyframes flipover-top {
  ……
}
@-ms-keyframes flipover-top {
  ……
}
@-o-keyframes flipover-top {
  ……
}
@keyframes flipover-top {
  ……
}
@-webkit-keyframes flipover-bottom {
  0% {
    z-index: 7;
    background-color: #0342aa;
    -webkit-transform: rotateX(180deg);
  }
  50% {
    z-index: 7;
    background-color: rgba(0, 0, 0, 0.1);
    -webkit-transform: rotateX(90deg);
  }
  50.1% {
    z-index: 8;
  }
  100% {
    z-index: 6;// 结束时把它调整为数字较少图的那一层,看.stage-1-icon-1 .stage-1-icon-top的z-index
    background-color: #0342aa;
    -webkit-transform: rotateX(0);
  }
}
@-moz-keyframes flipover-bottom {
  ……
}
@-ms-keyframes flipover-bottom {
  ……
}
@-o-keyframes flipover-bottom {
  ……
}
@keyframes flipover-bottom {
  ……
}
#stage-1.stage-state-default {
  z-index: 1;
}
.turn-top { // 上半块动画
  // ……,做-webkit-、-moz-、-ms-、-o-兼容
  animation: flipover-top 0.15s linear 0s 1 normal forwards;
}
.turn-bottom { // 下半块动画
  // ……,做-webkit-、-moz-、-ms-、-o-兼容
  animation: flipover-bottom 0.15s linear 0s 1 normal forwards;
}
</style>

(兼容性的代码太长注释掉了 自行加上哈)

上面注意的知识点有以下几点:

1.图片根据background-image引入

一张图片,通过background-image引入,而且分成上下两块。

<div class="stage-1-icon-top" :class="change?'turn-top':''" :style="{backgroundImage:'url('+pic1+')'}"></div>
<div class="stage-1-icon-bottom" :style="{backgroundImage:'url('+pic1+')'}"></div>

2.用change属性重新触发动画 html:

<div class="stage-1-icon-top" :class="change?'turn-top':''" :style="{backgroundImage:'url('+pic1+')'}"></div>
 …… 

<div class="stage-1-icon-bottom" :class="change?'turn-bottom':''" :style="{backgroundImage:'url('+pic2+')'}">

 js:

num(newVal, oldVal) {
      this.pic1 = picList[oldVal]
      this.pic2 = picList[newVal]
      this.change = false // 改成false
      setTimeout(() => {  
        this.change = true // 过零点一毫秒再改为true
      }, 100)
    },

css:

.turn-top { // 上半块动画
  // ……,做-webkit-、-moz-、-ms-、-o-兼容
  animation: flipover-top 0.15s linear 0s 1 normal forwards;
}
.turn-bottom { // 下半块动画
  // ……,做-webkit-、-moz-、-ms-、-o-兼容
  animation: flipover-bottom 0.15s linear 0s 1 normal forwards;
}

3.注意每一块的z-index关系

每个写有z-index的地方都要留意,因为是关键鸭!


分析父级组件如何制作计时器(先抛代码)

timer.vue

<template>
  <div class="timer">
    <countTurn :num="hh"></countTurn>
    <countTurn :num="h"></countTurn>
    <img src="../../assets/count/_.png" class="img" alt=":">
    <countTurn :num="mm"></countTurn>
    <countTurn :num="m"></countTurn>
    <img src="../../assets/count/_.png" class="img" alt=":">
    <countTurn :num="ss"></countTurn>
    <countTurn :num="s"></countTurn>
  </div>
</template>
<script>
import countTurn from '../countTurn' //子组件引入
let timer = null
export default {
  components: {countTurn},
  data() {
    return {
      hh: 0,
      h: 0,
      mm: 0,
      m: 0,
      ss: 0,
      s: 0,
    }
  },
  mounted() {
    this.startTimer(new Date())
    setInterval(() => {
      this.startTimer(new Date())
    }, 1000 * 60 * 60) // 一小时跟进一下实际时间防止系统卡顿产生计时误差
  },
  methods: {
    startTimer(date) {
      if (timer) {
        clearInterval(timer)
      }
      let dt = new Date(date)
      // 初始化6个值
      this.hh = dt.getHours() >= 10 ? Math.floor(dt.getHours() / 10) : 0
      this.h = dt.getHours() % 10
      this.mm = dt.getMinutes() >= 10 ? Math.floor(dt.getMinutes() / 10) : 0
      this.m = dt.getMinutes() % 10
      this.ss = dt.getSeconds() >= 10 ? Math.floor(dt.getSeconds() / 10) : 0
      this.s = dt.getSeconds() % 10
 
      // 开始计时
      timer = setInterval(() => {
        this.s++
        if (this.s == 10) {
          this.s = 0
          this.ss++
          if (this.ss == 6) {
            this.ss = 0
            this.m++
            if (this.m == 10) {
              this.m = 0
              this.mm++
              if (this.mm == 6) {
                this.mm = 0
                this.h++
                if (this.hh * 10 + this.h == 24) {
                  this.hh = 0
                  this.h = 0
                } else if (this.h == 10) {
                  this.h = 0
                  this.hh++
                }
              }
            }
          }
        }
      }, 1000)
    },
  },
}
</script>
<style lang="less" scoped>
.timer {
  display: flex;
  align-items: center;
  justify-content: center;
}
.img{
  width: 10px;
}
</style>

这一块代码主要重点在于计时的函数startTimer() 

 1.使用setInterval()1秒循环一次 

2.一小时跟进一下实际时间防止系统卡顿产生计时误差再调用一次startTime(),所以在函数开始时先清一下之前的计时器clearInterval(timer),然后再重新获取一次时间。 

3.为什么这里不直接每一秒都获取一下时间呢?

每秒都拿一次会影响性能 大致分享到这里,图片使用自己的图片替代就可以啦~ 

ps: 第一次写掘金的文章,有什么不足请多多指点。