還在使用定時器嗎?CSS 也能實現電子時鐘

通常要做一個時鐘,肯定離不開 JS 定時器。今天換一種思路,用 CSS 來實現一個時鐘,如下:

你也可以訪問這個 CSS time (codepen.io)[1] 查看實際效果

當然借用了一點點 JS 用於初始化時間,整個時鐘的運行都是由 CSS 完成的,有很多你可能不知道的小技巧,一起看看吧

一、數字的變換

先看看數字是如何變換的。

在以前,如果要實現數字的遞增變化,可能需要提前準備好這些數字,例如像這樣

<span>
 <i>1</i>
  <i>2</i>
  ...
  <i>59</i>
</span>

然後通過改變位移來實現。

但是,現在有更簡潔的方式可以實現了,那就是 CSS @property[2],不瞭解這個的可以參考這篇文章:CSS @property,讓不可能變可能 [3]。這是幹什麼的呢?簡單來講,可以自定義屬性,在這個例子中,可以讓數字像顏色一樣進行過渡和動畫,可能不太懂,直接看例子吧

假設 HTML 是這樣的

<span style="--num: 0"></span>

我們讓這個自定義變量在頁面中展示出來,單純的 content無法直接顯示自定義變量,需要藉助定時器,有興趣的可以參考這篇文章:小 tips: 如何藉助 content 屬性顯示 CSS var 變量值 [4]

span::after{
  counter-reset: num var(--num);
  content: counter(num);
}

然後,可以通過:hover改變這個數字

span:hover::after{
  --num: 59
}

很生硬的從 0 變成 59 了,非常符合常規。如果利用 CSS property,情況就不一樣了,需要改造的地方很少,先定義一下--h,然後給這個變量一個過渡時間,如下

@property --h { 
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
span::after{
  transition: 1s --num;
}

神奇的一幕發生了

看着好像不可思議?可以這麼理解,通過@property定義後,這個變量本身可以單獨設置過渡了,而不再取決於一些僅支持過渡的屬性(colorwidth等)。甚至還能加上動畫,需要用到steps方法,設置動畫週期爲無限,如下

@keyframes num {
  to {
    --num: 10
  }
}
span{
  animation: num 1s infinite steps(10);
}

時鐘的基本運行原理就是這樣了,一個無限循環的 CSS 動畫!

二、時、分、秒

下面來看具體時、分、秒的實現,HTML 如下

<div class="time">
  <span class="hour"></span>
  <a class="split">:</a>
  <span class="minitus"></span>
  <a class="split">:</a>
  <span class="seconds"></span>
</div>

給時、分、秒附上初始值

@property --h { 
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --m { 
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --s { 
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
.hour::after{
  counter-reset: hour var(--h);
  content: counter(hour);
}
.minitus::after{
  counter-reset: minitus var(--m);
  content: counter(minitus);
}
.seconds::after{
  counter-reset: seconds var(--s);
  content: counter(seconds);
}

這裏的時、分、秒並沒有聯動關係,所以各自都需要單獨的動畫。下面就需要思考一下🤔,如果用 CSS 動畫來實現,每個的動畫起始點和時長是多少呢?

沒錯,就是你想的,時針是0-23,時長24h,分針是0-59,時長60min,秒針是0-59,時長60s,但是 CSS 中的時間單位只支持秒(s)或者毫秒(ms),所以這裏需要轉換一下,時長分別是60s*60*2460s*6060s,具體實現如下:

@keyframes hour {
  to {
    --h: 24
  }
}
@keyframes minitus {
  to {
    --m: 60
  }
}
@keyframes seconds {
  to {
    --s: 60
  }
}
.hour::after{
  counter-reset: hour var(--h);
  content: counter(hour);
  animation: hour calc(60s * 60 * 24) infinite steps(24);
}
.minitus::after{
  counter-reset: minitus var(--m);
  content: counter(minitus);
  animation: minitus calc(60s * 60) infinite steps(60);
}
.seconds::after{
  counter-reset: seconds var(--s);
  content: counter(seconds);
  animation: seconds 60s infinite steps(60);
}

這裏爲了便於觀察,將時間調快了 10 倍(60s => 6s),如下

三、時、分、秒自動補零

上面的佈局有個問題,1 位數和 2 位數寬度變化導致時鐘整體都在 “晃動”,所以需要在 1 位數時補上一個 “0”。關於 CSS 補零,之前在文章 CSS 補全字符串?中提到了 3 種方案,由於這裏用了計數器,所以直接選擇更改計數器樣式的方法,通過decimal-leading-zero來實現,具體做法如下

.hour::after{
  /**/
  content: counter(hour, decimal-leading-zero);/*添加計數器樣式*/
}

這樣就和諧多了

四、時間初始化

剛纔都從00:00:00開始了,所以需要手動指定一下初始時間。假設現在是19:26:30,如何初始化呢?

這裏需要用animation-delay來提前運動到未來指定位置,爲了方便控制,使用三個變量--dh--dm--ds來表示初始時間,注意,由於animation-delay也只支持秒(s)或者毫秒(ms),所以也同樣需要轉換,實現如下

:root{
  --dh: 19;
  --dm: 26;
  --ds: 30;
}
.hour::after{
  /**/
  animation: hour calc(60s * 60 * 24) infinite steps(24);
  animation-delay: calc( -60s * 60 * var(--dh) );
}
.minitus::after{
  /**/
  animation: minitus calc(60s * 60) infinite steps(60);
  animation-delay: calc( -60s * var(--dm) );
}
.seconds::after{
  /**/
  animation: seconds 60s infinite steps(60);
  animation-delay: calc( -1s * var(--ds) );
}

是不是有點奇怪?分鐘在秒鐘走到 30 的時候才變化,晚了半分鐘。原因是這樣的,雖然從數字上看,分鐘是 26,但是還要考慮到秒鐘的運動情況,比如像這種情況,分鐘其實已經走了一半,應該是 26.5(26 + 30 / 60),所以在計算時還需要加上偏移量。下面我們通過 JS 獲取真實的時間,並修復偏移

const d = new Date()
const h = d.getHours();
const m = d.getMinutes();
const s = d.getSeconds();
document.body.style.setProperty('--ds', s)
document.body.style.setProperty('--dm', m + s/60)
document.body.style.setProperty('--dh', h + m/60 + s/3600)

這樣就正常了

五、閃爍的分隔符

爲了時鐘看起來更加 “動感”,可以給分隔符加上閃爍動畫,代碼如下

@keyframes shark {
  0%, 100%{
    opacity: 1;
  }
  50%{
    opacity: 0;
  }
}
.split{
  animation: shark 1s step-end infinite;
}

現在看下最終的效果

完整代碼可以訪問 CSS time (codepen.io)[5]

六、總結一下

想不到實現一個時鐘效果,用到了那麼多 CSS 知識和技巧,簡單總結一下吧

  1. CSS 實現本質是無限循環的 CSS 動畫

  2. 靈活運用 CSS calc 計算

  3. CSS 計數器可以將 CSS 變量通過 content 顯示在頁面

  4. 數字的變化現在可以通過 CSS @property 配合動畫實現

  5. 時分秒的區別在於各自的動畫時長、動畫起始點不同

  6. CSS 自動補零可以參考之前的文章,這裏採用 decimal-leading-zero 實現

  7. 時間初始化其實就是指定動畫 delay 值

  8. 指定初始值時還需要考慮到各自的偏移量,例如 19:30:30,此時的時針數字其實是 30.5

  9. 分隔符的閃爍動畫

其實整個實現過程就是一個不斷思考、學習的過程,比如爲了實現數字的變化,就必須去學習 @property 相關,爲了實現補零,就需要去了解更深層次的計數器相關,還有用到的各種動畫。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

參考資料

[1]

CSS time (codepen.io): https://codepen.io/xboxyan/pen/OJOdvyy

[2]

**CSS @property: **https://developer.mozilla.org/zh-CN/docs/Web/CSS/@property

[3]

**CSS @property,讓不可能變可能: **https://juejin.cn/post/6951201528543707150

[4]

小 tips: 如何藉助 content 屬性顯示 CSS var 變量值: https://www.zhangxinxu.com/wordpress/2019/05/content-css-var/

[5]

**CSS time (codepen.io): **https://codepen.io/xboxyan/pen/OJOdvyy

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/euSh7On_0jpXulVhbDhZVQ