還在使用定時器嗎?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
定義後,這個變量本身可以單獨設置過渡了,而不再取決於一些僅支持過渡的屬性(color
、width
等)。甚至還能加上動畫,需要用到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*24
、60s*60
、60s
,具體實現如下:
@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 知識和技巧,簡單總結一下吧
-
CSS 實現本質是無限循環的 CSS 動畫
-
靈活運用 CSS calc 計算
-
CSS 計數器可以將 CSS 變量通過 content 顯示在頁面
-
數字的變化現在可以通過 CSS @property 配合動畫實現
-
時分秒的區別在於各自的動畫時長、動畫起始點不同
-
CSS 自動補零可以參考之前的文章,這裏採用 decimal-leading-zero 實現
-
時間初始化其實就是指定動畫 delay 值
-
指定初始值時還需要考慮到各自的偏移量,例如 19:30:30,此時的時針數字其實是 30.5
-
分隔符的閃爍動畫
其實整個實現過程就是一個不斷思考、學習的過程,比如爲了實現數字的變化,就必須去學習 @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