你倒是用原生写个钟呀

227 阅读4分钟

前言

我为什么想写个钟?其实你上网胡乱一搜,资料便铺天盖地。但是我最烦的是搜着搜着,好不容易有点眉目了,就要你充个会员,真的很影响兴致。我承认,现在是一个知识付费的的大时代,包括音乐、视频、文档等等。当免费习以为常,付费就如鲠在喉。随着时间的推移,你终将败下阵来,然后付费变得习以为常。

扯远了,我为什么要写个钟?起因:写着玩儿,就是觉得这个东西很有趣。我可以把我它变成我喜欢的样子,用代码写出来的东西和买来的东西,意义截然不同。虽不惊艳,却乐在其中。 还会想起小时候,和小伙伴们一起用圆珠笔在手上笨拙地画一个歪歪扭扭的手表......

一、钟的大体结构

左思右想,索性采用清一色的 div 搭个架子:

  1. 首先来六根刻度线,交叉就是 12 根,足矣
  2. 然后来个遮罩层,把刻度线盖住,露出边边角角,作为刻度
  3. 再来时钟、分钟、秒钟各一个 div
  4. 最后就是修修补补的圆点点,再附上时期和星期
<body>
    <div class="clock">
        <!-- 刻度线 -->
        <div class="line"></div>
        <div class="line"></div>
        <div class="line"></div>
        <div class="line"></div>
        <div class="line"></div>
        <div class="line"></div>
        <!-- 遮罩层 -->
        <div class="mask"></div>
        <!-- 时钟 -->
        <div class="hour"></div>
        <!-- 分钟 -->
        <div class="minute"></div>
        <!-- 秒钟 -->
        <div class="second"></div>
        <!-- 螺丝 -->
        <div class="dotted-black"></div>
        <div class="dotted-red"></div>
        <div class="dotted-white"></div>
        <!-- 日期和星期 -->
        <div class="curtime"></div>
    </div>
</body>

二、钟的大体样式

虽“美人”在骨不在皮,重要时刻还是捯饬捯饬:

  1. 刻度线先水平垂直居中,然后给刻度线设置旋转度数
  2. 把遮罩盖上去,同样水平垂直居中,只漏出最外层的一点,作为刻度
  3. 时钟、分钟、秒钟注意设置旋转中心,秒钟要复杂一丢丢
  4. 层级问题可以最后再看,我是文字上不去,所以加了层级 z-index
<style>
        * {
            padding: 0;
            margin: 0;
        }
        .clock {
            position: relative;
            width: 200px;
            height: 200px;
            border-radius: 50%;
            margin: 100px auto;
            background-color: rgb(240, 241, 245);
            box-shadow: 0 0 10px rgb(231, 235, 244) inset;
        }
        .clock .line {
            position: absolute;
            width: 3px;
            height: 195px;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            background-color: rgb(195, 204, 215);
            border-radius: 5px;
        }
        .clock .line:nth-child(2) {
            transform: rotateZ(30deg);
        }
        .clock .line:nth-child(3) {
            transform: rotateZ(60deg);
        }
        .clock .line:nth-child(4) {
            transform: rotateZ(90deg);
        }
        .clock .line:nth-child(5) {
            transform: rotateZ(120deg);
        }
        .clock .line:nth-child(6) {
            transform: rotateZ(150deg);
        }
        .mask {
            position: absolute;
            z-index: 1;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            width: 180px;
            height: 180px;
            border-radius: 50%;
            background-color: rgb(240, 241, 245);
        }
        .hour {
            position: absolute;
            z-index: 3;
            left: 0;
            right: 0;
            bottom: 100px;
            margin: auto;
            width: 5px;
            height: 50px;
            background-color: black;
            border-radius: 5px 5px 0 0;
            transform-origin: bottom center;
        }
        .minute {
            position: absolute;
            z-index: 4;
            left: 0;
            right: 0;
            bottom: 100px;
            margin: auto;
            width: 3px;
            height: 70px;
            background-color: black;
            border-radius: 3px 3px 0 0;
            transform-origin: bottom center;
        }
        .second {
            position: absolute;
            z-index: 6;
            left: 0;
            right: 0;
            bottom: 90px;
            margin: auto;
            width: 2px;
            height: 100px;
            background-color: red;
            border-radius: 1px;
            transform-origin: 1px 90px;
        }
        .dotted-black {
            position: absolute;
            z-index: 5;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            width: 12px;
            height: 12px;
            background-color: black;
            border-radius: 50%;
        }
        .dotted-red {
            position: absolute;
            z-index: 7;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            width: 8px;
            height: 8px;
            background-color: red;
            border-radius: 50%;
        }
        .dotted-white {
            position: absolute;
            z-index: 7;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            width: 4px;
            height: 4px;
            background-color: white;
            border-radius: 50%;
        }
        .curtime {
            position: absolute;
            z-index: 2;
            left: 0;
            right: 0;
            top: 50px;
            text-align: center;
            color: #fff;
        }
 </style>

这是它最初的模样:素静。任尔东西南北风,它自岿然不动呀。

image.png

三、让时分秒跑起来

你既静如处子,想必动如脱兔:

  1. 采用 animation + @keyframs 使之旋转
  2. 秒针转一圈 60s ,分针转一圈 360s ,时针转一圈 43200s
  3. infinite 无限循环,linear 匀速运动
  4. 如果是 steps(60) 就会在规定时间内,走60下
        .hour {
            /* 补间动画*/
            animation: hour 43200s infinite linear;
            /* 逐帧动画*/
            /* animation: hour 43200s infinite steps(43200); */
        }
        .minute {
            animation: minute 3600s infinite linear;
            /* animation: minute 3600s infinite steps(3600); */
        }
        .second {
            animation: second 60s infinite linear;
            /* animation: second 60s infinite steps(60); */
        }
        @keyframes hour {
            0% {
                transform: rotate(0);
            }
            100% {
                transform: rotate(360deg);
            }
        }
        @keyframes minute {
            0% {
                transform: rotate(0);
            }
            100% {
                transform: rotate(360deg);
            }
        }
        @keyframes second {
            0% {
                transform: rotate(0);
            }
            100% {
                transform: rotate(360deg);
            }
        }

看,它可以旋转了!

image.png

四、时分秒匹配当前时间

即便使用 CSS 动画就可以让时钟、分钟、秒钟有节奏地跑起来,但如何匹配当前的时间呢?

  1. 首先一圈 360°,那么一秒钟走 6°,一分钟走 6°,一小时走 360°
  2. 秒钟旋转度数:s * 6
  3. 分钟旋转度数:( m + s / 60 ) * 6
  4. 时钟旋转度数:( h + m / 60 + s / 3600 ) * 30
 <script>
        // ◆ 获取元素
        let hour = document.querySelector('.hour')
        let minute = document.querySelector('.minute')
        let second = document.querySelector('.second')
        let curtime = document.querySelector('.curtime')
        let mask = document.querySelector('.mask')
        let clock = document.querySelector('.clock')
        let dottedB = document.querySelector('.dotted-black')
        let dottedR = document.querySelector('.dotted-red')
        let dottedW = document.querySelector('.dotted-white')
        // ◆ 在此调用一次,解决初次进入页面的卡顿现象
        setTime()
        // ◆ 我的钟表每隔一秒走一次,这里会有 1s 的卡顿
        setInterval(setTime, 1000)
        // ◆ 封装时分秒走动的函数
        function setTime() {
            // 1.获取当前日期与时间等
            let date = new Date()
            let Y = date.getFullYear()
            let M = date.getMonth() + 1
            let D = date.getDate()
            let d = date.getDay()
            let s = date.getSeconds()
            let m = date.getMinutes()
            let h = date.getHours()
            // 2.获取当前时分秒旋转的角度
            let sdeg = s * 6
            let mdeg = (m + s / 60) * 6
            let hdeg = (h + m / 60 + s / 3600) * 30
            // 3.把当前时分秒旋转的的角度赋值给 transform
            second.style.transform = `rotate(${sdeg}deg) `
            minute.style.transform = `rotate(${mdeg}deg) `
            hour.style.transform = `rotate(${hdeg}deg)`
            console.log(s, m, h)
            curtime.innerHTML = `${M}${D}日 星期${dayFormat(d)}`
            // 3.晚上钟的样式
            if (h >= 18 || h <= 7) {
                mask.style.backgroundColor = 'rgb(37, 38, 40)'
                clock.style.backgroundColor = 'rgb(37, 38, 40)'
                hour.style.backgroundColor = 'rgb(173, 174, 176)'
                minute.style.backgroundColor = 'rgb(194, 195, 197)'
                second.style.backgroundColor = 'rgb(217, 70, 24)'
                dottedB.style.backgroundColor = 'rgb(173, 174, 176)'
                dottedR.style.backgroundColor = 'rgb(217, 70, 24)'
                dottedW.style.backgroundColor = 'rgb(0, 0, 0)'
                curtime.style.color = 'black'
            }
        }
        // ◆ 封装星期函数
        function dayFormat(num) {
            const arr = ['日', '一', '二', '三', '四', '五', '六']
            return arr[num]
        }
 </script>

(前面的 CSS 动画可以干掉了。)

嗯,还是晚上看起来更高级。goodnight!

image.png

五、发现钟的一个小 bug

不不不,还没完!最后渲染页面会有一个 1s 的误差,如果你仔细去比对秒针的走势,就会发现,咱们的钟始终比实际时间慢一秒,更严重的会变成 2s,乃至更多。如果你的电脑比较好,应该稳定在 1s 的误差。在 PC 端,我们需要加 1s 解决这个误差,这是重绘导致。如果你在手机上看这个 demo 就不需要 加 1s 了,因为手机的渲染性能足够优秀!

 let s = date.getSeconds() + 1

结语

说实话,这个钟“折磨”了我两天,有两点:第一,旋转度数的计算;第二,那个 1s 的误差。我原以为这是一个小工程,没想到困扰我这么久。关于旋转度数的计算,我查了很多资料,网上答案千奇百怪,其实这就是一个小学数学题吧。至于那个 1s 的误差,我一开始百思不得其解,一直以为是我的度数计算有问题,也确实是有问题,最初时钟旋转计算掉了个 s/3600,可我把它加上时还是出了问题。最后问了一位资深的前端前辈,豁然开朗......