486 lines
13 KiB
HTML
486 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>翻页时钟</title>
|
||
<style type="text/css">
|
||
.single-demo {
|
||
margin: 50px auto;
|
||
padding: 30px;
|
||
width: 600px;
|
||
text-align: center;
|
||
border: solid 1px #999;
|
||
}
|
||
|
||
|
||
.flip {
|
||
display: inline-block;
|
||
position: relative;
|
||
width: 60px;
|
||
height: 100px;
|
||
line-height: 100px;
|
||
border: solid 1px #000;
|
||
border-radius: 10px;
|
||
background: #fff;
|
||
font-size: 66px;
|
||
color: #fff;
|
||
box-shadow: 0 0 6px rgba(0, 0, 0, .5);
|
||
text-align: center;
|
||
font-family: "Helvetica Neue",serif
|
||
}
|
||
|
||
.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: 10px 10px 0 0;
|
||
border-bottom: solid 1px #666;
|
||
}
|
||
|
||
.flip .digital:after {
|
||
top: 50%;
|
||
bottom: 0;
|
||
border-radius: 0 0 10px 10px;
|
||
line-height: 0;
|
||
}
|
||
|
||
/*向下翻*/
|
||
.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;
|
||
}
|
||
|
||
.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 .back:after {
|
||
animation: backFlipDown 0.6s ease-in-out both;
|
||
}
|
||
|
||
/*向上翻*/
|
||
.flip.up .front:after {
|
||
z-index: 3;
|
||
}
|
||
|
||
.flip.up .back:before {
|
||
z-index: 2;
|
||
transform-origin: 50% 100%;
|
||
transform: perspective(160px) rotateX(-180deg);
|
||
}
|
||
|
||
.flip.up .front:before,
|
||
.flip.up .back:after {
|
||
z-index: 1;
|
||
}
|
||
|
||
.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 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);
|
||
}
|
||
}
|
||
|
||
|
||
@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 .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";
|
||
}
|
||
|
||
.clock {
|
||
text-align: center;
|
||
margin-bottom: 200px;
|
||
}
|
||
|
||
.clock em {
|
||
display: inline-block;
|
||
line-height: 102px;
|
||
font-size: 66px;
|
||
font-style: normal;
|
||
vertical-align: top;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="single-demo">
|
||
<div class="flip down" id="flip">
|
||
<div class="digital front number0"></div>
|
||
<div class="digital back number1"></div>
|
||
</div>
|
||
<div class="btn-con">
|
||
<button id="btn1">向下翻+1</button>
|
||
<button id="btn2">向上翻-1</button>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
<script>
|
||
const flip = document.getElementById('flip');
|
||
const backNode = document.querySelector('.back');
|
||
const frontNode = document.querySelector('.front');
|
||
const btn1 = document.getElementById('btn1');
|
||
const btn2 = document.getElementById('btn2');
|
||
btn1.addEventListener('click', function () {
|
||
flipDown();
|
||
})
|
||
btn2.addEventListener('click', function () {
|
||
flipUp();
|
||
})
|
||
// 当前数字
|
||
let count = 0;
|
||
// 是否正在翻转(防止翻转未结束就进行下一次翻转)
|
||
let isFlipping = false;
|
||
|
||
// 向下翻转+1
|
||
function flipDown() {
|
||
// 如果处于翻转中,则不执行
|
||
if (isFlipping) {
|
||
return false
|
||
}
|
||
// 设置前牌的文字
|
||
frontNode.setAttribute('class', 'digital front number' + count)
|
||
// 计算后牌文字(越界判断)
|
||
const nextCount = count >= 9 ? 0 : (count + 1);
|
||
// 设置后牌的文字
|
||
backNode.setAttribute('class', 'digital back number' + nextCount)
|
||
// 添加go,执行翻转动画
|
||
flip.setAttribute('class', 'flip down go')
|
||
// 当翻转态设置为true
|
||
isFlipping = true
|
||
// 翻转结束后,恢复状态
|
||
setTimeout(function () {
|
||
// 去掉go
|
||
flip.setAttribute('class', 'flip down')
|
||
// 当翻转态设置为false
|
||
isFlipping = false
|
||
// 设置前牌文字为+1后的数字
|
||
frontNode.setAttribute('class', 'digital front number' + nextCount)
|
||
// 更新当前文字
|
||
count = nextCount
|
||
}, 1000)
|
||
}
|
||
|
||
// 向上翻转-1(同理,注释略)
|
||
function flipUp() {
|
||
if (isFlipping) {
|
||
return false
|
||
}
|
||
frontNode.setAttribute('class', 'digital front number' + count)
|
||
const nextCount = count <= 0 ? 9 : (count - 1);
|
||
backNode.setAttribute('class', 'digital back number' + nextCount)
|
||
flip.setAttribute('class', 'flip up go')
|
||
isFlipping = true
|
||
setTimeout(function () {
|
||
flip.setAttribute('class', 'flip up')
|
||
isFlipping = false
|
||
frontNode.setAttribute('class', 'digital front number' + nextCount)
|
||
count = nextCount
|
||
}, 1000)
|
||
}
|
||
|
||
|
||
/* 时钟代码 */
|
||
// 时钟翻牌
|
||
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)
|
||
}
|
||
}
|
||
|
||
// 定位时钟模块
|
||
let clock = document.getElementById('clock')
|
||
// 定位6个翻板
|
||
let flips = clock.querySelectorAll('.flip')
|
||
// 获取当前时间
|
||
let now = new Date()
|
||
// 格式化当前时间,例如现在是20:30:10,则输出"203010"字符串
|
||
let nowTimeStr = formatDate(now, 'hhiiss')
|
||
// 格式化下一秒的时间
|
||
let nextTimeStr = formatDate(new Date(now.getTime() + 1000), 'hhiiss')
|
||
// 定义牌板数组,用来存储6个Flipper翻板对象
|
||
let flipObjs = []
|
||
for (let i = 0; i < flips.length; i++) {
|
||
// 创建6个Flipper实例,并初始化
|
||
flipObjs.push(new Flipper({
|
||
// 每个flipper实例按数组顺序与翻板DOM的顺序一一对应
|
||
node: flips[i],
|
||
// 按数组顺序取时间字符串对应位置的数字
|
||
frontText: 'number' + nowTimeStr[i],
|
||
backText: 'number' + nextTimeStr[i]
|
||
}))
|
||
}
|
||
|
||
|
||
// 开始计时
|
||
setInterval(function () {
|
||
// 获取当前时间
|
||
let now = new Date()
|
||
let nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'hhiiss')
|
||
let nextTimeStr = formatDate(now, 'hhiiss')
|
||
for (let i = 0; i < flipObjs.length; i++) {
|
||
if (nowTimeStr[i] === nextTimeStr[i]) {
|
||
continue
|
||
}
|
||
flipObjs[i].flipDown('number' + nowTimeStr[i], 'number' + nextTimeStr[i])
|
||
}
|
||
}, 1000)
|
||
|
||
//正则格式化日期
|
||
function 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 : padLeftZero(str));
|
||
}
|
||
}
|
||
return dateFormat;
|
||
}
|
||
|
||
//日期时间补零
|
||
function padLeftZero(str) {
|
||
return ('00' + str).substr(str.length);
|
||
}
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|