使用 Vue、TS 编写一个自定义长按指令

4,271 阅读2分钟

实现用户按下超过半秒钟就执行对应的操作

思路

  • 定义一个变量存储计时器
  • 在用户按下的一瞬间启动计时器,当按下的时间超过半秒就执行对应的操作
  • 在用户松开的一瞬间取消计时器并将该变量置空,以便多次操作 伪代码如下:
let pressTimer: NodeJS.Timer | null = null;
// 创建计时器
 const start = (e: Event) => {
      if (e.type === "click") {
        return;
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          // 执行对应功能
        }, 500);
      }
    };
// 取消计时器
    const cancel = () => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer);
        pressTimer = null;
      }
    };
    
    el.addEventListener("mousedown", start);
    el.addEventListener("touchstart", start);
    el.addEventListener("click", cancel);
    el.addEventListener("mouseout", cancel);
    el.addEventListener("touchend", cancel);
    el.addEventListener("touchcancel", cancel);

自定义指令

自定义指令需要用到钩子函数,我在长按指令中用到的是 bind 钩子函数,结果出现 bug ,获取到的永远是第一次长按的元素信息,改用 update 解决也符合需求。

钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。
  • inserted:被绑定元素插入父节点时调用。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。

钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象。 name:指令名,value:指令的绑定值
  • vnode:编译生成的虚拟节点

伪代码如下:

import {DirectiveOptions} from "vue";

const directive: DirectiveOptions = {

  update: function (el, binding, vNode) {
    // 确保提供的表达式是函数
    if (typeof binding.value !== "function") {
      // 获取组件名称
      const compName = vNode.context;
      // 将警告传递给控制台
      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);
    }
    const handler = (e: Event) => {
    // 执行功能
      binding.value(e);
    };
  }
};
export default directive;

附上所有代码:

// longpress.ts
import {DirectiveOptions} from "vue";

const directive: DirectiveOptions = {
  update: function (el, binding, vNode) {

    if (typeof binding.value !== "function") {
      const compName = vNode.context;
      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: NodeJS.Timer | null = null;
    const handler = (e: Event) => {
      binding.value(e);
    };
    const start = (e: Event) => {
      if (e.type === "click") {
        return;
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          handler(e);
        }, 500);
      }
    };

    const cancel = () => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer);
        pressTimer = null;
      }
    };
    document.oncontextmenu = function (event) {
      event.preventDefault();
      return false;
    };
    el.addEventListener('contextmenu',function (e){
      e.preventDefault()
    },false)
    el.addEventListener("mousedown", start);
    el.addEventListener("touchstart", start);
    el.addEventListener("click", cancel);
    el.addEventListener("mouseout", cancel);
    el.addEventListener("touchend", cancel);
    el.addEventListener("touchcancel", cancel);
  }
};
export default directive;

使用指令:

// Tag.vue
<template>
  <div v-longpress="showTest">
 	test 
   </div>
</template>

<script lang="ts">
import Vue from "vue";
import {Component} from "vue-property-decorator";
import longpress from "@/lib/longpress";
@Component({
  directives: {longpress}
})
export default class Tags extends Vue {
  showTest() {}
</script>

参考文献:自定义指令