翻页时钟.html11.91 KB · UTF-8下载
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="format-detection" content="telephone=no">
<title>翻页时钟</title>
<style>
/* ===== 兼容安卓4.2(WebKit 534 / Chrome 18):
   仅使用 ES5 语法;CSS 全部带 -webkit- 前缀;
   不使用 flex/grid/css变量/vh/vw ===== */
html, body { height: 100%; margin: 0; padding: 0; }
body {
  background: #000000;
  color: #ffffff;
  font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft YaHei", sans-serif;
  display: table;
  width: 100%;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
.center { display: table-cell; vertical-align: middle; text-align: center; }

.clock {
  display: inline-block;
  position: relative;
  -webkit-perspective: 900px;
  perspective: 900px;
}

/* ---- 卡片 ---- */
.digits { display: inline-block; }
.flip {
  position: relative;
  width: 140px; height: 180px;
  display: inline-block;
  margin: 0 4px;
}
.half {
  position: absolute;
  left: 0; width: 100%; height: 90px;
  overflow: hidden;
  background: #161616;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.half span {
  position: absolute;
  left: 0; width: 100%;
  height: 180px; line-height: 180px;
  text-align: center;
  font-size: 118px; font-weight: 700;
  color: #f5f5f5;
}
.half.top    { top: 0; border-radius: 10px 10px 0 0; border-bottom: 1px solid #000000; -webkit-box-shadow: inset 0 -2px 4px rgba(0,0,0,0.55); box-shadow: inset 0 -2px 4px rgba(0,0,0,0.55); }
.half.top span    { top: 0; }
.half.bottom { bottom: 0; border-radius: 0 0 10px 10px; -webkit-box-shadow: inset 0 2px 4px rgba(255,255,255,0.05); box-shadow: inset 0 2px 4px rgba(255,255,255,0.05); }
.half.bottom span { top: -90px; }

/* 静态层 */
.half.static { z-index: 1; }

/* 动画层:上翻层(新值从顶部翻下) 与 下翻层(旧值向下翻走) */
.anim-top {
  z-index: 3;
  opacity: 0;
  -webkit-transform: rotateX(90deg);
  transform: rotateX(90deg);
  -webkit-transform-origin: 50% 0%;
  transform-origin: 50% 0%;
  -webkit-transition: -webkit-transform 0.4s ease-out, opacity 0.3s linear;
  transition: transform 0.4s ease-out, opacity 0.3s linear;
}
.anim-top.on {
  opacity: 1;
  -webkit-transform: rotateX(0deg);
  transform: rotateX(0deg);
}
.anim-bottom {
  z-index: 2;
  -webkit-transform: rotateX(0deg);
  transform: rotateX(0deg);
  -webkit-transform-origin: 50% 100%;
  transform-origin: 50% 100%;
  -webkit-transition: -webkit-transform 0.4s ease-in;
  transition: transform 0.4s ease-in;
}
.anim-bottom.on {
  -webkit-transform: rotateX(-90deg);
  transform: rotateX(-90deg);
}

/* ---- 冒号分隔符(闪烁) ---- */
.sep {
  display: inline-block;
  width: 42px; height: 180px;
  line-height: 180px;
  font-size: 100px; font-weight: 700;
  color: #e0e0e0;
  text-align: center;
  vertical-align: top;
  -webkit-animation: sepblink 1s linear infinite;
  animation: sepblink 1s linear infinite;
}
@-webkit-keyframes sepblink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }
@keyframes sepblink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }

/* ---- 日期行 ---- */
.date {
  margin-top: 34px;
  font-size: 20px;
  letter-spacing: 3px;
  color: #8a8a8a;
}

/* ---- 一键全屏按钮(兼容安卓4.2) ---- */
.fsbtn {
  position: fixed;
  right: 14px;
  bottom: 14px;
  z-index: 999;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 22px;
  color: #eeeeee;
  font-size: 15px;
  line-height: 1;
  padding: 11px 20px;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.fsbtn:active {
  background: rgba(255, 255, 255, 0.28);
}

/* ---- 小屏适配 ---- */
@media (max-width: 600px) {
  .flip { width: 84px; height: 108px; margin: 0 2px; }
  .half { height: 54px; }
  .half span { height: 108px; line-height: 108px; font-size: 68px; }
  .half.bottom span { top: -54px; }
  .sep { width: 26px; height: 108px; line-height: 108px; font-size: 60px; }
  .date { font-size: 15px; margin-top: 22px; letter-spacing: 2px; }
  .fsbtn { font-size: 13px; padding: 9px 16px; right: 10px; bottom: 10px; }
}
@media (max-width: 360px) {
  .flip { width: 64px; height: 82px; margin: 0 1px; }
  .half { height: 41px; }
  .half span { height: 82px; line-height: 82px; font-size: 52px; }
  .half.bottom span { top: -41px; }
  .sep { width: 20px; height: 82px; line-height: 82px; font-size: 46px; }
  .date { font-size: 12px; margin-top: 16px; letter-spacing: 1px; }
}
</style>
</head>
<body>
<div class="center">
  <div class="clock">
    <div class="digits" id="hour">
      <div class="flip">
        <div class="half top static"><span>0</span></div>
        <div class="half bottom static"><span>0</span></div>
        <div class="half top anim-top"><span>0</span></div>
        <div class="half bottom anim-bottom"><span>0</span></div>
      </div>
      <div class="flip">
        <div class="half top static"><span>0</span></div>
        <div class="half bottom static"><span>0</span></div>
        <div class="half top anim-top"><span>0</span></div>
        <div class="half bottom anim-bottom"><span>0</span></div>
      </div>
    </div>
    <span class="sep">:</span>
    <div class="digits" id="min">
      <div class="flip">
        <div class="half top static"><span>0</span></div>
        <div class="half bottom static"><span>0</span></div>
        <div class="half top anim-top"><span>0</span></div>
        <div class="half bottom anim-bottom"><span>0</span></div>
      </div>
      <div class="flip">
        <div class="half top static"><span>0</span></div>
        <div class="half bottom static"><span>0</span></div>
        <div class="half top anim-top"><span>0</span></div>
        <div class="half bottom anim-bottom"><span>0</span></div>
      </div>
    </div>
    <span class="sep">:</span>
    <div class="digits" id="sec">
      <div class="flip">
        <div class="half top static"><span>0</span></div>
        <div class="half bottom static"><span>0</span></div>
        <div class="half top anim-top"><span>0</span></div>
        <div class="half bottom anim-bottom"><span>0</span></div>
      </div>
      <div class="flip">
        <div class="half top static"><span>0</span></div>
        <div class="half bottom static"><span>0</span></div>
        <div class="half top anim-top"><span>0</span></div>
        <div class="half bottom anim-bottom"><span>0</span></div>
      </div>
    </div>
    <div class="date" id="date"> </div>
  </div>
</div>
<button type="button" class="fsbtn" id="fsbtn">⛶ 全屏</button>

<script>
/* ===== 纯 ES5 实现,兼容安卓4.2 WebView ===== */
(function () {
  var WEEK = ['日', '一', '二', '三', '四', '五', '六'];

  function pad(n) { return n < 10 ? '0' + n : '' + n; }

  /* 收集某组(时/分/秒)的两位卡片 */
  function buildCells(id) {
    var flips = document.getElementById(id).getElementsByClassName('flip');
    var cells = [];
    var i, f;
    for (i = 0; i < flips.length; i++) {
      f = flips[i];
      cells.push({
        staticTop:    f.getElementsByClassName('static top')[0].getElementsByTagName('span')[0],
        staticBottom: f.getElementsByClassName('static bottom')[0].getElementsByTagName('span')[0],
        animTop:      f.getElementsByClassName('anim-top')[0],
        animBottom:   f.getElementsByClassName('anim-bottom')[0],
        current: '0'
      });
    }
    return cells;
  }

  /* 单个数字位翻页 */
  function setVal(cell, val) {
    var old = cell.current;
    if (old === val) { return; }
    cell.current = val;

    cell.animTop.getElementsByTagName('span')[0].innerHTML = val;      /* 新值上半:翻下来 */
    cell.animBottom.getElementsByTagName('span')[0].innerHTML = old;   /* 旧值下半:翻走 */

    /* 重置动画层到起始姿态 */
    cell.animTop.className = 'half top anim-top';
    cell.animBottom.className = 'half bottom anim-bottom';
    void cell.animTop.offsetWidth;   /* 强制 reflow,保证过渡生效 */

    /* 播放 */
    cell.animTop.className = 'half top anim-top on';
    cell.animBottom.className = 'half bottom anim-bottom on';

    (function (c) {
      setTimeout(function () {
        /* 动画结束:静态层换成新值,动画层归位待命 */
        c.staticTop.innerHTML = val;
        c.staticBottom.innerHTML = val;
        c.animTop.className = 'half top anim-top';
        c.animBottom.className = 'half bottom anim-bottom';
        c.animBottom.getElementsByTagName('span')[0].innerHTML = val;
      }, 450);
    })(cell);
  }

  var hours = buildCells('hour');
  var mins  = buildCells('min');
  var secs  = buildCells('sec');
  var dateEl = document.getElementById('date');

  function tick() {
    var d = new Date();
    var h = pad(d.getHours());
    var m = pad(d.getMinutes());
    var s = pad(d.getSeconds());
    var i;
    for (i = 0; i < 2; i++) {
      setVal(hours[i], h.charAt(i));
      setVal(mins[i], m.charAt(i));
      setVal(secs[i], s.charAt(i));
    }
    var str = d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日 星期' + WEEK[d.getDay()];
    if (dateEl.innerHTML !== str) { dateEl.innerHTML = str; }
  }

  tick();
  setInterval(tick, 1000);

  /* ===== 一键全