双手一摊 發表於 2026-4-7 10:45:00

vue3这些常见指令你封装了吗

<h1 data-id="heading-0">🧑‍💻 写在开头</h1>
<p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<h2 data-id="heading-0">vue3这些常见指令你封装了吗</h2>
<h3 data-id="heading-1">👉指令搭建</h3>
<p>vue3之中会有一些常见的指令操作,接下来我们就写一下,之前我们写了权限按钮,其实是类似的</p>
<p>指令的最主要文件如下,我们主要是主模块之中使用,其他的模块之中分割写好方法即可</p>
<h4 data-id="heading-2">指令主要文件</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">src\utils\directive\index.ts

import type { App, Directive } from 'vue'
const directives={};
// 导出插件对象
export const registerDirectives = {
install(app: App) {
    Object.keys(directives).forEach((key) =&gt; {
      app.directive(key, directives)
    })
}
}</pre>
</div>
<h4 data-id="heading-3">指令使用</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 指令使用
import {registerDirectives} from '@/utils/directive'// 导入全局指令
app.use(registerDirectives);//全局指令注册</pre>
</div>
<h3 data-id="heading-4">👉指令编写</h3>
<h4 data-id="heading-5">复制指令</h4>
<h5 data-id="heading-6">指令编写</h5>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">import type { Directive, App } from 'vue'

// 扩展 HTMLElement 接口
declare global {
interface HTMLElement {
    copyData?: string
}
}

// 定义指令值的类型
interface CopyBinding {
value: string
}

// 复制指令配置
const copy: Directive&lt;HTMLElement, string&gt; = {
mounted(el: HTMLElement, binding: CopyBinding) {
    // 保存要复制的值
    el.copyData = binding.value
    // 添加点击事件监听
    el.addEventListener('click', handleClick)
},
updated(el: HTMLElement, binding: CopyBinding) {
    // 更新要复制的值
    el.copyData = binding.value
},
beforeUnmount(el: HTMLElement) {
    // 移除事件监听
    el.removeEventListener('click', handleClick)
}
}

// 处理复制功能
const handleClick = async (event: Event) =&gt; {
const el = event.currentTarget as HTMLElement
if (!el.copyData) return

try {
    // 使用现代的 Clipboard API
    await navigator.clipboard.writeText(el.copyData)
    // 可以在这里添加成功提示
    console.log('复制成功')
} catch (err) {
    // 降级方案:使用传统方法
    const input = document.createElement('input')
    input.value = el.copyData
    document.body.appendChild(input)
    input.select()
    try {
      document.execCommand('Copy')
      console.log('复制成功')
    } catch (err) {
      console.error('复制失败:', err)
    }
    document.body.removeChild(input)
}
}
// 导出指令对象
export { copy }</pre>
</div>
<h5 data-id="heading-7">引入指令</h5>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 复制指令
import {copy} from './modules/copy'

// 定义所有指令
const directives: Record&lt;string, Directive&gt; = {

// 复制指令
copy,
}</pre>
</div>
<h5 data-id="heading-8">使用指令</h5>
<p>接下来演示一下在项目之中进行使用指令</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;template&gt;
&lt;div class="flex gap-3"&gt;
    &lt;input
      class="flex-1 px-4 py-2 bg-gray-50 border-0 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200"
      placeholder="请输入要复制的内容"
      type="text"
      v-model="data"
    &gt;
    &lt;el-button
      class="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-200"
      v-copy="data"
    &gt;
      复制
    &lt;/el-button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
const data = ref('我是被复制的内容 🍒 🍉 🍊')
&lt;/script&gt;</pre>
</div>
<h4 data-id="heading-9">水印指令</h4>
<p>接下来写一个水印指令,我们设置的是采取canvas实现的水印效果,接下来我们就编写一下</p>
<h5 data-id="heading-10">引入指令</h5>
<p>接下来我们就在这里编写水印</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">src\utils\directive\modules\watermark .ts</pre>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 水印指令
import {watermark} from './modules/watermark'

// 定义所有指令
const directives: Record&lt;string, Directive&gt; = {
// 水印指令
watermark,
}</pre>
</div>
<h5 data-id="heading-11">指令编写</h5>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// modules/watermark.ts

export interface WatermarkConfig {
text?: string
color?: string
fontSize?: number
fontFamily?: string
width?: number
height?: number
rotate?: number
zIndex?: number
}

interface HTMLElementWithWatermark extends HTMLElement {
_watermarkElement?: HTMLDivElement
}

const defaultConfig: Required&lt;WatermarkConfig&gt; = {
text: 'Watermark',
color: 'rgba(0, 0, 0, 0.15)',
fontSize: 16,
fontFamily: 'Arial',
width: 200,
height: 200,
rotate: -20,
zIndex: 9999
}

const createWatermark = (config: WatermarkConfig): string =&gt; {
const finalConfig = { ...defaultConfig, ...config }

const canvas = document.createElement('canvas')
canvas.width = finalConfig.width
canvas.height = finalConfig.height
const ctx = canvas.getContext('2d')!

// 设置画布样式
ctx.rotate((finalConfig.rotate * Math.PI) / 180)
ctx.font = `${finalConfig.fontSize}px ${finalConfig.fontFamily}`
ctx.fillStyle = finalConfig.color
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'

// 绘制水印文本
ctx.fillText(finalConfig.text, finalConfig.width / 2, finalConfig.height / 2)

return canvas.toDataURL()
}

const watermark = {
mounted(el: HTMLElementWithWatermark, binding: { value: WatermarkConfig }) {
    const config = binding.value || {}
    const dataURL = createWatermark(config)

    // 创建水印层
    const watermarkDiv = document.createElement('div')
    watermarkDiv.style.position = 'absolute'
    watermarkDiv.style.top = '0'
    watermarkDiv.style.left = '0'
    watermarkDiv.style.width = '100%'
    watermarkDiv.style.height = '100%'
    watermarkDiv.style.pointerEvents = 'none'
    watermarkDiv.style.backgroundImage = `url(${dataURL})`
    watermarkDiv.style.backgroundRepeat = 'repeat'
    watermarkDiv.style.zIndex = String(config.zIndex || defaultConfig.zIndex)

    // 设置父元素为相对定位
    el.style.position = 'relative'
    // 添加水印层
    el.appendChild(watermarkDiv)

    // 保存水印元素引用
    el._watermarkElement = watermarkDiv
},

updated(el: HTMLElementWithWatermark, binding: { value: WatermarkConfig; oldValue: WatermarkConfig }) {
    // 如果配置发生变化,重新渲染水印
    if (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue)) {
      // 移除旧水印
      if (el._watermarkElement) {
      el.removeChild(el._watermarkElement)
      }
      // 创建新水印
      watermark.mounted(el, binding)
    }
},

unmounted(el: HTMLElementWithWatermark) {
    // 组件卸载时移除水印
    if (el._watermarkElement) {
      el.removeChild(el._watermarkElement)
      delete el._watermarkElement
    }
}
}
export { watermark }
export default watermark;</pre>
</div>
<h5 data-id="heading-12">指令使用</h5>
<p>这个时候使用我们的指令,可以看到我们的效果</p>
<div>&nbsp;
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;template&gt;
&lt;div class="flex gap-3 content" v-watermark="watermarkConfig"&gt;
    &lt;h3 class="text-lg font-semibold mb-4 text-gray-800"&gt;水印指令&lt;/h3&gt;
    &lt;input
      class="flex-1 px-4 py-2border-0 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200"
      placeholder="请输入要复制的内容"
      type="text"
      v-model="data"
    &gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref,computed } from 'vue'
// 将原来的 compute 方法改为计算属性
const watermarkText = computed(() =&gt; data.value)

const data = ref('水印内容🍒 🍉 🍊')

// 然后在 watermarkConfig 中使用这个计算属性
const watermarkConfig = computed(() =&gt; ({
text: watermarkText.value,
color: 'rgba(0, 0, 0, 0.15)',
fontSize: 16,
fontFamily: 'Arial',
width: 200,
height: 200,
rotate: -20,
zIndex: 9999,
}))
&lt;/script&gt;
&lt;style scoped&gt;
.content {
position: relative;
width: 100%;
height: 100%;
background: #fff;
}
&lt;/style&gt;</pre>
</div>
<h4 data-id="heading-13">拖拽指令</h4>
<h5 data-id="heading-14">指令编写</h5>
</div>
<div class="code-block-extension-header">
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">src\utils\directive\modules\draggable.ts</pre>
</div>
<p>指令内容,这里需要注意一个部分,指令的位置是相对于我们父元素位置,而不是相对于我们视口的位置</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 记录初始位置
const rect = el.getBoundingClientRect()
dragData.initialLeft = rect.left
dragData.initialTop = rect.top

=&gt;更改为

// 获取当前位置,如果没有设置则默认为0
dragData.initialLeft = parseInt(el.style.left) || 0
dragData.initialTop = parseInt(el.style.top) || 0</pre>
</div>
<p>完整修改以后我们的版本如下</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">import type { Directive, DirectiveBinding } from 'vue'

interface DraggableElement extends HTMLElement {
_dragData?: {
    isDragging: boolean
    startX: number
    startY: number
    initialLeft: number
    initialTop: number
    initialPosition: string
    zIndex: string
}
_cleanup?: () =&gt; void// 添加这一行
}


const draggable: Directive&lt;DraggableElement, boolean&gt; = {
mounted(el: DraggableElement, binding: DirectiveBinding&lt;boolean&gt;) {
    if (binding.value === false) return

    const dragData = {
      isDragging: false,
      startX: 0,
      startY: 0,
      initialLeft: 0,
      initialTop: 0,
      initialPosition: '',
      zIndex: ''
    }
    el._dragData = dragData

    // 设置初始样式
    el.style.cursor = 'move'
    el.style.position = el.style.position || 'absolute'

    const handleMouseDown = (e: MouseEvent) =&gt; {
      dragData.isDragging = true
      dragData.startX = e.clientX
      dragData.startY = e.clientY
      dragData.initialPosition = el.style.position
      dragData.zIndex = el.style.zIndex
      
      // 获取当前位置,如果没有设置则默认为0
      dragData.initialLeft = parseInt(el.style.left) || 0
      dragData.initialTop = parseInt(el.style.top) || 0

      // 提高层级
      el.style.zIndex = '9999'
      
      // 添加移动时的样式
      el.style.transition = 'none'
      el.style.userSelect = 'none'
    }

    const handleMouseMove = (e: MouseEvent) =&gt; {
      if (!dragData.isDragging) return

      const deltaX = e.clientX - dragData.startX
      const deltaY = e.clientY - dragData.startY

      el.style.left = `${dragData.initialLeft + deltaX}px`
      el.style.top = `${dragData.initialTop + deltaY}px`
    }

    const handleMouseUp = () =&gt; {
      if (!dragData.isDragging) return
      
      dragData.isDragging = false
      
      // 恢复样式
      el.style.zIndex = dragData.zIndex
      el.style.userSelect = ''
      el.style.transition = ''
    }

    // 添加事件监听
    el.addEventListener('mousedown', handleMouseDown)
    document.addEventListener('mousemove', handleMouseMove)
    document.addEventListener('mouseup', handleMouseUp)

    // 保存清理函数
    el._cleanup = () =&gt; {
      el.removeEventListener('mousedown', handleMouseDown)
      document.removeEventListener('mousemove', handleMouseMove)
      document.removeEventListener('mouseup', handleMouseUp)
    }
},

unmounted(el: DraggableElement) {
    // 清理事件监听
    if (el._cleanup) {
      el._cleanup()
    }
    delete el._dragData
},

updated(el: DraggableElement, binding: DirectiveBinding&lt;boolean&gt;) {
    // 如果指令值改变,更新状态
    if (binding.value === false &amp;&amp; el._dragData) {
      el.style.cursor = ''
    } else if (binding.value === true) {
      el.style.cursor = 'move'
    }
}
}

export {draggable}</pre>
</div>
<h5 data-id="heading-15">指令使用</h5>
<p>我们在指令之中进行使用,效果ok</p>
<div>&nbsp;
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;template&gt;
&lt;div class="relative"&gt;
    &lt;div v-draggable class="draggable-box"&gt;
      可拖拽的内容
    &lt;/div&gt;
    &lt;!-- 也可以动态控制是否可拖拽 --&gt;
    &lt;div v-draggable="isDraggable" class="draggable-box"&gt;
      条件拖拽的内容
    &lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script setup lang="ts"&gt;
import { ref } from 'vue'
const isDraggable = ref(true)
&lt;/script&gt;
&lt;style&gt;
.draggable-box {
width: 200px;
height: 200px;
background-color: #409EFF;
color: white;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
}
&lt;/style&gt;</pre>
</div>
<h4 data-id="heading-16">防抖指令</h4>
<h5 data-id="heading-17">指令编写</h5>
</div>
<div class="code-block-extension-header">&nbsp;
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// modules/debounce.ts
/**
* 防抖函数
* @param fn 需要防抖的函数
* @param delay 延迟时间,单位毫秒,默认300ms
* @param immediate 是否立即执行,默认false
* @returns 返回防抖处理后的函数
*/
interface DebounceBinding {
value: Function;
arg?: string; // 延迟时间参数
}

// 防抖函数
function debounceFn(func: Function, wait: number) {
let timeout: NodeJS.Timeout;
return function(this: any, ...args: any[]) {
    clearTimeout(timeout);
    timeout = setTimeout(() =&gt; {
      func.apply(this, args);
    }, wait);
};
}

export const debounce = {
mounted(el: HTMLElement, binding: DebounceBinding) {
    // 获取延迟时间,默认为 500ms
    const delay = Number(binding.arg) || 500;
   
    // 创建防抖函数
    const debouncedFn = debounceFn(binding.value, delay);
   
    // 保存原始函数和防抖函数到元素的 dataset 中
    el.dataset.debounceFn = JSON.stringify({
      original: binding.value.toString(),
      debounced: debouncedFn.toString()
    });
   
    // 添加事件监听器
    el.addEventListener('click', debouncedFn);
},

updated(el: HTMLElement, binding: DebounceBinding) {
    // 如果值发生变化,更新防抖函数
    const delay = Number(binding.arg) || 500;
    const debouncedFn = debounceFn(binding.value, delay);
   
    // 移除旧的事件监听器
    const oldFn = new Function('return ' + JSON.parse(el.dataset.debounceFn || '{}').debounced)();
    el.removeEventListener('click', oldFn);
   
    // 更新 dataset
    el.dataset.debounceFn = JSON.stringify({
      original: binding.value.toString(),
      debounced: debouncedFn.toString()
    });
   
    // 添加新的事件监听器
    el.addEventListener('click', debouncedFn);
},

unmounted(el: HTMLElement) {
    // 组件卸载时移除事件监听器
    const fn = new Function('return ' + JSON.parse(el.dataset.debounceFn || '{}').debounced)();
    el.removeEventListener('click', fn);
    delete el.dataset.debounceFn;
}
};

// 导出防抖函数供其他地方使用
export { debounceFn };</pre>
</div>
<h5 data-id="heading-18">指令使用</h5>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;template&gt;
&lt;div class="flex flex-wrap gap-4 p-6"&gt;
    &lt;!-- 基础防抖按钮 --&gt;
    &lt;button
      v-debounce="handleClick"
      class="px-6 py-2.5 bg-blue-600 text-white font-medium text-sm leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
    &gt;
      防抖按钮
    &lt;/button&gt;

    &lt;!-- 500ms防抖按钮 --&gt;
    &lt;button
      v-debounce:500="handleClick"
      class="px-6 py-2.5 bg-green-600 text-white font-medium text-sm leading-tight uppercase rounded shadow-md hover:bg-green-700 hover:shadow-lg focus:bg-green-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-green-800 active:shadow-lg transition duration-150 ease-in-out"
    &gt;
      500ms防抖按钮
    &lt;/button&gt;

    &lt;!-- 立即执行防抖按钮 --&gt;
    &lt;button
      v-debounce.immediate="handleClick"
      class="px-6 py-2.5 bg-purple-600 text-white font-medium text-sm leading-tight uppercase rounded shadow-md hover:bg-purple-700 hover:shadow-lg focus:bg-purple-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-purple-800 active:shadow-lg transition duration-150 ease-in-out"
    &gt;
      立即执行防抖按钮
    &lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script setup&gt;
const handleClick = () =&gt; {
console.log('防抖按钮点击');
}
&lt;/script&gt;</pre>
</div>
<h4 data-id="heading-19">节流指令</h4>
<h5 data-id="heading-20">指令编写</h5>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">/**
* v-throttle 指令
* @param {Function} fn 需要节流的函数
* @param {Number} delay 延迟时间
* @param {Boolean} immediate 是否立即执行
* @returns {Function} 返回一个节流后的函数
*/

// modules/throttle.ts

interface ThrottleBinding {
value: Function;
arg?: string | number; // 延迟时间参数
modifiers?: {
    immediate?: boolean;
};
}

// 节流函数
function throttleFn(
func: Function,
wait: number,
immediate: boolean = false
) {
let timeout: NodeJS.Timeout | null = null;
let previous = 0;

return function(this: any, ...args: any[]) {
    const now = Date.now();
    const remaining = wait - (now - previous);

    if (remaining &lt;= 0 || remaining &gt; wait) {
      if (timeout) {
      clearTimeout(timeout);
      timeout = null;
      }
      previous = now;
      func.apply(this, args);
    } else if (!timeout &amp;&amp; !immediate) {
      timeout = setTimeout(() =&gt; {
      previous = immediate ? 0 : Date.now();
      timeout = null;
      if (!immediate) {
          func.apply(this, args);
      }
      }, remaining);
    }

    if (immediate &amp;&amp; !timeout) {
      func.apply(this, args);
      previous = now;
    }
};
}

export const throttle = {
mounted(el: HTMLElement, binding: ThrottleBinding) {
    const delay = Number(binding.arg) || 500;
    const immediate = binding.modifiers?.immediate || false;
   
    const throttledFn = throttleFn(binding.value, delay, immediate);
   
    el.dataset.throttleFn = JSON.stringify({
      original: binding.value.toString(),
      throttled: throttledFn.toString()
    });
   
    el.addEventListener('click', throttledFn);
},

updated(el: HTMLElement, binding: ThrottleBinding) {
    const delay = Number(binding.arg) || 500;
    const immediate = binding.modifiers?.immediate || false;
    const throttledFn = throttleFn(binding.value, delay, immediate);
   
    const oldFn = new Function('return ' + JSON.parse(el.dataset.throttleFn || '{}').throttled)();
    el.removeEventListener('click', oldFn);
   
    el.dataset.throttleFn = JSON.stringify({
      original: binding.value.toString(),
      throttled: throttledFn.toString()
    });
   
    el.addEventListener('click', throttledFn);
},

unmounted(el: HTMLElement) {
    const fn = new Function('return ' + JSON.parse(el.dataset.throttleFn || '{}').throttled)();
    el.removeEventListener('click', fn);
    delete el.dataset.throttleFn;
}
};

export { throttleFn };</pre>
</div>
<h5 data-id="heading-21">指令使用</h5>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;template&gt;
&lt;div class="flex flex-wrap gap-4 p-6"&gt;
    &lt;!-- 基础节流按钮 --&gt;
    &lt;button
      v-throttle="handleClick"
      class="px-6 py-2.5 bg-blue-600 text-white font-medium text-sm leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
    &gt;
      节流按钮
    &lt;/button&gt;

    &lt;!-- 500ms节流按钮 --&gt;
    &lt;button
      v-throttle:500="handleClick"
      class="px-6 py-2.5 bg-green-600 text-white font-medium text-sm leading-tight uppercase rounded shadow-md hover:bg-green-700 hover:shadow-lg focus:bg-green-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-green-800 active:shadow-lg transition duration-150 ease-in-out"
    &gt;
      500ms节流按钮
    &lt;/button&gt;

    &lt;!-- 立即执行节流按钮 --&gt;
    &lt;button
      v-throttle.immediate="handleClick"
      class="px-6 py-2.5 bg-purple-600 text-white font-medium text-sm leading-tight uppercase rounded shadow-md hover:bg-purple-700 hover:shadow-lg focus:bg-purple-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-purple-800 active:shadow-lg transition duration-150 ease-in-out"
    &gt;
      立即执行节流按钮
    &lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts"&gt;
const handleClick = () =&gt; {
console.log('按钮被点击');
};
&lt;/script&gt;</pre>
</div>
<h4 data-id="heading-22">长按指令</h4>
<h5 data-id="heading-23">指令编写</h5>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">src\utils\directive\modules\longPress.ts
</pre>
</div>
<p>  </p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// modules/longPress.ts

interface LongPressBinding {
value: Function;
arg?: number; // 长按时间,单位毫秒,默认500ms
modifiers?: {
    stop?: boolean; // 是否阻止事件冒泡
    prevent?: boolean; // 是否阻止默认事件
};
}

export const longPress = {
mounted(el: HTMLElement, binding: LongPressBinding) {
    if (typeof binding.value !== 'function') {
      console.warn('v-longPress 指令需要一个函数作为值');
      return;
    }

    let pressTimer: NodeJS.Timeout | null = null;
    let startTime: number = 0;
    const duration = Number(binding.arg) || 500;
    const isStop = binding.modifiers?.stop || false;
    const isPrevent = binding.modifiers?.prevent || false;

    const start = (e: MouseEvent | TouchEvent) =&gt; {
      if (isPrevent) {
      e.preventDefault();
      }
      if (isStop) {
      e.stopPropagation();
      }

      startTime = Date.now();
      
      pressTimer = setTimeout(() =&gt; {
      binding.value(e);
      }, duration);
    };

    const cancel = () =&gt; {
      if (pressTimer) {
      clearTimeout(pressTimer);
      pressTimer = null;
      }
    };

    const end = (e: MouseEvent | TouchEvent) =&gt; {
      const endTime = Date.now();
      const timeDiff = endTime - startTime;
      
      // 如果按住时间小于设定时间,则视为普通点击
      if (timeDiff &lt; duration &amp;&amp; pressTimer) {
      cancel();
      return;
      }
      
      cancel();
    };

    // 添加事件监听器
    el.addEventListener('mousedown', start);
    el.addEventListener('touchstart', start);
    el.addEventListener('mouseup', end);
    el.addEventListener('touchend', end);
    el.addEventListener('mouseleave', cancel);
    el.addEventListener('touchcancel', cancel);

    // 保存清理函数到元素上
    (el as any)._longPressCleanup = () =&gt; {
      el.removeEventListener('mousedown', start);
      el.removeEventListener('touchstart', start);
      el.removeEventListener('mouseup', end);
      el.removeEventListener('touchend', end);
      el.removeEventListener('mouseleave', cancel);
      el.removeEventListener('touchcancel', cancel);
      cancel();
    };
},

unmounted(el: HTMLElement) {
    // 清理事件监听器
    if ((el as any)._longPressCleanup) {
      (el as any)._longPressCleanup();
    }
}
};</pre>
</div>
<h5 data-id="heading-24">指令使用</h5>
<p>测试一下我们的按钮指令,效果ok</p>
<div>&nbsp;
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;template&gt;
&lt;div class="p-6 space-y-4"&gt;
    &lt;!-- 基础用法,默认500ms --&gt;
    &lt;button
      v-longPress="handleLongPress"
      class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
    &gt;
      长按按钮
    &lt;/button&gt;

    &lt;!-- 自定义长按时间 --&gt;
    &lt;button
      v-longPress:1000="handleLongPress"
      class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
    &gt;
      1秒长按按钮
    &lt;/button&gt;

    &lt;!-- 阻止事件冒泡 --&gt;
    &lt;button
      v-longPress.stop="handleLongPress"
      class="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
    &gt;
      阻止冒泡长按按钮
    &lt;/button&gt;

    &lt;!-- 阻止默认事件 --&gt;
    &lt;button
      v-longPress.prevent="handleLongPress"
      class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
    &gt;
      阻止默认事件长按按钮
    &lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script setup lang="ts"&gt;
const handleLongPress = (event: MouseEvent | TouchEvent) =&gt; {
console.log('长按触发', new Date().toISOString());
// 这里可以添加你的长按处理逻辑
if (event instanceof MouseEvent) {
    console.log('鼠标事件');
} else {
    console.log('触摸事件');
}
};
&lt;/script&gt;</pre>
</div>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19828809
頁: [1]
查看完整版本: vue3这些常见指令你封装了吗