英文:Obaseki Nosa? ?譯文:kingrychen
github.com/OFED/translation/issues/3
有沒有想過只需按住一個按鈕幾秒鐘就能在你的 Vue 應(yīng)用中觸發(fā)一個功能敞嗡?
有沒有想過創(chuàng)建一個按鈕研乒,按下一次就可以清除單次輸入(或者持續(xù)按住可以清除所有輸入)颈墅?
想過?太好了债沮,英雄所見略同。
本文就是講解如何在按下(或者按桌苎)一個按鈕時隔披,既執(zhí)行一個函數(shù),又清除輸入鸠姨。
首先歼狼,我會講解如何使用純 JS 實(shí)現(xiàn)。而后也會創(chuàng)建一個 Vue 指令享怀。
請系好安全帶羽峰。好戲在后頭呢。
原理
要實(shí)現(xiàn)長按添瓷,用戶需要按下并按住按鈕幾秒鐘梅屉。
想通過代碼模擬這一效果,我們需要在鼠標(biāo)“點(diǎn)擊”按下按鈕時鳞贷,啟動一個計時器監(jiān)聽用戶按下的時長坯汤,如果時間超過我們期望的時長,就執(zhí)行相應(yīng)的函數(shù)搀愧。
非常簡單惰聂!然而疆偿,我們需要知道用戶何時按住按鈕。
如何實(shí)現(xiàn)
當(dāng)用戶點(diǎn)擊按鈕時搓幌,在點(diǎn)擊事件之前會觸發(fā)另外兩個事件: mousedown 和 mouseup杆故。
當(dāng)用戶按下按鈕時觸發(fā) mousedown 事件,用戶松開按鈕時調(diào)用 mouseup 事件溉愁。
我們需要做的是:
mousedown 事件觸發(fā)時处铛,啟動計時器。
一旦 mouseup 事件在預(yù)期的 2 秒前被觸發(fā)拐揭,就清除計時器撤蟆,不要執(zhí)行相應(yīng)的函數(shù)。就當(dāng)作一個普通的點(diǎn)擊事件堂污。
只要計時器在我們預(yù)設(shè)的時間內(nèi)沒有被清除家肯,即 mouseup 事件沒有被觸發(fā)——那么可以斷定用戶沒有釋放按鈕。因此盟猖,可以判定為一次長按息楔,可以執(zhí)行關(guān)聯(lián)的函數(shù)。
實(shí)踐
讓我們深入代碼扒披,完成這一功能值依。
首先,我們必須定義三件事碟案,即:
一個 變量 用于存儲計時器愿险。
一個 啟動 功能函數(shù),用于啟動計時器价说。
一個 取消 功能函數(shù),用于取消計時器鳖目。
變量
這個變量主要用來保存 setTimeout 的值领迈,以便當(dāng)鼠標(biāo) mouseup 事件觸發(fā)時我們可以取消它彻磁。
let pressTimer = null;
我們把變量值設(shè)置為 null 是為了在執(zhí)行取消操作前,檢查這個變量的值判斷當(dāng)前是否有一個正在運(yùn)行的計時器狸捅。
啟動函數(shù)
這個函數(shù)包括一個 setTimeout,它是 JavaScript 中的一個基本方法磁浇,允許在特定時間之后執(zhí)行一個函數(shù)。
注意朽褪,click 事件執(zhí)行的過程中置吓,會觸發(fā)另外兩個事件无虚。但是我們需要啟動計時器的是 mousedown 事件友题。如果只是點(diǎn)擊事件构拳,不需要啟動計時器梁棠。
// 創(chuàng)建計時器 ( 1s之后執(zhí)行函數(shù) )
let start = (e) => {
????// 如果是點(diǎn)擊事件,不啟動計時器
????if (e.type === 'click' && e.button !== 0) {
????????return;
????}
????// 在啟動一個定時器之前確保沒有正在運(yùn)行的計時器
????if (pressTimer === null) {
????????pressTimer = setTimeout(() => {
????????????// 執(zhí)行任務(wù) !!!
????????}, 1000)
????}
}
取消函數(shù)
這個函數(shù)見名知意凫海,用來取消啟動函數(shù)創(chuàng)建的 setTimeout男娄。
要取消 setTimeout ,可以使用 JavaScript 中的 clearTimeout 方法建瘫,它主要用來清除 setTimeout() 方法設(shè)置的計時器尸折。
在使用 clearTimeout 之前,需要檢查 pressTimer 變量是否為 null橄浓。如果沒有為 null亮航,意味著有一個正在運(yùn)行的計時器。因此准给,我們需要先清除它重抖,并且將 pressTimer 變量設(shè)置為 null。
let cancel = (e) => {
????// 檢查 pressTimer 的值是否為 null
????if (pressTimer !== null) {
????????clearTimeout(pressTimer)
????????pressTimer = null
????}
}
一旦 mouseup 事件觸發(fā)沦辙,這個函數(shù)就會被調(diào)用讹剔。
設(shè)置觸發(fā)器
剩下的就是將事件監(jiān)聽器添加到想要長按效果的按鈕上。
addEventListener("mousedown", start);
addEventListener("click", cancel);
以上代碼合到一起是這樣:
// 定義變量
let pressTimer = null;
// 創(chuàng)建計時器( 1秒后執(zhí)行函數(shù) )
let start = (e) => {
????if (e.type === 'click' && e.button !== 0) {
????????return;
????}
????if (pressTimer === null) {
????????pressTimer = setTimeout(() => {
????????????// 執(zhí)行任務(wù) !!!
????????}, 1000)
????}
}
// 停止計時器
let cancel = (e) => {
????// 檢查是否有正在運(yùn)行的計時器
????if ( pressTimer !== null ) {
????????clearTimeout(pressTimer);
????????pressTimer = null;
????}
}
// 選擇 id 為 longPressButton 的元素
let el = document.getElementById('longPressButton');
// 添加事件監(jiān)聽器
el.addEventListener("mousedown", start);
// 長按事件取消沈跨,取消計時器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
用 Vue 指令包裝
創(chuàng)建 Vue 指令時兔综,可以創(chuàng)建全局或局部指令,本文中涧窒,我們采用全局指令锭亏。
首先,我們必須聲明自定義指令的名稱戴已。
Vue.directive('longpress', {
})
這就注冊了一個名為 v-longpress 的全局自定義指令锅减。
接下來怔匣,我們添加帶參數(shù)的 bind 鉤子函數(shù),它允許我們引用指令綁定的元素劫狠,獲取傳遞給指令的值独泞,并標(biāo)識指令使用的組件。
Vue.directive('longpress', {
????bind: function(el, binding, vNode) {
????}
})
接下來蜒犯,我們在 bind 函數(shù)中添加長按功能的代碼荞膘。
Vue.directive('longpress', {
????bind: function(el, binding, vNode) {
????????// 定義變量
????????let pressTimer = null;
????????// 定義函數(shù)處理程序
????????// 創(chuàng)建計時器( 1秒后執(zhí)行函數(shù) )
????????let start = (e) => {
????????????if (e.type === 'click' && e.button !== 0) {
????????????????return;
????????????}
????????????if (pressTimer === null) {
????????????????pressTimer = setTimeout(() => {
????????????????????// 執(zhí)行任務(wù) !!!
????????????????}, 1000)
????????????}
????????}
????????// 取消計時器
????????let cancel = (e) => {
????????????// 檢查是否有正在運(yùn)行的計時器
????????????if ( pressTimer !== null ) {
????????????????clearTimeout(pressTimer);
????????????????pressTimer = null;
????????????}
????????}
????????// 添加事件監(jiān)聽器
????????el.addEventListener("mousedown", start);
????????// 取消計時器
????????el.addEventListener("click", cancel);
????????el.addEventListener("mouseout", cancel);
????}
})
接下來羽资,我們需要添加一個函數(shù)來運(yùn)行傳遞給 longpress 指令的方法。
Vue.directive('longpress', {
????bind: function(el, binding, vNode) {
????????// 定義變量
????????let pressTimer = null;
????????// 定義函數(shù)處理程序
????????// 創(chuàng)建計時器( 1秒后執(zhí)行函數(shù) )
????????let start = (e) => {
????????????if (e.type === 'click' && e.button !== 0) {
????????????????return;
????????????}
????????????if (pressTimer === null) {
????????????????pressTimer = setTimeout(() => {
????????????????????// 執(zhí)行函數(shù)
????????????????????handler();
????????????????}, 1000)
????????????}
????????}
????????// 停止計時器
????????let cancel = (e) => {
????????????// 檢查是否有正在運(yùn)行的計時器
????????????if ( pressTimer !== null ) {
????????????????clearTimeout(pressTimer);
????????????????pressTimer = null;
????????????}
????????}
????????// 運(yùn)行函數(shù)
????????const handler = (e) => {
????????????// 執(zhí)行傳遞給指令的方法
????????????binding.value(e)
????????}
????????// 添加事件監(jiān)聽器
????????el.addEventListener("mousedown", start);
????????// 取消計時器
????????el.addEventListener("click", cancel);
????????el.addEventListener("mouseout", cancel);
????}
})
現(xiàn)在狭郑,可以在 Vue 應(yīng)用中使用這個指令了汇在,除非使用者給指令傳入的值不是一個函數(shù)。因此亩鬼,我們需要通過警告反饋給使用者阿蝶。
為了反饋給使用者,我們在 bind 函數(shù)中添加了以下內(nèi)容:
// 確保提供的表達(dá)式是函數(shù)
if (typeof binding.value !== 'function') {
????// 獲取組件名稱
????const compName = vNode.context.name;
????// 將警告?zhèn)鬟f給控制臺
????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);
}
最后魄缚,如果這個指令也適用于觸屏設(shè)備焚廊,那會是極好的习劫。因此诽里,我們添加了 touchstart、touchend 和 touchcancel 事件監(jiān)聽器谤狡。
最終代碼如下:
Vue.directive('longpress', {
????bind: function(el, binding, vNode) {
????????// 確保提供的表達(dá)式是函數(shù)
????????if (typeof binding.value !== 'function') {
????????????// 獲取組件名稱
????????????const compName = vNode.context.name;
????????????// 將警告?zhèn)鬟f給控制臺
????????????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;
????????// 定義函數(shù)處理程序
????????// 創(chuàng)建計時器( 1秒后執(zhí)行函數(shù) )
????????let start = (e) => {
????????????if (e.type === 'click' && e.button !== 0) {
????????????????return;
????????????}
????????????if (pressTimer === null) {
????????????????pressTimer = setTimeout(() => {
????????????????????// 執(zhí)行函數(shù)
????????????????????handler();
????????????????}, 1000)
????????????}
????????}
????????// 取消計時器
????????let cancel = (e) => {
????????????// 檢查計時器是否有值
????????????if ( pressTimer !== null ) {
????????????????clearTimeout(pressTimer);
????????????????pressTimer = null;
????????????}
????????}
????????// 運(yùn)行函數(shù)
????????const handler = (e) => {
????????????// 執(zhí)行傳遞給指令的方法
????????????binding.value(e)
????????}
????????// 添加事件監(jiān)聽器
????????el.addEventListener("mousedown", start);
????????el.addEventListener("touchstart", start);
????????// 取消計時器
????????el.addEventListener("click", cancel);
????????el.addEventListener("mouseout", cancel);
????????el.addEventListener("touchend", cancel);
????????el.addEventListener("touchcancel", cancel);
????}
})
現(xiàn)在可以在 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>
. . .
如果你想知道更多關(guān)于 自定義指令焰宣、可用的 鉤子函數(shù)匕积、可以傳遞到這個鉤子函數(shù)中的 參數(shù)榜跌、函數(shù)簡寫 的信息, 參照 @vuejs 官方文檔,作者做了很好的解釋悄蕾。