原文連結:Building a long press directive in Vue
譯者:OFED
使用 Vue 編寫一個長按指令
有沒有想過只需按住一個按鈕幾秒鐘就能在你的 Vue 應用中觸發一個功能?
有沒有想過建立一個按鈕,按下一次就可以清除單次輸入(或者持續按住可以清除所有輸入)?
想過?太好了,英雄所見略同。
本文就是講解如何在按下(或者按住)一個按鈕時,既執行一個函式,又清除輸入。
首先,我會講解如何使用純 JS 實現。而後也會建立一個 Vue 指令。
請繫好安全帶。好戲在後頭呢。
原理
要實現長按,使用者需要按下並按住按鈕幾秒鐘。
想通過程式碼模擬這一效果,我們需要在滑鼠“點選”按下按鈕時,啟動一個計時器監聽使用者按下的時長,如果時間超過我們期望的時長,就執行相應的函式。
非常簡單!然而,我們需要知道使用者何時按住按鈕。
如何實現
當使用者點選按鈕時,在點選事件之前會觸發另外兩個事件: mousedown 和 mouseup。
當使用者按下按鈕時觸發 mousedown
事件,使用者鬆開按鈕時呼叫 mouseup
事件。
我們需要做的是:
mousedown
事件觸發時,啟動計時器。- 一旦
mouseup
事件在預期的 2 秒前被觸發,就清除計時器,不要執行相應的函式。就當作一個普通的點選事件。
只要計時器在我們預設的時間內沒有被清除,即 mouseup
事件沒有被觸發——那麼可以斷定使用者沒有釋放按鈕。因此,可以判定為一次長按,可以執行關聯的函式。
實踐
讓我們深入程式碼,完成這一功能。
首先,我們必須定義三件事,即:
- 一個 變數 用於儲存計時器。
- 一個 啟動 功能函式,用於啟動計時器。
- 一個 取消 功能函式,用於取消計時器。
變數
這個變數主要用來儲存 setTimeout
的值,以便當滑鼠 mouseup
事件觸發時我們可以取消它。
let pressTimer = null;
複製程式碼
我們把變數值設定為 null
是為了在執行取消操作前,檢查這個變數的值判斷當前是否有一個正在執行的計時器。
啟動函式
這個函式包括一個 setTimeout,它是 JavaScript 中的一個基本方法,允許在特定時間之後執行一個函式。
注意,click
事件執行的過程中,會觸發另外兩個事件。但是我們需要啟動計時器的是 mousedown
事件。如果只是點選事件,不需要啟動計時器。
// 建立計時器 ( 1s之後執行函式 )
let start = (e) => {
// 如果是點選事件,不啟動計時器
if (e.type === 'click' && e.button !== 0) {
return;
}
// 在啟動一個定時器之前確保沒有正在執行的計時器
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 執行任務 !!!
}, 1000)
}
}
複製程式碼
取消函式
這個函式見名知意,用來取消啟動函式建立的 setTimeout
。
要取消 setTimeout
,可以使用 JavaScript 中的 clearTimeout 方法,它主要用來清除 setTimeout() 方法設定的計時器。
在使用 clearTimeout
之前,需要檢查 pressTimer 變數是否為 null
。如果沒有為 null
,意味著有一個正在執行的計時器。因此,我們需要先清除它,並且將 pressTimer 變數設定為 null。
let cancel = (e) => {
// 檢查 pressTimer 的值是否為 null
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
複製程式碼
一旦 mouseup 事件觸發,這個函式就會被呼叫。
設定觸發器
剩下的就是將事件監聽器新增到想要長按效果的按鈕上。
addEventListener("mousedown", start);
addEventListener("click", cancel);
複製程式碼
以上程式碼合到一起是這樣:
// 定義變數
let pressTimer = null;
// 建立計時器( 1秒後執行函式 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 執行任務 !!!
}, 1000)
}
}
// 停止計時器
let cancel = (e) => {
// 檢查是否有正在執行的計時器
if ( pressTimer !== null ) {
clearTimeout(pressTimer);
pressTimer = null;
}
}
// 選擇 id 為 longPressButton 的元素
let el = document.getElementById('longPressButton');
// 新增事件監聽器
el.addEventListener("mousedown", start);
// 長按事件取消,取消計時器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
複製程式碼
用 Vue 指令包裝
建立 Vue 指令時,可以建立全域性或區域性指令,本文中,我們採用全域性指令。
首先,我們必須宣告自定義指令的名稱。
Vue.directive('longpress', {
})
複製程式碼
這就註冊了一個名為 v-longpress 的全域性自定義指令。
接下來,我們新增帶引數的 bind 鉤子函式,它允許我們引用指令繫結的元素,獲取傳遞給指令的值,並標識指令使用的元件。
Vue.directive('longpress', {
bind: function(el, binding, vNode) {
}
})
複製程式碼
接下來,我們在 bind 函式中新增長按功能的程式碼。
Vue.directive('longpress', {
bind: function(el, binding, vNode) {
// 定義變數
let pressTimer = null;
// 定義函式處理程式
// 建立計時器( 1秒後執行函式 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 執行任務 !!!
}, 1000)
}
}
// 取消計時器
let cancel = (e) => {
// 檢查是否有正在執行的計時器
if ( pressTimer !== null ) {
clearTimeout(pressTimer);
pressTimer = null;
}
}
// 新增事件監聽器
el.addEventListener("mousedown", start);
// 取消計時器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
}
})
複製程式碼
接下來,我們需要新增一個函式來執行傳遞給 longpress 指令的方法。
Vue.directive('longpress', {
bind: function(el, binding, vNode) {
// 定義變數
let pressTimer = null;
// 定義函式處理程式
// 建立計時器( 1秒後執行函式 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 執行函式
handler();
}, 1000)
}
}
// 停止計時器
let cancel = (e) => {
// 檢查是否有正在執行的計時器
if ( pressTimer !== null ) {
clearTimeout(pressTimer);
pressTimer = null;
}
}
// 執行函式
const handler = (e) => {
// 執行傳遞給指令的方法
binding.value(e)
}
// 新增事件監聽器
el.addEventListener("mousedown", start);
// 取消計時器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
}
})
複製程式碼
現在,可以在 Vue 應用中使用這個指令了,除非使用者給指令傳入的值不是一個函式。因此,我們需要通過警告反饋給使用者。
為了反饋給使用者,我們在 bind 函式中新增了以下內容:
// 確保提供的表示式是函式
if (typeof binding.value !== 'function') {
// 獲取元件名稱
const compName = vNode.context.name;
// 將警告傳遞給控制檯
let warn = `[longpress:] provided expression '${binding.expression}' is not a function, but has to be `;
if (compName) { warn += `Found in component '${compName}' ` }
console.warn(warn);
}
複製程式碼
最後,如果這個指令也適用於觸屏裝置,那會是極好的。因此,我們新增了 touchstart、touchend 和 touchcancel 事件監聽器。
最終程式碼如下:
Vue.directive('longpress', {
bind: function(el, binding, vNode) {
// 確保提供的表示式是函式
if (typeof binding.value !== 'function') {
// 獲取元件名稱
const compName = vNode.context.name;
// 將警告傳遞給控制檯
let warn = `[longpress:] provided expression '${binding.expression}' is not a function, but has to be `;
if (compName) { warn += `Found in component '${compName}' `}
console.warn(warn);
}
// 定義變數
let pressTimer = null;
// 定義函式處理程式
// 建立計時器( 1秒後執行函式 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 執行函式
handler();
}, 1000)
}
}
// 取消計時器
let cancel = (e) => {
// 檢查計時器是否有值
if ( pressTimer !== null ) {
clearTimeout(pressTimer);
pressTimer = null;
}
}
// 執行函式
const handler = (e) => {
// 執行傳遞給指令的方法
binding.value(e)
}
// 新增事件監聽器
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
// 取消計時器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
}
})
複製程式碼
現在可以在 Vue 元件裡使用了:
<template>
<div>
<button v-longpress="incrementPlusTen" @click="incrementPlusOne">{{value}}</button>
</div>
</template>
<script>
export default {
data() {
return {
value: 10
}
},
methods: {
// 增加1
incrementPlusOne() {
this.value++
},
// 增加10
incrementPlusTen() {
this.value += 10
}
}
}
</script>
複製程式碼
如果你想知道更多關於 自定義指令、可用的 鉤子函式、可以傳遞到這個鉤子函式中的 引數、函式簡寫 的資訊, 參照 @vuejs 官方文件,作者做了很好的解釋。
收工,乾杯!