還在用 JS 做節流嗎?CSS 也可以防止按鈕重複點選

XboxYan 發表於 2022-11-24
CSS

眾所周知,函式節流(throttle)是 JS 中一個非常常見的最佳化手段,可以有效的避免函式過於頻繁的執行。

歡迎關注我的公眾號:前端偵探

舉個例子:一個儲存按鈕,為了避免重複提交或者伺服器考慮,往往需要對點選行為做一定的限制,比如只允許每300ms提交一次,這時候我想大部分同學都會到網上直接複製一段throttle函式,或者直接引用lodash工具庫

btn.addEventListener('click', _.throttle(save, 300))

其實除了 JS 方式, CSS 也可以非常輕易的實現這樣一個功能,無需任何框架庫,一起看看吧

一、CSS 實現思路分析

CSS 實現和 JS 的思維不同,需要從另一個角度去看待這個問題。

比如這裡的需要對點選事件進行限制,也就是禁用點選事件,想想有什麼方式可以禁用事件,沒錯,就是pointer-events;

然後是時間的限制,每次點選後需要自動禁用300ms,時間過後重新恢復,那麼,有什麼特性和時間以及狀態恢復有關呢?沒錯,就是animation;

除此之外,還需要有觸發時機,這裡是點選行為,所以必然和偽類:active有關聯。

因此,綜合分析,實現這樣一個功能需要用到pointer-eventsanimation以及:active,那麼如何將這些思路串聯起來呢?

image-20221112001600031

思考3秒...

🤔

🤔

🤔

你想到了嗎?💡💡💡

其實這種場景可以理解成是對 CSS 動畫的控制,比如有一個動畫控制按鈕從禁用->可點選的變化,每次點選時讓這個動畫重新執行一遍,在執行的過程中,一直處於禁用狀態,是不是就達到了“節流”的效果了?

接下來看看具體實現

二、CSS 動畫的精準控制

假設有一個按鈕,繫結了一個點選事件

<button onclick="console.log('儲存')">儲存</button>

這時的按鈕連續點選就會不斷地觸發,效果如下

Kapture 2022-11-12 at 00.17.44

下面定義一個關於pointer-events的動畫,就叫做 throttle

@keyframes throttle {
  from {
    pointer-events: none;
  }
  to {
    pointer-events: all;
  }
}

很簡單吧,就是從禁用可點選的變化。

接下來,將這個動畫繫結在按鈕上,這裡為了方便測試,將動畫設定成了2s

button{
  animation: throttle 2s step-end forwards;
}

注意,這裡動畫的緩動函式設定成了階梯曲線,step-end,它可以很方便的控制pointer-events的變化時間點。

有興趣的可以參考這篇文章:CSS3 animation屬性中的steps功能符深入介紹 « 張鑫旭-鑫空間-鑫生活 (zhangxinxu.com)

如下示意,pointer-events在0~2秒內的值都是none,一旦到達2秒,就立刻變成了all,由於是forwards,會一直保持all的狀態

image-20221112005210875

最後,在點選時重新執行一遍動畫,只需要在按下時設定動畫為none就行了

這個技巧之前在這篇文章中有更詳細的介紹: CSS 實現按鈕點選動效的套路

實現如下

button:active{
  animation: none;
}

為了演示方便,我們暫時把顏色變化也加在動畫裡

@keyframes throttle {
  from {
    color: red;
    pointer-events: none;
  }
  to {
    color: green;
    pointer-events: all;
  }
}

現在如果文字是red,表示是禁用態,只有是green,才表示可以被點選,非常清晰明瞭,如下

Kapture 2022-11-12 at 10.54.43

下面是最終點選對比效果,很好地限制了點選頻率

Kapture 2022-11-12 at 10.48.13

完整程式碼如下,就這麼幾行,如果需要改限制時間,直接改動畫時間就行了

button{
  animation: throttle 2s step-end forwards;
}
button:active{
  animation: none;
}
@keyframes throttle {
  from {
    pointer-events: none;
  }
  to {
    pointer-events: all;
  }
}

你也可以檢視以下任意連結:

三、CSS 實現的其他思路

還記得之前這一篇文章嗎?

還在用定時器嗎?藉助 CSS 來監聽事件

借用這種思路,也可以很輕鬆的實現節流的效果。而且為了更好的體驗,可以用上真正的按鈕禁用

btn.disabled = true

具體思路是這樣的,透過:active去觸發transition變化,然後透過監聽transition回撥去動態設定按鈕的禁用狀態,實現如下

定義一個無關緊要的過渡屬性,比如opacity

button{
  opacity: .99;
  transition: opacity 2s;
}
button:not(:disabled):active{
  opacity: 1;
  transition: 0s;
}

然後監聽transition的起始回撥

// 過渡開始
document.addEventListener('transitionstart', function(ev){
  ev.target.disabled = true
})
// 過渡結束
document.addEventListener('transitionend', function(ev){
  ev.target.disabled = false
})

這樣做的最大好處是,這部分禁用的邏輯是完全和業務邏輯是解耦的,可以在任意時候,任意場合下無縫接入,也不受框架和環境影響,效果如下

Kapture 2022-11-14 at 17.25.49

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

四、總結一下

以上透過 CSS 的思路實現了類似“節流”的功能,相比 JS 實現而言,實現更精簡、使用更簡單,沒有框架限制,下面一起總結一下實現要點:

  1. 函式節流是一個非常常見的最佳化方式,可以有效避免函式過於頻繁的執行
  2. CSS 的實現思路和 JS 不同,重點在於在於找到和該場景相關聯的屬性
  3. CSS 實現“節流”其實就是控制一個動畫的精準控制,假設有一個動畫控制按鈕從禁用->可點選的變化,每次點選時讓這個動畫重新執行一遍,在執行的過程中,一直處於禁用狀態,這樣就達到了“節流”的效果
  4. 還可以透過 transition 的回撥函式動態設定按鈕禁用態
  5. 這種實現的好處在於禁用邏輯和業務邏輯是完全解耦的

不過,這種實現方式還是比較有侷限的,僅限於點選行為,像很多時候,節流可能會用在滾動事件或者鍵盤事件上,像這些場景就用傳統方式實現就行了。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

歡迎關注我的公眾號:前端偵探