mouseup 和 click 事件不触发

6,163 阅读3分钟

具体需求

基于Vue 2.x,实现一个可拖动的内容组件

  • 按下鼠标左键开始拖动,随光标移动而移动,松开左键后停止移动,即不可拖动了

  • 内容区部分内嵌iframe

  • 右上角有操作按钮

原代码概况

拖动效果是使用mousedownmousemovemouseup实现的,大致代码如下:

<template>
  <div :class="contentClassObj" @mousedown.stop="handleMoveStart">
    <div :class="buttonArea">
      <button @click="handleAction">Action</button>
    </div>
    <div :class="headerClassObj"></div>
    <div :class="bodyClassObj">
      <iframe :src="iframeUrl"></iframe>
    </div>
    <div :class="footerClassObj"></div>
  </div>
</template>

可知,鼠标按下即执行handleMoveStart

export default {
  // data、computed略
  methods: {
    handleMoveStart(e) {}, // 拖动开始
    handleMove(e) {}, // 拖动中
    handleMoveUp(e) {}, // 拖动结束
    handleAction() {}, // 按钮执行事件(导致位置变化)
  },
  mounted() {
    // 监听鼠标移动 & 鼠标松开事件
    window.addEventListener("mousemove", this.handleMove);
    window.addEventListener("mouseup", this.handleMoveUp);
  },
  beforeDestroy() {
    window.removeEventListener("mousemove", this.handleMove);
    window.removeEventListener("mouseup", this.handleMoveUp);
  },
};

问题

运行代码,在操作中,偶尔会发现以下的BUG

1、组件被拖动,在松开鼠标左键后,仍然可以随光标的移动而移动 —— 不触发mouseup事件了?

2、点击按钮,未执行handleAction,而是执行handleMoveStart —— click事件不执行了?

原因

问题1

不断操作,复现问题,结合代码,发现mouseup事件丢失是因为:

  • a)触发到主页面的拖动事件(drag & drop)

  • b)触发到iframe的拖动事件(内容区是iframe)

  • c)光标移到iframe内,然后松开鼠标

参考链接

问题2

原因:多次测试,发现“点击”:鼠标左键按下两秒后才松开

未找到原因所在前,复现次数不多,后续仔细查看代码,再根据以下3个事件的触发时机

  • mousedown:当鼠标移动到元素上方,并按下按键(左/右键)时

  • mouseup: 当在元素上松开按键(左/右键)时

  • click:当鼠标停留在元素上方,然后按下并松开鼠标左键

在同一个元素上按下并松开鼠标左键,会依次触发mousedown、mouseup、click

参考链接

可知,若鼠标停留在操作按钮上方,左键按下时间较长,未松开,即暂未完成一个完整的click,就会先触发了父元素的mousedown事件;又因为添加了stop修饰符(@mousedown.stop),阻止事件继续传播,handleAction事件不会触发了

解决方案

问题1

  • 1、针对a),需组件监听dragstart事件,防止主页面触发drag & drop操作:
mounted() {
    window.addEventListener("dragstart", (e) => {
        e.preventDefault();
        e.stopPropagation(); 
    });
},
beforeDestroy() {
    // ……
}
  • 2、同理,对于b)和c),需要监听iframe页面的拖动和鼠标松开事件:

此监听,需要iframe与主页面需通信,详见:vue页面与iframe页面通信方法

使用到window.postMessage:实现跨源通信,一个窗口可以获得对另一个窗口的引用,一窗口分发信息,另一接收消息的窗口可以根据需要自由处理此事件。链接

iframe向页面传递事件
document.addEventListener("dragstart", () => {
    window.parent.postMessage({ type: "dragstart" }, "*");
});
document.addEventListener("mouseup", () => {
    window.parent.postMessage({ type: "mouseup" }, "*");
});
页面接受信息,进行处理
mounted() {
    window.addEventListener('message', (e) => {
        let type = e.data.type;
        if (type == "dragstart") {
            // 防止触发iframe的drag & drop操作
            e.preventDefault();
            e.stopPropagation();   
        } else if (type == "mouseup") {
            // 鼠标移到了iframe区域,再松开左键,停止拖动
            this.handleMoveUp();
        }
    });
},
beforeDestroy() {
    // ……
}

问题2

  • 解决方法:子元素事件阻止往上冒泡

添加@mousedown.stop 或者 对其mousedown事件执行 e.stopPropagation(),可避免执行父元素的mousedown事件,如:

<button @click="handleAction" @mousedown.stop>Action</button>

Last but not least

如有不妥,请多指教~