时钟翻牌器

 

clock.js

function Flipper(config) {
  // 默认配置
  this.config = {
    // 时钟模块的节点
    node: null,
    // 初始前牌文字
    frontText: 'number0',
    // 初始后牌文字
    backText: 'number1',
    // 翻转动画时间(毫秒,与翻转动画CSS 设置的animation-duration时间要一致)
    duration: 600,
  }
  // 节点的原本class,与html对应,方便后面添加/删除新的class
  this.nodeClass = {
    flip: 'flip',
    front: 'digital front',
    back: 'digital back'
  }
  // 覆盖默认配置
  Object.assign(this.config, config)
  // 定位前后两个牌的DOM节点
  this.frontNode = this.config.node.querySelector('.front')
  this.backNode = this.config.node.querySelector('.back')
  // 是否处于翻牌动画过程中(防止动画未完成就进入下一次翻牌)
  this.isFlipping = false
  // 初始化
  this._init()
}
Flipper.prototype = {
  constructor: Flipper,
  // 初始化
  _init: function () {
    // 设置初始牌面字符
    this._setFront(this.config.frontText)
    this._setBack(this.config.backText)
  },
  // 设置前牌文字
  _setFront: function (className) {  
    this.frontNode.setAttribute('class', this.nodeClass.front + ' ' + className)
  },
  // 设置后牌文字
  _setBack: function (className) {
    this.backNode.setAttribute('class', this.nodeClass.back + ' ' + className)
  },
  _flip: function (type, front, back) { 
    // 如果处于翻转中,则不执行
    if (this.isFlipping) {
      return false
    }
    // 设置翻转状态为true
    this.isFlipping = true
    // 设置前牌文字
    this._setFront(front)
    // 设置后牌文字
    this._setBack(back)
    // 根据传递过来的type设置翻转方向
    let flipClass = this.nodeClass.flip;
    if (type === 'down') {
      flipClass += ' down'
    } else {
      flipClass += ' up'
    }
    // 添加翻转方向和执行动画的class,执行翻转动画
    this.config.node.setAttribute('class', flipClass + ' go')
    // 根据设置的动画时间,在动画结束后,还原class并更新前牌文字
    setTimeout(() => {
      // 还原class
      this.config.node.setAttribute('class', flipClass)
      // 设置翻转状态为false
      this.isFlipping = false
      // 将前牌文字设置为当前新的数字,后牌因为被前牌挡住了,就不用设置了。
      this._setFront(back)
    }, this.config.duration)
  },
  // 下翻牌
  flipDown: function (front, back) {
    this._flip('down', front, back)
  },
  // 上翻牌
  flipUp: function (front, back) {
    this._flip('up', front, back)
  }
}

export default Flipper

  

clock.vue

<template>
  <div class="clock" id="clock">
    <div class="flip down">
      <div class="digital front number0"></div>
      <div class="digital back number1"></div>
    </div>
    <div class="flip down">
      <div class="digital front number0"></div>
      <div class="digital back number1"></div>
    </div>
    <em>:</em>
    <div class="flip down">
      <div class="digital front number0"></div>
      <div class="digital back number1"></div>
    </div>
    <div class="flip down">
      <div class="digital front number0"></div>
      <div class="digital back number1"></div>
    </div>
    <em>:</em>
    <div class="flip down">
      <div class="digital front number0"></div>
      <div class="digital back number1"></div>
    </div>
    <div class="flip down">
      <div class="digital front number0"></div>
      <div class="digital back number1"></div>
    </div>
  </div>
</template>
<script>
import Flipper from './clock.js'
export default {
  name: 'clock',
  components: {},
  data() {
    return {
      flipObjs: [],
    }
  },
  methods: {
    init() {
      // 定位时钟模块
      let clock = document.getElementById('clock')
      // 定位6个翻板
      let flips = clock.querySelectorAll('.flip')
      // 获取当前时间
      let now = new Date()
      // 格式化当前时间,例如现在是20:30:10,则输出"203010"字符串
      let nowTimeStr = this.formatDate(now, 'hhiiss')
      // 格式化下一秒的时间
      let nextTimeStr = this.formatDate(
        new Date(now.getTime() + 1000),
        'hhiiss'
      )
      // 定义牌板数组,用来存储6个Flipper翻板对象

      for (let i = 0; i < flips.length; i++) {
        // 创建6个Flipper实例,初始化并存入flipObjs
        this.flipObjs.push(
          new Flipper({
            // 每个Flipper实例按数组顺序与翻板DOM的顺序一一对应
            node: flips[i],
            // 按数组顺序取时间字符串对应位置的数字
            frontText: 'number' + nowTimeStr[i],
            backText: 'number' + nextTimeStr[i],
          })
        )
      } 
    },
    formatDate(date, dateFormat) {
      /* 单独格式化年份,根据y的字符数量输出年份
     * 例如:yyyy => 2019
            yy => 19
            y => 9
     */
      if (/(y+)/.test(dateFormat)) {
        dateFormat = dateFormat.replace(
          RegExp.$1,
          (date.getFullYear() + '').substr(4 - RegExp.$1.length)
        )
      }
      // 格式化月、日、时、分、秒
      let o = {
        'm+': date.getMonth() + 1,
        'd+': date.getDate(),
        'h+': date.getHours(),
        'i+': date.getMinutes(),
        's+': date.getSeconds(),
      }
      for (let k in o) {
        if (new RegExp(`(${k})`).test(dateFormat)) {
          // 取出对应的值
          let str = o[k] + ''
          /* 根据设置的格式,输出对应的字符
           * 例如: 早上8时,hh => 08,h => 8
           * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
           * 例如: 下午15时,hh => 15, h => 15
           */
          dateFormat = dateFormat.replace(
            RegExp.$1,
            RegExp.$1.length === 1 ? str : this.padLeftZero(str)
          )
        }
      }
      return dateFormat
    },
    //日期时间补零
    padLeftZero(str) {
      return ('00' + str).substr(str.length)
    },
    start() {
      setInterval(() => {
        // 获取当前时间
        let now = new Date()
        // 格式化当前时间
        let nowTimeStr = this.formatDate(
          new Date(now.getTime() - 1000),
          'hhiiss'
        )
        // 格式化下一秒时间
        let nextTimeStr = this.formatDate(now, 'hhiiss')  
        // 将当前时间和下一秒时间逐位对比
        for (let i = 0; i < this.flipObjs.length; i++) {
          // 如果前后数字没有变化,则直接跳过,不翻牌
          if (nowTimeStr[i] === nextTimeStr[i]) {
            continue
          }
          // 传递前后牌的数字,进行向下翻牌动画
          this.flipObjs[i].flipDown(
            'number' + nowTimeStr[i],
            'number' + nextTimeStr[i]
          ) 
        }
      }, 1000)
    },
  },
  mounted() {
    this.init()
    this.start()
  },
}
</script>

<style scoped>
.clock {  
  text-align: center;
  margin-top: 200px;
   
}

.clock em {
  display: inline-block;
  line-height: 51px;
  font-size: 33px;
  font-style: normal;
  vertical-align: top;
}

.flip.up.go .front:after {
  transform-origin: 50% 0;
  animation: frontFlipUp 0.6s ease-in-out both;
  box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
  backface-visibility: hidden;
}

.flip.up.go .back:before {
  animation: backFlipUp 0.6s ease-in-out both;
}
@keyframes frontFlipUp {
  0% {
    transform: perspective(160px) rotateX(0deg);
  }

  100% {
    transform: perspective(160px) rotateX(180deg);
  }
}

@keyframes backFlipUp {
  0% {
    transform: perspective(160px) rotateX(-180deg);
  }

  100% {
    transform: perspective(160px) rotateX(0deg);
  }
}
.flip.down.go .front:before {
  transform-origin: 50% 100%;
  animation: frontFlipDown 0.6s ease-in-out both;
  box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
  backface-visibility: hidden;
}
.flip.down.go .front:before {
  transform-origin: 50% 100%;
  animation: frontFlipDown 0.6s ease-in-out both;
  box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
}

.flip.down.go .back:after {
  animation: backFlipDown 0.6s ease-in-out both;
}

@keyframes frontFlipDown {
  0% {
    transform: perspective(160px) rotateX(0deg);
  }

  100% {
    transform: perspective(160px) rotateX(-180deg);
  }
}

@keyframes backFlipDown {
  0% {
    transform: perspective(160px) rotateX(180deg);
  }

  100% {
    transform: perspective(160px) rotateX(0deg);
  }
}
.flip {
    display: inline-block;
    position: relative;
    width: 30px;
    height: 50px;
    line-height: 50px;
    border: solid 1px #000;
    border-radius: 5px;
    background: #fff;
    font-size: 33px;
    color: #fff;
    box-shadow: 0 0 6px rgba(0, 0, 0, .5);
    text-align: center;  
    overflow: hidden; 
}
 
 .flip .digital:before,
.flip .digital:after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    background: #000;
    overflow: hidden;
    box-sizing: border-box; 
}

.flip .digital:before {
    top: 0;
    bottom: 50%;
    border-radius: 2px 2px 0 0;
    border-bottom: solid 1px #666;
}

.flip .digital:after {
    top: 50%;
    bottom: 0;
    border-radius: 0 0 2px 2px;
    line-height: 0; 
} 


.flip .number0:before,
.flip .number0:after {
    content: "0";
}

.flip .number1:before,
.flip .number1:after {
    content: "1";
}

.flip .number2:before,
.flip .number2:after {
    content: "2";
} 
.flip .number3:before,
.flip .number3:after {
    content: "3";
} 

.flip .number4:before,
.flip .number4:after {
    content: "4";
} 
.flip .number5:before,
.flip .number5:after {
    content: "5";
} 
.flip .number6:before,
.flip .number6:after {
    content: "6";
} 
.flip .number7:before,
.flip .number7:after {
    content: "7";
} 
.flip .number8:before,
.flip .number8:after {
    content: "8";
} 
.flip .number9:before,
.flip .number9:after {
    content: "9";
} 
.flip.down .front:before {
    z-index: 3;
}

.flip.down .back:after {
    z-index: 2;
    transform-origin: 50% 0%;
    transform: perspective(160px) rotateX(180deg);
}

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

 
</style>

  

 

posted @ 2022-12-01 10:31  福超  阅读(125)  评论(0编辑  收藏  举报