1. 程式人生 > >用 Vue 編寫一個長按指令

用 Vue 編寫一個長按指令

有沒有想過只需按住一個按鈕幾秒鐘就能在你的 Vue 應用中觸發一個功能?

有沒有想過建立一個按鈕,按下一次就可以清除單次輸入(或者持續按住可以清除所有輸入)?

想過?太好了,英雄所見略同。

本文就是講解如何在按下(或者按住)一個按鈕時,既執行一個函式,又清除輸入。

首先,我會講解如何使用純 JS 實現。而後也會建立一個 Vue 指令。

請繫好安全帶。好戲在後頭呢。

原理

要實現長按,使用者需要按下並按住按鈕幾秒鐘。

想通過程式碼模擬這一效果,我們需要在滑鼠“點選”按下按鈕時,啟動一個計時器監聽使用者按下的時長,如果時間超過我們期望的時長,就執行相應的函式。

非常簡單!然而,我們需要知道使用者何時按住按鈕。

如何實現

當用戶點選按鈕時,在點選事件之前會觸發另外兩個事件: mousedown 和 mouseup。

當用戶按下按鈕時觸發 mousedown 事件,使用者鬆開按鈕時呼叫 mouseup 事件。

我們需要做的是:

  1. mousedown 事件觸發時,啟動計時器。

  2. 一旦 mouseup 事件在預期的 2 秒前被觸發,就清除計時器,不要執行相應的函式。就當作一個普通的點選事件。

只要計時器在我們預設的時間內沒有被清除,即 mouseup 事件沒有被觸發——那麼可以斷定使用者沒有釋放按鈕。因此,可以判定為一次長按,可以執行關聯的函式。

實踐

讓我們深入程式碼,完成這一功能。

首先,我們必須定義三件事,即:

  1. 一個 變數 用於儲存計時器。

  2. 一個 啟動 功能函式,用於啟動計時器。

  3. 一個 取消 功能函式,用於取消計時器。

變數

這個變數主要用來儲存 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 tobe `;

            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 官方文件,作者做了很好的解釋。