vue3 监听鼠标点击拖动事件,移动端滑动事件,指针事件,页面指针坐标事件
Pointer Events API 是Hmtl5的事件规范之一,它主要目的是用来将鼠标(Mouse)、触摸(touch)和触控笔(pen)三种事件整合为统一的API。
Pointer指可以在屏幕上反馈一个指定坐标的输入设备。Pointer Event事件和Touch Event API对应的触摸事件类似,它继承扩展了Touch Event,因此拥有Touch Event的常用属性。
指针事件
指针事件(Pointer Events)是一种用于处理来自各种输入设备(例如鼠标、触控笔和触摸屏等)的输入信息的现代化解决方案。
https://zh.javascript.info/pointer-events#zhi-zhen-shi-jian-lei-xing
在指针事件规范中,touchcancel事件对应的指针事件是pointercancel,而mouseleave事件对应的指针事件是pointerleave。
Pointer Event Api-整合鼠标事件、触摸和触控笔事件(https://www.cnblogs.com/moqiutao/p/13237093.html)
mdn文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/pointerdown_event
总结
指针事件允许我们通过一份代码,同时处理鼠标、触摸和触控笔事件。
指针事件是鼠标事件的拓展。我们可以在事件名称中用 pointer 替换 mouse 来让我们的代码既能继续支持鼠标,也能更好地支持其他类型的设备。
对于浏览器可能会决定进行劫持并自行处理的拖放和复杂的触控交互 —— 请记住取消事件的默认操作,并在 CSS 中为涉及到的元素设置 touch-action: none。
传统监听绑定事件:
<View
id="carousel"
class="phone-container"
:style="{ transform: `translateX(${state.offset}px)`"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@touchcancel="handleTouchEnd"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
@focusout="handleMouseUp"
@mouseleave="handleMouseUp"
@blur="handleMouseUp"
>
</View>
startX: 0,
isDragging: false,
offset: 0,
index: 0,
let banner: HTMLDivElement;
banner = document.querySelector('#carousel') as HTMLDivElement;
onMounted
/** 兼容鼠标拖动过程不触发点击事件 */
setTimeout(() => {
nextTick(() => {
banner.addEventListener(
'click',
event => {
/** 滑动有偏移的话就阻止点击事件的捕获 */
if (Math.abs(state.startX - event.clientX) > 5) event.stopPropagation();
},
true
);
});
}, 100);
function handleTouchMove(event: any) {
if (showDeviceList.value.length === 1) return;
event.preventDefault();
const moveX = event.changedTouches[0].clientX;
const moveLength = moveX - startX;
state.offset = -state.index * banner.offsetWidth + moveLength;
}
function handleTouchStart(event: any) {
if (showDeviceList.value.length === 1) return;
startX = event.touches[0].clientX;
banner.style.transition = '0';
}
function handleTouchEnd(event: any) {
if (showDeviceList.value.length === 1) return;
const endX = event.changedTouches[0].clientX;
if (Math.abs(startX - endX) < 50) return autoMove();
if (startX > endX) {
state.index < showDeviceList.value.length - 1 && state.index++;
} else {
state.index > 0 && state.index--;
}
autoMove();
}
function handleMouseDown(event) {
event.preventDefault();
if (showDeviceList.value.length === 1) return;
state.startX = event.clientX;
state.isDragging = true;
banner.style.transition = '0';
}
function handleMouseMove(event) {
if (!state.isDragging || showDeviceList.value.length === 1) return;
event.preventDefault();
const moveX = event.clientX;
const moveLength = moveX - state.startX;
state.offset = -state.index * banner.offsetWidth + moveLength;
}
function handleMouseUp(event) {
event.preventDefault();
if (!state.isDragging || showDeviceList.value.length === 1) return;
state.isDragging = false;
const endX = event.clientX;
if (Math.abs(state.startX - endX) < 50) return autoMove();
if (state.startX > endX) {
state.index < showDeviceList.value.length - 1 && state.index++;
} else {
state.index > 0 && state.index--;
}
autoMove();
}
function autoMove() {
banner.style.transition = '1s';
state.offset = -state.index * banner.offsetWidth;
handleTraceExpose('cp_app_cloud_switch_visit');
}
vue指令事件:页面元素指针坐标事件
<View class="product-outer" :key="state.listUpdated" :class="{ 'one-card': state.list.length === 1 }" v-pointer-event:[state.index]="handleProductChange">
import vPointerEvent from '@/directives/vPointerEvent';
index: Number(sessionStorage.getItem(PRODUCT_INDEX_NAME) || 0), // 所选择产品
function handleProductChange(index: number) {
state.subIndex = 0;
state.index = index;
trace({ id: 'cp_app_pay_product_click' });
sessionStorage.setItem(PRODUCT_INDEX_NAME, String(index));
}
在指针事件规范中,touchcancel事件对应的指针事件是pointercancel,而mouseleave事件对应的指针事件是pointerleave。
vPointerEvent.ts
const vPointerEvent = {
mounted(el: HTMLElement, binding: any) {
const childNum = el.children.length;
el.style.width = 'max-content';
const childWidth = Math.floor(el.clientWidth / childNum);
const emitIndex = binding.value || (() => {});
if (childNum == 1) return;
let startX = 0;
let offsetX = 0;
let hasMove = false;
el.addEventListener('pointerdown', event => {
startX = event.pageX;
el.style.transition = '';
});
el.addEventListener('pointermove', event => {
console.log('0000000');
if (event.pointerType === 'mouse' && event.buttons === 0) return; // 鼠标滑过忽略
const nowOffsetX = event.pageX - startX;
if (Math.abs(nowOffsetX) > 10) hasMove = true;
el.style.transform = `translate3d(${offsetX + nowOffsetX}px, 0px, 0px)`;
});
/** 兼容鼠标拖动的滚动事件 */
el.addEventListener('mouseleave', event => {
// if (event.pointerType === 'mouse' && event.buttons === 0) return; // 鼠标滑过忽略
if (!hasMove) return;
const tempX = offsetX + event.pageX - startX;
hasMove = false;
if (tempX > 0) {
// 右滑超过边界,回弹
offsetX = 0;
emitIndex(0);
} else if (Math.abs(tempX) + childWidth + 100 > el.clientWidth) {
offsetX = -childWidth * (childNum - 1) + 50;
emitIndex(childNum - 1);
} else {
if (event.pageX - startX === 0) return;
const isLeftMove = event.pageX - startX < 0;
const targetIndex = Math[isLeftMove ? 'floor' : 'ceil'](tempX / childWidth);
offsetX = targetIndex * childWidth + (isLeftMove ? 20 : 20);
emitIndex(Math.abs(targetIndex));
}
el.style.transition = 'all 800ms ease 0s';
el.style.transform = `translate3d(${offsetX}px, 0px, 0px)`;
});
el.addEventListener('pointerup', event => {
if (!hasMove) return;
const tempX = offsetX + event.pageX - startX;
hasMove = false;
console.log('444444', tempX);
if (tempX > 0) {
// 右滑超过边界,回弹
offsetX = 0;
emitIndex(0);
} else if (Math.abs(tempX) + childWidth + 100 > el.clientWidth) {
offsetX = -childWidth * (childNum - 1) + 50;
emitIndex(childNum - 1);
} else {
if (event.pageX - startX === 0) return;
const isLeftMove = event.pageX - startX < 0;
const targetIndex = Math[isLeftMove ? 'floor' : 'ceil'](tempX / childWidth);
offsetX = targetIndex * childWidth + (isLeftMove ? 20 : 20);
emitIndex(Math.abs(targetIndex));
}
el.style.transition = 'all 800ms ease 0s';
el.style.transform = `translate3d(${offsetX}px, 0px, 0px)`;
});
el.addEventListener('touchmove', event => {
event.preventDefault();
});
if (typeof binding.arg === 'number' && binding.arg > 0) {
const num = binding.arg;
offsetX = -num * childWidth + 50;
emitIndex(Math.abs(num));
el.style.transform = `translate3d(${offsetX}px, 0px, 0px)`;
}
el.addEventListener('click', event => {
if (hasMove) return;
const id = (event.target as any)?.id || '';
if (!id) return;
const num = Number(id.split('-')?.[1] || 0);
offsetX = -num * childWidth + 20;
console.log(num);
emitIndex(Math.abs(num));
el.style.transition = 'all 800ms ease 0s';
el.style.transform = `translate3d(${offsetX}px, 0px, 0px)`;
});
}
};
export default vPointerEvent;