你可能不需要JS!CSS實現一個計時器

XboxYan發表於2023-04-17

CSS現在可不僅僅只是改一個顏色這麼簡單,還可以做很多互動,比如做一個功能齊全的計時器?

Kapture 2023-04-13 at 19.41.25.gif

樣式上並不複雜,主要是幾個互動的地方

  1. 數字時鐘的變化
  2. 開始、暫停操作
  3. 重置操作

如何僅使用 CSS 來實現這樣的功能呢?一起看看吧

一、數字時鐘的變化

這個功能之前在這篇文章中有詳細介紹,有興趣的可以回顧一下

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

這裡簡單介紹一下實現原理

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

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

或者用偽元素建立也行

span::before{
  content: '1\A 2\A 3\A ... 59'
}

這種方式需要建立多個標籤,略微繁瑣,也不易擴充套件。現在有更簡潔的方式可以實現了,那就是 CSS @property。這是幹什麼的呢?簡單來講,可以自定義屬性,在這個例子中,可以讓數字像顏色一樣進行過渡和動畫,可能不太懂,直接看例子吧

假設 HTML 是這樣的

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

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

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

image.png

現在可以透過:hover改變這個數字

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

Kapture 2022-03-04 at 17.09.17.gif

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

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

神奇的一幕發生了

Kapture 2022-03-04 at 17.14.07.gif

看著好像不可思議?可以這麼理解,透過@property定義後,這個變數本身可以單獨設定過渡了,而不再取決於一些僅支援過渡的屬性(colorwidth等)。還可以使用動畫,如下

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

數字變化的基本原理就是這樣了,一個無限迴圈的 CSS 動畫!

Kapture 2022-03-04 at 17.26.23.gif

回到這裡,這裡需要的是一個秒錶,分為“分”、“秒”、“毫秒”(這裡的毫秒就用 1/100秒來代替),3個數字的動畫時長都不一致,所以需要定義3個 CSS 變數,完整實現如下

@keyframes minitus {
  to {
    --m: 59
  }
}
@keyframes seconds {
  to {
    --s: 59
  }
}
@keyframes ms {
  to {
    --ms: 99
  }
}
span{
  counter-reset: minitus var(--m) seconds var(--s) ms var(--ms);
  animation: minitus 3600s infinite steps(60, end), 
    seconds 60s infinite steps(60, end), 
    ms 1s infinite steps(100, end);
}
span::before{
  content: counter(minitus, decimal-leading-zero) ':' counter(seconds, decimal-leading-zero) ':' counter(ms, decimal-leading-zero);
}

這樣就得到了一個自動執行的秒錶

Kapture 2023-04-13 at 20.12.49.gif

二、開始、暫停操作

首先思考一下,CSS 需要怎樣記住點選操作?答案就是input type="checkbox"(透過label關聯),可以這樣來佈局

<div class="counter">
  <input type="checkbox" id="start" hidden>
  <label class="btn start" for="start"></label>
  <label class="btn reset">重置</label>
  <div class="clock"></div>
</div>

因為需要透過input:cheked來控制秒錶的狀態,需要藉助後置兄弟選擇器~來實現,所以input需要在前面(當然,現在有了:has也可以不需要這樣)。

這裡可以透過grid佈局來靈活擺放各個模組的位置

.counter{
  display: grid;
  grid-template-areas:
    "clock clock"
    "start reset"
}
.start{
  /**/
  grid-area: start;
}
.reset{
  /**/
  grid-area: reset;
}

簡單美化以後,可以得到這樣的效果

Kapture 2023-04-13 at 20.20.15.gif

然後,由於秒錶的執行其實就是一個 CSS 動畫,所以我們可以直接用:cheked來控制動畫的狀態,預設設定成暫停的,還有按鈕文字也可以透過::before來生成,實現如下

.clock{
  animation-play-state: paused;/*預設暫停*/
}
.start::before{
  content: '開始';
}
:checked~.start::before{
  content: '暫停';
}
:checked~.clock{
  animation-play-state: running;
}

這樣就可以透過按鈕手動控制開始和暫停了

Kapture 2023-04-13 at 20.25.13.gif

三、重置操作

重置看起來好像有點麻煩,有點無從下手。

其實重置一個動畫非常簡單,直接將動畫取消就可以了,也就是相當於重置了動畫,如下

.reset:active+.clock{
  animation: none;
}

其次,重置一般只有在暫停時才可用,所以還需要用前面的:checked禁用一下,並且視覺上可以透明度降低一點,實現如下

:checked~.reset{
  opacity: .65;
  pointer-events: none;
}

這樣就得到了文章開頭所示的效果

Kapture 2023-04-13 at 19.41.25.gif

當然,你還可以使用自定義字型,比如DigitalNumbers

Kapture 2023-04-13 at 20.32.40.gif

完整程式碼可以檢視以下任意連結:

四、會影響業務邏輯嗎?

還有一點,有同學擔心 CSS 只是視覺層面的,可能會影響業務邏輯。

image.png

確實,由於是偽元素渲染,頁面上看不到任何數字,也就是無法直接透過innerText獲取當前時間,但是,我們可以藉助getComputedStyle來得到 CSS 變數

getComputedStyle($0).getPropertyValue('--ms')

實時獲取如下

image.png

所以透過 CSS 方式也是完全不影響業務邏輯的

五、相容性和總結

由於在實現中用到了CSS @property特性,這是CSS Houdini的一部分,目前只有 Chrome 支援(可惜了?)。讓人驚奇的是,Safari居然在前不久也支援了這個特性,未來可期,如下

image.png

當然這不是重點,只是這種方式實現更加簡潔而已,完全可以用傳統方式來實現,有興趣的可以嘗試一下。

下面總結一下實現要點

  1. CSS 現在很強大,不僅僅只是樣式,還能做很多互動
  2. CSS @property 可以使CSS變數支援動畫
  3. 數字時鐘的變化其實是一個CSS變數不斷遞增迴圈的動畫
  4. CSS 點選操作狀態可以透過:checked控制
  5. Grid 佈局可以很方便的控制各個元素的位置
  6. 計時器開始和暫停其實就是動畫的執行和暫停
  7. 直接將動畫取消就相當於重置了整個動畫

最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

相關文章