具体需求
基于Vue 2.x,实现一个可拖动的内容组件
-
按下鼠标左键开始拖动,随光标移动而移动,松开左键后停止移动,即不可拖动了
-
内容区部分内嵌iframe
-
右上角有操作按钮
原代码概况
拖动效果是使用mousedown
、mousemove
和mouseup
实现的,大致代码如下:
<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
如有不妥,请多指教~