Day 2 - CSS + JS Clock

前言

JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用课程,课程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教学影片里,建立30个JavaScript的有趣小东西。

另外,Wes Bos 也很无私地在 Github 上公开了所有 JS 30 课程的程序码,有兴趣的话可以去 fork 或下载。


本日目标

透过 JavaScript 的 Date 物件分别取得"时"、"分"、"秒"并计算出在圆上的相对应角度,最後搭配 CSS 的 transformtransition 属性,制作出一个简易的时钟。


解析程序码

HTML 部分

由最外层的"clock"部分包住内层的"clock-face"和其内部的"时针"、"分针"、"秒针",形成一个完整的巢状结构。

<div class="clock">
      <div class="clock-face">
        <div class="hand hour-hand"></div>
        <div class="hand min-hand"></div>
        <div class="hand second-hand"></div>
      </div>
</div>

CSS 部分

首先,将物件 transform 的基准点,更改为最右端。接着,将所有指针都预先固定在12点钟方向。最後,透过 transition 属性还有 transition-timing-function属性,分别调整 CSS animation 效果变化速度和做出指针移动时的弹跳效果。

.hand {
      width: 50%;
      height: 6px;
      background: black;
      position: absolute;
      top: 50%;
      
      /*以下是影片中教学的部分*/
      transform-origin: 100%; /*改变 transform 的 x-axis*/
      transform: rotate(90deg); /*初始位置从12点钟出发*/
      transition: all 0.05s; /*调整 CSS animation 变动的速度*/ 
      transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1);
}
补充说明1:

transform 预设是以物件中心作为平移、旋转、缩放、倾斜时的基准点。详细内容见此

补充说明2:

transition timing function,可用来定义转场发生的时间曲线,以四个参数的贝兹曲线代表。详细内容见此

JS 部分

首先,分别取得代表"时针"、"分针"、"秒针"的标签。

/*JS*/
const secondHand = document.querySelector('.second-hand');
const minsHand = document.querySelector('.min-hand')
const hourHand = document.querySelector('.hour-hand')

建立 Date 物件,取得"时"、"分"、"秒"的资料,以此算出所应旋转的角度(注意,角度必须加上早先设定的90度,才会是正确的),之後分别调整 CSS 的 transform 属性。

最後用 setInterval() 方法,设定每1000毫秒(1秒)就执行 setDate() 方法一次,藉此动态改变 rotate 的值。

(时针、分针、秒针的原理都一样,只是在角度计算上有所差异)

/*JS*/
function setDate(){
    const now = new Date();

    /*时针、分针、秒针的原理都一样*/
    const seconds = now.getSeconds();
    const secondsDegrees = ((seconds/60)*360) + 90;/*旋转的角度要加上预设的90度*/
    
    secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
}  

setInterval(setDate,1000)

分别设定完"时针"、"分针"、"秒针"後,setDate() 方法如下。

/*JS*/
function setDate(){
    const now = new Date();

    /*时针、分针、秒针的原理都一样*/
    const seconds = now.getSeconds()
    const secondsDegrees = ((seconds/60)*360) + 90;/*旋转的角度要加上预设的90度*/
    secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
  
    const mins = now.getMinutes();
    const minsDegrees = ((mins/60)*360) + 90;
    minsHand.style.transform = `rotate(${minsDegrees}deg)`;

    const hours = now.getHours();
    const hoursDegrees = ((hours/12)*360) + 90;
    hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
}

以上都完成後,一个简单的时钟就出现了。但仔细一看就会发现指针在某个时间点会突然倒转一圈。

举"秒针"为例,在59秒~0秒之间,数值上的角度会从444度变为90度(分针也是如此),整整倒转354度接近一圈,这就解释了为什麽指针会有突然倒转的现象。

而我们可以分别记录时针和分针所走的圈数,并将原来计算出的度数加上360度*圈数,解决指针倒转的问题。

用 if 判断到 0 秒(分)时,就将圈数加1。

var secRound = 0;  /*纪录秒针所走圈数*/
var minRound = 0;  /*纪录秒针所走圈数*/

function setDate(){
    const now = new Date();

    /*时针、分针、秒针的原理都一样*/
    const seconds = now.getSeconds()
        if(seconds == 0){/*避免回弹*/ 
            secRound += 1;
        }
    const secondsDegrees = ((seconds/60)*360) + 360*secRound + 90;/*旋转的角度要加上预设的90度*/
    secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
  
    const mins = now.getMinutes();
    if(mins == 0){/*避免回弹*/ 
        minRound += 1;
    }
    const minsDegrees = ((mins/60)*360) + 360*minRound + 90;
    minsHand.style.transform = `rotate(${minsDegrees}deg)`;

    const hours = now.getHours();
    const hoursDegrees = ((hours/12)*360) + 90;
    hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
}

最後的最後,我们还可以让指针的位置更加精准。一般而言,秒针每走一格,分针应该跟着移动一点,同理时针也是如此。

对分针而言移动每过一分钟移动6度,我们可以用 (秒数/60)*6,算出实际上每过一秒钟,分针应该要跟着移动多少度。

对时针而言移动每过一小时移动30度,我们可以用 (分钟数/60)*30,算出实际上每过一分钟,时针应该要跟着移动多少度。

const mins = now.getMinutes();
if(mins == 0){/*避免回弹*/ 
    minRound += 1;
}
const minsDegrees = ((mins/60)*360) + 360*minRound + ((seconds/60)*6) + 90;
minsHand.style.transform = `rotate(${minsDegrees}deg)`;

const hours = now.getHours();
const hoursDegrees = ((hours/12)*360) + ((mins/60)*30) + 90;
hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
补充说明1:

rotate(${hoursDegrees}deg)ES6 Template literals 的写法。
详细介绍 Template literals

范例网页请按此


<<:  Day 1 - JavaScript 的变数与基本资料型态

>>:  Day 2 - 何谓 Rancher

CIA安全目标

曾就「资讯本身的破坏」和「资讯或资讯系统获取或使用中断」进行了辩论。然而,FISMA和FIPS 19...

HTML 与 CSS 学习笔记 - 系列目录

前言 将这 30 天的资料做成目录,并简单说明内容,方便查找 纪录一下 系列目录 HTML 与 CS...

linebot 结合网路爬虫

linebot 结合网路爬虫 讲解完网路爬虫的实际应用後,接下来将他跟 Line chatbot 进...

.NET CLI 打包成单一免安装 Runtime/SDK Exe 执行档

Youtube 影片 : 影片介绍如何使用 dotnet cli 打包 .net 开发程序,建立单一...

[ Day 18 ] 条件 Render - Conditional Rendering

在前面元件以及生命周期的章节中我们提过 render() 这个方法,而且有特别指出它是在 Clas...