新建文件longpress.ts
文件
// longpress.ts
import { DirectiveBinding } from 'vue'
const longpress = {
beforeMount(el: any, binding: DirectiveBinding) {
const cb = binding.value
const duration = binding.arg || 800 // 长按等待时间
let timer: number | null = null
let isLongPress = false // 记录是否触发长按
const longPress = (e: TouchEvent) => {
if (e.type === 'touchstart') {
timer = window.setTimeout(() => {
isLongPress = true
if (typeof cb === 'function') cb(el) // 3. 回调函数
}, +duration)
return false
} else if (e.type === 'touchmove') {
if (isLongPress) {
e.preventDefault() // 1. 阻止默认行为
}
// 2. 若移动了就不触发长按事件
if (timer) {
clearTimeout(timer)
timer = null
}
} else if (e.type === 'touchend') {
if (isLongPress) {
e.preventDefault() // 1. 阻止默认行为
isLongPress = false
}
if (timer) {
clearTimeout(timer)
timer = null
}
return false
}
}
el.addEventListener('touchstart', longPress)
el.addEventListener('touchmove', longPress)
el.addEventListener('touchend', longPress)
el['longPress'] = longPress
},
unmounted(el: any) {
el.removeEventListener('touchstart', el['longPress'])
el.removeEventListener('touchmove', el['longPress'])
el.removeEventListener('touchend', el['longPress'])
},
}
export default longpress
在main.ts
中引入并全局定义
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import longpress from './directives/longpress'
const app = createApp(App)
app.directive('longpress ', longpress)
app.mount('#app')
在组件中直接使用
// demo.vue
<template>
<div v-for="item in msgs" :key="item.id">
<img class="avatar" :src="item.icon" :data-id="item.id" v-longpress="onPress"/>
<div class="name">{{ item.name }}</div>
</div>
</template>
<script setup>
import { ref } from "vue"
const msgs = ref([
{"id":1,"name":"朱鸢","icon":"https://bbs-static.miyoushe.com/static/2024/03/18/d70f25b443df8029e05cd20a897126a6_7820117047572824878.png"},
{"id":2,"name":"艾莲","icon":"https://bbs-static.miyoushe.com/static/2023/11/03/060c8ba17ff372078dcf62d4d8b46d28_7229548510559384850.png"},
{"id":3,"name":"丽娜","icon":"https://bbs-static.miyoushe.com/static/2023/11/03/6ce65ace05f501e8f6af3616dea1c634_1107629373726729394.png"}
])
const onPress = (e) => {
console.log("press", e, e.dataset.id)
}
</script>
这个指令里有三点处理需要特别说明一下(对应代码注释里的序号):
- 浏览器中长按会有一些默认的行为(比如弹出菜单栏),那么就需要阻止掉这些行为的触发。在
touchend
并且判断是长按的情况下才阻止,防止在非长按时默认行为(比如滑动)也被阻止。在touchmove
中也阻止,防止在长按后不松手的情况下滑动导致以下报错。
- 若在长按触发前触发了移动事件,则清除长按监听。因为这个时候用户很可能只是想滑动页面,不需要触发长按事件。
- 长按触发后调用回调函数,将当前长按的目标元素传回。这个有很大作用,比如本文的示例,头像列表由数据循环渲染而成,每个头像都添加了长按事件,那么当前长按的是哪个,就需要有参数的回调了。由于在指令中无法通过以下形式传参,
因此,我们在作用元素上添加自定义属性<div v-for="item in msgs" :key="item.id"> <img :src="item.icon" v-longpress="onPress(item.id)"/> // 错误的传参方式 ❌ </div>
data-id
,将id设置在此属性上。
当我们触发长按时,长按指令会回调当前元素,此时只需要通过<div v-for="item in msgs" :key="item.id"> <img :src="item.icon" :data-id="item.id" v-longpress="onPress"/> // 将参数设置在自定义属性上 </div>
e.dataset.id
就可以获取到长按项对应的id了。如下图所示: