本文介绍在实现按钮拖拽功能时,如何避免鼠标移动或触摸移动事件意外触发点击事件。通过设置移动状态开关并结合延迟处理机制,有效区分拖拽与点击操作,提升PC端与移动端的交互体验。
一、背景
近期开发开源项目 Msw-Tools 时,参考了 VConsole 工具中按钮的拖拽交互逻辑,计划为 MSW 按钮添加类似的拖拽功能,并需同时兼容 PC 端与手机端。在此过程中遇到一个关键问题:当一个按钮绑定了多个事件时,如何确保在触发 mousemove 或 touchmove 移动事件时,不会同时触发 click 点击事件。

MSW Tools
如上图所示,实现 MSW 按钮的拖拽功能需要监听 mousedown、mousemove 和 mouseup 事件,在移动端则对应 touchstart、touchmove 和 touchend 事件。然而,该按钮上已预先绑定了 click 点击事件。因此,必须找到一种机制来阻止鼠标或触摸移动事件与 click 事件同时触发。否则,每次用户拖拽按钮后都会误触发 click 事件,这将严重影响用户体验。
二、问题解析
事件的触发顺序依次为:mousedown > mousemove > mouseup > click。因此,想要在 mouseup 事件执行完毕后直接阻止 click 事件的发生,直接处理较为困难,但可以通过间接方式实现。具体做法是设置一个表示“移动状态”的开关,并结合延迟处理机制,从而达到"阻止 click 事件"的目的。
三、代码实现
由于 Msw-Tools 工具是基于 Svelte 框架开发的,以下展示 Svelte 框架下的部分核心代码实现。
<!-- msw.svelte -->
<div class="msw-container">
<div on:click|stopPropagation={showModal}
bind:this={btnDOM}
class="msw-show">MSW</div>
</div>
<script>
import { onMount } from "svelte";
// 区分当前是PC端,还是移动端,来设置 mouse 事件 或 touch 事件
function getModels() {
let userAgentInfo = navigator.userAgent;
let mobileAgents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
return mobileAgents.reduce((prev, ua)=>{
return userAgentInfo.includes(ua) || prev
}, false)
};
const MSW_BTN_POSITION = '__MSW_BTN_POSITION__'
let show = false;
let btnDOM = null;
let isDrop = false;
let isMoving = false;
let offset = {
x: 0,
y: 0,
};
let offsetDown = {};
let dropTimer = null;
let isMobile = getModels();
let btnW = 0;
let btnH = 0;
let clientW = 0;
let clientH = 0;
let eventType = isMobile ? 'touchstart' : 'mousedown';
// DOM 挂载后执行
onMount(async () => {
// 初始化,获取按钮、视口宽高、计算边界值
initClientData();
return () => {
// component 卸载后,解除事件绑定
btnDOM.removeEventListener(eventType, btnMousedown)
}
});
function initClientData() {
// 按钮位置保存在本地,可以记住位置,避免每次去拖拽
let local = localStorage.getItem(MSW_BTN_POSITION)
if (local) {
offset = JSON.parse(local)
btnMove()
}
let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
clientW = isMobile ? w : document.body.clientWidth
clientH = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
btnW = btnDOM.offsetWidth
btnH = btnDOM.offsetHeight
// 给按钮绑定 mousedown 或 touchstart 事件
btnDOM.addEventListener(eventType, btnMousedown)
}
function eventHandle (type) {
if (isMobile) {
document[`${type}EventListener`]('touchmove', mousemove);
document[`${type}EventListener`]('touchend', mouseup);
} else {
document[`${type}EventListener`]('mousemove', mousemove);
document[`${type}EventListener`]('mouseup', mouseup);
}
}
function showModal () {
if (!isMoving) {
show = true;
}
}
function btnMousedown(e) {
e = e || window.event
isDrop = true
offsetDown = {
...getOffset(e)
};
eventHandle('add')
}
function mousemove(e) {
e = e || window.event
if (isDrop) {
let data = getOffset(e);
// 判断是否移动了
isMoving = !(offsetDown.x === data.x && offsetDown.y === data.y)
let x = data.x - btnW / 2;
let y = data.y - btnH / 2;
if (x > 5 && x < (clientW-btnW - 5)) {
offset.x = x;
}
if (y > 5 && y < (clientH-btnH - 5)) {
offset.y = y;
}
if (isMoving) {
btnMove()
}
clearTimeout(dropTimer);
dropTimer = setTimeout(()=>{
isMoving = false;
clearTimeout(dropTimer);
dropTimer = null;
}, 300);
}
}
function mouseup() {
if (isDrop) {
window.localStorage.setItem(MSW_BTN_POSITION, JSON.stringify(offset))
eventHandle('remove')
}
isDrop = false
// console.log('mouseup')
}
function btnMove (){
btnDOM.style.cssText = `
left: ${offset.x}px;
top: ${offset.y}px;
right: auto;
bottom: auto;
`
}
function getOffset(e) {
return isMobile ? {
x: e.targetTouches[0].clientX,
y: e.targetTouches[0].clientY,
} : {
x: e.clientX,
y: e.clientY,
}
}
</script>
<style lang="scss" type="text/scss">
@import "index";
</style>- 效果体验:msw-tools
以上就是阻止mousemove鼠标移动或touchmove触摸移动触发click点击事件的详细内容,更多关于阻止鼠标移动触发点击事件的资料请关注本站其它相关文章!