vue2中常用的自定义指令

166 阅读8分钟

vue2中常用的自定义指令,可以用于扩展 Vue 的功能,处理一些特定的 DOM 操作或交互需求,如有需要请点击进行查看自定义指令参数详解

存放自定义指令文件目录

以下为全局自定义指令封装目录,在src目录下建一个专门存储指令的文件夹directivedirective文件夹下新建modules模块文件夹,再建一个和modules平级的index.js文件。目录如下:

// 指令文件目录
--src
|---directive
|-----modules
|-------focus.js // 自动聚焦指令
|-------highlight.js // 文本高亮指令
|-----index.js
|---main.js // vue项目的入口文件

directive下的index.js文件内容

// 注册全局指令
import auth from "./modules/auth"
import focus from "./modules/focus"
const directives = {
  auth,
  focus,
}

export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}

main.js文件引入注册的指令

import Vue from "vue";
import App from "./App";
import router from "./router";
import store from "./store";
// 引入指令
import Directive from "@/directive";
Vue.use(Directive)

new Vue({
  el: '#app',
  router,
  store,
  components: {App},
  template: '<App/>'
});

1. v-focus:自动聚焦指令

用途: 需要页面加载或特定交互后自动聚焦到输入框的场景,比如:登录页,表单弹窗,搜索框(页面刷新后直接聚焦搜索框)案例代码:

// 焦点指令
const focus = {
  inserted(el) {
    // el:指令所绑定的元素,可以用来直接操作DOM
    // 此处使用的是el-input组件,所以使用el-input__inner来获取焦点,el-input__inner元素是el-input的子元素
    const input = el.querySelector('.el-input__inner');
    if (input) {
      input.focus();
    }
  }
}

export default focus

使用:

<el-input v-focus v-model="focusValue" placeholder="鼠标自动聚焦"></el-input>

详解:

  • inserted 钩子在元素插入到 DOM 时触发,调用 input.focus() 使元素自动聚焦。

注意事项:

  • 如果是其他组件库,查看el下标签input元素的类名替换.el-input__inner即可。

2. v-copy:复制指令

用途: 需要一键复制文本内容的场景,比如:订单号复制,分享链接,代码片段等案例代码:

const copy = {
  bind: function (el, binding) {
    el.addEventListener('click', async () => {
      console.log('复制文本:', binding.value,navigator.clipboard)
      const text = binding.value?.text || binding.value; // 兼容对象或直接文本

      try {
        // 优先使用现代 Clipboard API
        if (navigator.clipboard) {
          await navigator.clipboard.writeText(text);
        } else {
          // 兼容旧版 execCommand
          const textarea = document.createElement('textarea');
          textarea.value = text;
          textarea.style.position = 'fixed'; // 避免滚动到底部
          document.body.appendChild(textarea);
          textarea.select();
          document.execCommand('copy');
          document.body.removeChild(textarea);
        }

        // 成功回调(可选)可替换成成功弹出提示框
        binding.value?.success?.() || console.log('复制成功:', text);
      } catch (err) {
        // 失败回调(可选)
        binding.value?.error?.(err) || console.error('复制失败:', err);
      }
    });

    // 添加光标样式提示
    el.style.cursor = 'pointer';
  }
}

export default copy

使用:

示例 1:直接复制固定文本

<button v-copy="'https://example.com'">复制链接</button>

示例 2:复制动态数据

<template>
  <div>
    <input v-model="copyText" />
    <button v-copy="copyText">复制输入框内容</button>
  </div>
</template>

<script>
export default {
  data() {
    return { copyText: '默认文本' };
  }
}
</script>

详解:

  • 现代浏览器:优先使用 navigator.clipboard.writeText(),需要 HTTPS 环境。
  • 旧版浏览器:通过 document.execCommand('copy') 兼容:
    1. 创建隐藏的 textarea
    2. 选中文本并执行复制
    3. 移除临时元素

3. v-auth:权限控制指令

用途: 根据用户权限动态显示或禁用按钮,比如:仅管理员可见的“删除”或“编辑”按钮,仅管理员可见的“删除”或“编辑”按钮。 案例代码:


// auth.js
/**
 * 权限指令模块,用于根据用户权限动态控制DOM元素的显示与隐藏。
 * 该模块通过检查用户的权限列表来决定是否移除或替换指定的DOM元素。
 */

import store from '@/store'; // 导入获取本地存储权限的工具函数

// 权限指令
const auth = {
  inserted(el, { value }) {
    if (!value) {
      return;
    }
    //  获取用户权限数组(这里以vuex为案例,需根据项目实际情况调整)
    const permissions = store.getters.authList || [];
    if (!(permissions.includes(value) || permissions.includes("*:*:*"))) {
      // 如果没有权限且权限值包含"edit",则进行编辑权限单独处理
      if (value.includes("edit")) {
        handleEditPermission(el);
      } else {
        // 如果没有权限且权限值不包含"edit",则移除元素
        el.remove();
      }
    }
  }
}

/**
 * 处理编辑权限的特殊逻辑。
 * 将原元素替换为一个新创建的span元素,并保留原文本内容。
 * @param {HTMLElement} el - 需要处理的DOM元素
 */
function handleEditPermission(el) {
  const parentNode = el.parentNode;
  if (parentNode) {
    const newNode = document.createElement("span"); // 创建新的span元素
    newNode.textContent = el.textContent; // 设置新元素的文本内容为原元素的文本内容
    parentNode.appendChild(newNode); // 将新元素插入到父节点中
    el.remove(); // 移除原元素
  }
}

export default auth; // 导出权限指令对象

使用:

<button v-auth="'ad:sys:add'">新增</button>
<!--   此编辑按钮是模拟在点击表格中的单元格,点击系统名称,打开编辑信息,如果没有权限是要让此点击事件失效,故在权限指令处对编辑权限进行了单独处理 -->
<el-table-column label="系统名称" width="180">
   <template slot-scope="scope">
     <div v-auth="'ad:sys:edit'"  @click="handleClick(row)">{{ scope.row.name }}</div>
   </template>
 </el-table-column>

详解:

  • inserted 钩子在元素插入时触发,根据权限值决定是否隐藏或者移除元素。

4. v-longpress:长按指令

用途: 常用于移动端或需要长按交互的场景(如游戏、编辑器等)。 案例代码:

const longpress = {
  bind: function(el, binding) {
    // 定义长按触发的回调函数
    const handler = binding.value;

    // 定义计时器变量
    let pressTimer = null;

    // 开始计时函数
    const start = (e) => {
      // 阻止默认行为(如移动端长按菜单)
      e.preventDefault();

      // 确保只有一个计时器运行
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          // 执行长按回调,并传递原生事件对象
          handler(e);
        }, binding.arg || 800); // 默认800ms,可通过指令参数修改
      }
    };

    // 取消计时函数
    const cancel = () => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer);
        pressTimer = null;
      }
    };

    // 存储事件处理函数,便于解绑时使用
    el._longPressHandlers = { start, cancel };

    // 绑定事件监听
    // 桌面端:mousedown(按下)和 mouseup(松开)
    // 移动端:touchstart(触摸开始)和 touchend(触摸结束)
    // 额外监听 click 和 touchcancel 确保意外操作时取消计时器。
    el.addEventListener('mousedown', start);
    el.addEventListener('touchstart', start);
    el.addEventListener('click', cancel);
    el.addEventListener('mouseup', cancel);
    el.addEventListener('touchend', cancel);
    el.addEventListener('touchcancel', cancel);
  },

  unbind: function(el) {
    // 解绑所有事件监听
    const { start, cancel } = el._longPressHandlers || {};
    if (start) {
      el.removeEventListener('mousedown', start);
      el.removeEventListener('touchstart', start);
    }
    if (cancel) {
      el.removeEventListener('click', cancel);
      el.removeEventListener('mouseup', cancel);
      el.removeEventListener('touchend', cancel);
      el.removeEventListener('touchcancel', cancel);
    }
  }
};
export default longpress;

使用: 示例 1:基础用法,默认时长

<template>
  <button v-longpress="handleLongPress">长按我</button>
</template>

<script>
export default {
  methods: {
    handleLongPress() {
      console.log('长按成功!');
    }
  }
}
</script>

示例 2:自定义时长时长

<template>
 <button v-longpress:1500="handleLongPress">1.5秒后触发</button>
</template>

<script>
export default {
  methods: {
    handleLongPress() {
      console.log('长按成功!');
    }
  }
}
</script>

详解:

  1. 监听按下事件:通过 mousedown(桌面端)和 touchstart(移动端)触发长按计时。

  2. 设置计时器:按下后启动计时器,若在指定时间内未松开,则触发长按回调。

  3. 取消机制:通过 mouseuptouchend等事件取消计时器,避免误触发。

  4. 清理资源:在指令解绑时移除事件监听,防止内存泄漏。

5. v-draggable:元素拖拽效果指令

用途: 需要用户手动调整元素位置的场景,比如:网页客服icon显示位置。 案例代码:

const draggable = {
  inserted: function (el, binding) {
    // 初始化变量
    let isDragging = false; // 标记是否正在拖拽
    let initialX = 0;       // 拖拽开始时的初始X坐标
    let initialY = 0;       // 拖拽开始时的初始Y坐标
    let currentX = 0;       // 当前的X坐标
    let currentY = 0;       // 当前的Y坐标

    // 处理桌面端事件
    const onMouseDown = (e) => {
      isDragging = true;
      initialX = e.clientX - currentX;
      initialY = e.clientY - currentY;
      document.addEventListener('mousemove', onMouseMove);
      document.addEventListener('mouseup', onMouseUp);
      e.preventDefault(); // 阻止默认选中文本行为
    };
    // PC端移动事件
    const onMouseMove = (e) => {
      if (!isDragging) return;
      currentX = e.clientX - initialX;
      currentY = e.clientY - initialY;
      updatePosition();
    };

    // 停止拖拽
    const endDrag = () => {
      isDragging = false;
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);
    };

    // 更新元素位置
    const updatePosition = () => {
      // 使用 transform 优化性能(避免重排)
      //  “移动重排”通常指的是在重新布置元素时动态改变元素的位置或布局,这种操作会影响整个页面或部分页面的布局,导致浏览器重新计算和渲染布局。
      el.style.transform = `translate(${currentX}px, ${currentY}px)`;
      el.style.zIndex = 9999;
      // 若需保留原始定位,可使用以下方式:
      // el.style.left = currentX + 'px';
      // el.style.top = currentY + 'px';
    };
    // 鼠标释放事件
    const onMouseUp = (e) => {
      endDrag();
    };
    // 绑定事件
    el.addEventListener('mousedown', onMouseDown);

    // 清理函数
    el._cleanupDraggable = () => {
      el.removeEventListener('mousedown', onMouseDown);
      endDrag();
    };

    // 初始化元素样式(确保可拖拽)
    el.style.position = 'relative'; // 或 'absolute'/'fixed'
    el.style.userSelect = 'none';    // 阻止拖拽时选中文本
  },

  unbind: function (el) {
    // 解绑所有事件
    el._cleanupDraggable?.();
  }
}
export default draggable

使用:

<template>
  <div v-draggable class="box">拖拽我</div>
</template>

<style>
.box {
  width: 100px;
  height: 100px;
  background: #42b983;
  cursor: move;
}
</style>

详解:

  1. 事件监听:通过 mousedown)触发拖拽。
  2. 坐标计算:记录初始点击位置与元素位置的偏移量。
  3. 动态更新位置:在 mousemove 事件中实时更新元素位置。
  4. 释放资源:在 mouseup 事件中移除移动事件监听。
  5. 边界控制(可选):限制元素在父容器或可视区域内移动。

6. v-debounce:防抖指令

用途: 防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次,比如:输入事件,提交事件等。 案例代码:

const debounce = {
  // 防抖指令:v-debounce
  bind(el, binding) {
    // 解析参数:格式为 "事件类型.延迟时间",如 "input.300"
    const [event, delay] = binding.arg ?
      binding.arg.split('.') :
      ['input', 500]; // 默认事件 input,延迟 500ms

    // 防抖时间转为数字
    const debounceTime = parseInt(delay) || 500;

    // 保存原始事件处理函数
    const handler = binding.value;

    // 防抖函数容器
    let debounceTimer = null;

    // 创建防抖后的处理函数
    const debouncedHandler = (e) => {
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => {
        handler(e); // 传递事件对象 e 给用户函数
      }, debounceTime);
    };

    // 绑定事件监听
    el.addEventListener(event, debouncedHandler);

    // 在元素上保存引用,用于解绑
    el._debounce = {event, handler: debouncedHandler};
  },

  unbind(el) {
    // 移除事件监听
    if (el._debounce) {
      const {event, handler} = el._debounce;
      el.removeEventListener(event, handler);
      el._debounce = null;
    }
  }
}

export default debounce;

使用: 示例 1:基础用法,输入框防抖

<<template>
  <input 
    v-debounce:input.300="handleInput" 
    placeholder="输入内容后 300ms 触发"
  />
</template>

<script>
export default {
  methods: {
    handleInput(e) {
      console.log('防抖后的值:', e.target.value);
    }
  }
}
</script>

示例 2: 按钮点击防抖

<template>
  <button v-debounce:click.1000="handleClick">点击后 1 秒内只能触发一次</button>
  <button v-debounce:click.1000="()=>handleClickParam(params))">携带参数的函数指令调用</button>
</template>

<script>
export default {
  data(){
	return{
		params:"这是参数"
	}
   }
  methods: {
    handleClick() {
      console.log('按钮点击生效!');
    },
    handleClickParam(value) {
      console.log('按钮点击生效!',value);
    },
  }
}
</script>

详解:

  1. 提取指令参数:binding.arg 获取指令参数(如 input.300)
  2. 定义计时器:debounceTimer 用于存储 setTimeout 的引用
  3. 清除旧计时器:每次事件触发时,先清除之前的计时器(避免重复执行)
  4. 设置新计时器:延迟 debounceTime 后执行用户回调函数 handler(e)
  5. 传递事件对象:将原生事件 e 传递给用户函数,便于获取事件目标(如 e.target.value)