时钟翻牌器
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>