有沒有想過只需按住一個按鈕幾秒鐘就能在你的 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.diective('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?afunction,?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 官方文檔,作者做了很好的解釋。
英文:Obaseki Nosa? ?譯文:kingrychen
github.com/OFED/translation/issues/3