drag-drop嵌套AddEventListener中drop事件重复触发问题咨询
拖放事件重复执行的原因与运行逻辑
核心问题:事件监听器被重复绑定
addEventListener的绑定行为是追加式的:只要对同一个DOM元素、同一个事件类型重复执行绑定代码,就会在该元素的事件监听队列中新增对应的监听器,后续事件触发时,队列里所有监听器都会按绑定顺序依次执行,不会因为回调逻辑完全一致就自动去重。
当前代码把destination元素的dragover、drop事件监听器,写在了objects元素的dragstart事件回调内部。而dragstart事件的触发时机是每一次用户开始拖动元素时就会触发一次,并非只在首次拖动时触发。
对应的累计绑定逻辑为:
- 第一次拖动元素:触发1次
dragstart回调 → 给destination绑定1个dragover监听器、1个drop监听器 → 后续触发drop时仅执行1次回调 - 第二次拖动元素:再次触发
dragstart回调 → 给destination额外追加1个dragover监听器、1个drop监听器 → 此时destination上累计存在2个drop监听器 → 触发drop时会连续执行2次回调 - 第三次拖动元素:累计绑定3个
drop监听器 → 触发drop时连续执行3次回调
这就是“第二次drop执行两次、第三次执行三次”的根本原因。
为什么“仅打印简单字符串日志时不易发现重复”
如果drop回调里仅打印固定的字符串内容,控制台输出的重复行如果没有逐行计数,很容易被忽略,误以为回调没有重复执行;但当打印事件对象关联的DOM元素时,每一次重复执行的日志块都会清晰展示出同一次drop动作触发了多轮输出,重复问题就会非常明显。
事件对象e与e2的实际运行逻辑
两个事件对象的生成时机、引用关系完全独立:
e:是对应dragstart事件被触发那一刻,浏览器生成的临时事件对象。因为它是在dragstart的回调作用域内被引用,后续所有在这个回调里新增的drop、dragover回调,都会通过闭包持有这个e的固定引用。哪怕后续过了很久才触发drop事件,回调里读到的e依然是当初触发那一次dragstart时的事件对象,它的target就是那次拖动动作的起始源元素,不会随后续拖放动作变化。e2:是对应dragover、drop事件被实际触发的那一刻,浏览器重新生成的全新独立事件对象,它的target就是当前鼠标位置悬停/松开时,处于最顶层的DOM元素。
正确实现逻辑
如果要同时跟踪拖动源元素和放置目标元素,完全不需要把destination的事件监听器嵌套在dragstart回调里重复绑定,正确写法为:
var objects = document.querySelector('.objects'); var destination = document.querySelector('.dest'); // 闭包内暂存当前被拖动的源元素 let currentDragSource = null; // 监听器全部在页面初始化时只绑定一次 objects.addEventListener('dragstart', function(e) { // 每次拖动开始时,更新暂存的源元素 currentDragSource = e.target; console.log("dragstart"); }); destination.addEventListener('dragover', function(e) { e.preventDefault(); }); destination.addEventListener('drop', function(e) { console.log("drop"); // 读取暂存的拖动源 console.log(currentDragSource); // 读取当前放置位置的目标元素 console.log(e.target); })
内容的提问来源于stack exchange,提问作者guitaric
相关产品推荐
相关产品推荐

