有沒有想過只需按住一個按鈕幾秒鐘就能在你的 Vue 應用中觸發(fā)一個功能历涝?
有沒有想過創(chuàng)建一個按鈕,按下一次就可以清除單次輸入(或者持續(xù)按住可以清除所有輸入)漾唉?
想過荧库?太好了,英雄所見略同赵刑。
本文就是講解如何在按下(或者按追稚馈)一個按鈕時,既執(zhí)行一個函數(shù)般此,又清除輸入蚪战。
首先,我會講解如何使用純 JS 實現(xiàn)铐懊。而后也會創(chuàng)建一個 Vue 指令邀桑。
請系好安全帶。好戲在后頭呢科乎。
原理
要實現(xiàn)長按壁畸,用戶需要按下并按住按鈕幾秒鐘。
想通過代碼模擬這一效果喜喂,我們需要在鼠標“點擊”按下按鈕時瓤摧,啟動一個計時器監(jiān)聽用戶按下的時長竿裂,如果時間超過我們期望的時長,就執(zhí)行相應的函數(shù)照弥。
非常簡單腻异!然而,我們需要知道用戶何時按住按鈕这揣。
如何實現(xiàn)
當用戶點擊按鈕時悔常,在點擊事件之前會觸發(fā)另外兩個事件: mousedown 和 mouseup。
當用戶按下按鈕時觸發(fā) mousedown 事件给赞,用戶松開按鈕時調(diào)用 mouseup 事件机打。
我們需要做的是:
mousedown 事件觸發(fā)時,啟動計時器片迅。
一旦 mouseup 事件在預期的 2 秒前被觸發(fā)残邀,就清除計時器,不要執(zhí)行相應的函數(shù)柑蛇。就當作一個普通的點擊事件芥挣。
只要計時器在我們預設的時間內(nèi)沒有被清除,即 mouseup 事件沒有被觸發(fā)——那么可以斷定用戶沒有釋放按鈕耻台。因此空免,可以判定為一次長按,可以執(zhí)行關聯(lián)的函數(shù)盆耽。
實踐
讓我們深入代碼蹋砚,完成這一功能。
首先摄杂,我們必須定義三件事坝咐,即:
一個 變量 用于存儲計時器。
一個 啟動 功能函數(shù)匙姜,用于啟動計時器畅厢。
一個 取消 功能函數(shù),用于取消計時器氮昧。
變量
這個變量主要用來保存 setTimeout 的值,以便當鼠標 mouseup 事件觸發(fā)時我們可以取消它浦楣。
let pressTimer = null;
我們把變量值設置為 null 是為了在執(zhí)行取消操作前袖肥,檢查這個變量的值判斷當前是否有一個正在運行的計時器。
啟動函數(shù)
這個函數(shù)包括一個 setTimeout振劳,它是 JavaScript 中的一個基本方法椎组,允許在特定時間之后執(zhí)行一個函數(shù)。
注意历恐,click 事件執(zhí)行的過程中寸癌,會觸發(fā)另外兩個事件专筷。但是我們需要啟動計時器的是 mousedown 事件。如果只是點擊事件蒸苇,不需要啟動計時器磷蛹。
// 創(chuàng)建計時器 ( 1s之后執(zhí)行函數(shù) )
let?start?=?(e)?=>?{
????// 如果是點擊事件,不啟動計時器
????if?(e.type?===?'click'?&&?e.button?!==?0)?{
????????return;
????}
????// 在啟動一個定時器之前確保沒有正在運行的計時器
????if?(pressTimer?===?null)?{
????????pressTimer?=?setTimeout(()?=>?{
????????????// 執(zhí)行任務 !!!
????????},?1000)
????}
}
取消函數(shù)
這個函數(shù)見名知意溪烤,用來取消啟動函數(shù)創(chuàng)建的 setTimeout味咳。
要取消 setTimeout ,可以使用 JavaScript 中的 clearTimeout 方法檬嘀,它主要用來清除 setTimeout() 方法設置的計時器槽驶。
在使用 clearTimeout 之前,需要檢查 pressTimer 變量是否為 null鸳兽。如果沒有為 null掂铐,意味著有一個正在運行的計時器。因此揍异,我們需要先清除它全陨,并且將 pressTimer 變量設置為 null。
let?cancel?=?(e)?=>?{
????// 檢查 pressTimer 的值是否為 null
????if?(pressTimer?!==?null)?{
????????clearTimeout(pressTimer)
????????pressTimer?=?null
????}
}
一旦 mouseup 事件觸發(fā)蒿秦,這個函數(shù)就會被調(diào)用烤镐。
設置觸發(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í)行任務 !!!
????????},?1000)
????}
}
// 停止計時器
let?cancel?=?(e)?=>?{
????// 檢查是否有正在運行的計時器
????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.diective('longpress',?{
})
這就注冊了一個名為 v-longpress 的全局自定義指令侣肄。
接下來,我們添加帶參數(shù)的 bind 鉤子函數(shù)醇份,它允許我們引用指令綁定的元素稼锅,獲取傳遞給指令的值,并標識指令使用的組件僚纷。
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í)行任務 !!!
????????????????},?1000)
????????????}
????????}
????????// 取消計時器
????????let?cancel?=?(e)?=>?{
????????????// 檢查是否有正在運行的計時器
????????????if?(?pressTimer?!==?null?)?{
????????????????clearTimeout(pressTimer);
????????????????pressTimer?=?null;
????????????}
????????}
????????// 添加事件監(jiān)聽器
????????el.addEventListener("mousedown",?start);
????????// 取消計時器
????????el.addEventListener("click",?cancel);
????????el.addEventListener("mouseout",?cancel);
????}
})
接下來怖竭,我們需要添加一個函數(shù)來運行傳遞給 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)?=>?{
????????????// 檢查是否有正在運行的計時器
????????????if?(?pressTimer?!==?null?)?{
????????????????clearTimeout(pressTimer);
????????????????pressTimer?=?null;
????????????}
????????}
????????// 運行函數(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 應用中使用這個指令了,除非使用者給指令傳入的值不是一個函數(shù)哮肚。因此登夫,我們需要通過警告反饋給使用者。
為了反饋給使用者允趟,我們在 bind 函數(shù)中添加了以下內(nèi)容:
// 確保提供的表達式是函數(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);
}
最后恼策,如果這個指令也適用于觸屏設備,那會是極好的拼窥。因此戏蔑,我們添加了 touchstart、touchend 和 touchcancel 事件監(jiān)聽器鲁纠。
最終代碼如下:
Vue.directive('longpress',?{
????bind:?function(el,?binding,?vNode)?{
????????// 確保提供的表達式是函數(shù)
????????if?(typeof?binding.value?!==?'function')?{
????????????// 獲取組件名稱
????????????const?compName?=?vNode.context.name;
????????????// 將警告?zhèn)鬟f給控制臺
????????????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;
????????// 定義函數(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;
????????????}
????????}
????????// 運行函數(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>
. . .
如果你想知道更多關于 自定義指令总棵、可用的 鉤子函數(shù)、可以傳遞到這個鉤子函數(shù)中的 參數(shù)改含、函數(shù)簡寫 的信息, 參照 @vuejs 官方文檔情龄,作者做了很好的解釋。
英文:Obaseki Nosa? ?譯文:kingrychen
github.com/OFED/translation/issues/3