翻转时钟动画的实现

Zaylen原创大约 3 分钟前端css技术分享

翻转时钟动画实现

1. 背景

今天遇到一个特殊的需求, 就是需要实现类似于翻转式的时钟动画,他的效果大概是这样子的。

首先老惯例,先到网上找找有没有插件 😁😁😁😁,找了一圈后发现没有能满足定制化需求的插件。只能自己开干了。

2. 分析

参考网上的案例,我们可以将该动画分为四个部分:

1.首先我们需要两对盒子,分别显示翻转前的数字的上半部分,下半部分, 以及显示翻转后数字的上下部分.

2.先将翻转后的后半部分先向上旋转0.5turn, 动画开始:翻转前的上半部分开始向下旋转0.5turn

3.后半部分的数字变化成下一秒时间,然后他原先向上旋转的部分开始恢复原来的样子

4.时分秒进阶的话就每次检查时间时分是否出现变动,出现变动的话就执行动画。

大概就是这个样子!!!😃😃😃😃😃

3. 实现

html 部分

<div class="clock">
  <div class="flip">
    <div class="digital front">
      <div class="before">
        <div class="content">
          <span class="date">0</span>
          <span class="unit">min</span>
        </div>
      </div>
      <div class="after">
        <div class="content">
          <span class="date">0</span>
          <span class="unit">min</span>
        </div>
      </div>
    </div>
    <div class="digital back">
      <div class="before">
        <div class="content">
          <span class="date">1</span>
          <span class="unit">min</span>
        </div>
      </div>
      <div class="after">
        <div class="content">
          <span class="date">1</span>
          <span class="unit">min</span>
        </div>
      </div>
    </div>
  </div>
  <em class="divider">:</em>
  <div class="flip">
    <div class="digital front">
      <div class="before">
        <div class="content">
          <span class="date">0</span>
          <span class="unit">sec</span>
        </div>
      </div>
      <div class="after">
        <div class="content">
          <span class="date">0</span>
          <span class="unit">sec</span>
        </div>
      </div>
    </div>
    <div class="digital back">
      <div class="before">
        <div class="content">
          <span class="date">1</span>
          <span class="unit">sec</span>
        </div>
      </div>
      <div class="after">
        <div class="content">
          <span class="date">1</span>
          <span class="unit">sec</span>
        </div>
      </div>
    </div>
  </div>
</div>
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
body {
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background: linear-gradient(144deg, #f13073 0%, #e42ca5 100%);
}
/* 时钟盒子 */
.clock {
  display: flex;
  align-items: center;

/* 分割点 */
.divider {
  font-size: 100px;
  font-style: normal;
  color: white;

/* 时钟卡片 */
.flip {
  position: relative;
  width: 123px;
  height: 169px;
  background-color: white;
  border-radius: 20px;
}
/* 内容 */
.content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 123px;
  height: 169px;
  border-radius: 20px;
}
.content::after,
.content::before {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  content: '';
  display: block;
  width: 5px;
  height: 28px;
  background-color: rgba(162, 162, 162, 1);
}
.content::after {
  left: 0;
}
.content::before {
  right: 0;
}
.content .date {
  color: #f13073;
  font-size: 81px;
  font-weight: bold;
}
.content .unit {
  font-size: 31px;
  color: #a9a9a9;
}
.after .content {
  transform: translateY(-50%);

 /*上下部分盒子*/
.digital .before,
.digital .after {
  position: absolute;
  left: 0;
  right: 0;
  overflow: hidden;
  border-radius: 20px;
  background-color: #fff;
  perspective: 160px;

.digital .before {
  top: 0;
  bottom: 50%;
  border-radius: 20px 20px 0px 0px;
  border-bottom: 2px dashed #a9a9a9;

.digital .after {
  top: 50%;
  bottom: 0;
  border-radius: 0px 0px 20px 20px;

.clock .flip .back .before,
.clock .flip .front .after {
  z-index: 1;

.clock .flip .back .after {
  z-index: 2;
}
.clock .flip .front .before {
  z-index: 3;
}
.clock .flip .back .after {
  transform-origin: center top;
  transform: rotateX(0.5turn);
}
.clock .flip.running .front .before {
  transform-origin: center bottom;
  animation: frontFlipDown 0.6s ease-in-out;
  backface-visibility: hidden;
}
.clock .flip.running .back .after {
  animation: backFlipDown 0.6s ease-in-out;

@keyframes frontFlipDown {
  to {
    transform: rotateX(0.5turn);
  }

@keyframes backFlipDown {
  to {
    transform: rotateX(0);
  }
}

js 部分

class Flipper {
  constructor(node, currentTime, nextTime) {
    this.isFlipping = false
    this.duration = 600
    this.flipNode = node
    this.frontNode = node.querySelector('.front')
    this.backNode = node.querySelector('.back')

    this.setFrontTime(currentTime)
    this.setBackTime(nextTime)
  }

  setFrontTime = function (time) {
    let textNode = this.frontNode.querySelectorAll('.date')
    Array.from(textNode).forEach((item) => {
      item.innerText = time
    })
  }

  setBackTime(time) {
    let textNode = this.backNode.querySelectorAll('.date')
    Array.from(textNode).forEach((item) => {
      item.innerText = time
    })
  }

  flipDown(currentTime, nextTime) {
    if (this.isFlipping) {
      return
    }
    this.isFlipping = true

    // 设置变化前时间
    this.setFrontTime(currentTime)
    // 设置变化后时间
    this.setBackTime(nextTime)
    this.flipNode.classList.add('running')
    let timer = setTimeout(() => {
      this.flipNode.classList.remove('running')
      this.isFlipping = false

      // 设置变化前卡片为下一个时间
      this.setFrontTime(nextTime)
      clearTimeout(timer)
      timer = null
    }, this.duration)
  }
}

// 格式化获取时间
function getTimeFromDate(date) {
  return date.toTimeString().slice(3, 8).split(':')
}

// 获取时间显示框
let flips = document.querySelectorAll('.flip')

// 当前时间
let now = new Date()
let nowTimeStr = getTimeFromDate(new Date(now.getTime() - 1000))
let nextTimeStr = getTimeFromDate(now)

let flippers = Array.from(flips).map(function (flip, i) {
  return new Flipper(flip, nowTimeStr[i], nextTimeStr[i])
})

setInterval(() => {
  let now = new Date()
  let nowTimeStr = getTimeFromDate(new Date(now.getTime() - 1000))
  let nextTimeStr = getTimeFromDate(now)
  for (let i = 0; i < flippers.length; i++) {
    if (nowTimeStr[i] === nextTimeStr[i]) {
      continue
    }
    flippers[i].flipDown(nowTimeStr[i], nextTimeStr[i])
  }
}, 1000)

4. 效果展示