TypeScript中无法移除EventListener问题求助
TypeScript中移除EventListener失败的解决方案
问题场景
在TypeScript开发中,通过按钮控制布尔值movePick,当movePick为true时给document添加pointermove、mousedown、pointerup事件监听器实现物体拖动;但将movePick设为false时,无法移除这些监听器。仅在添加监听器的同一if代码块内执行移除操作才有效,尝试多种方案均无效。相关代码如下:
public moveObject(movePick: boolean, raycaster: THREE.Raycaster): void { const plane = new THREE.Plane(); const pNormal = new THREE.Vector3(0, 1, 0); // plane's normal const planeIntersect = new THREE.Vector3(); // point of intersection with the plane const pIntersect = new THREE.Vector3(); // point of intersection with an object (plane's point) const shift = new THREE.Vector3(); // distance between position of an object and points of intersection with the object let isDragging = false; let dragObject: any; // events const pointermove = (event: PointerEvent) => { const rect = this.canvas.getBoundingClientRect(); const x = event.clientX - rect.left const y = event.clientY - rect.top const pickPosition = { x: 0, y: 0 }; pickPosition.x = (x / this.canvas.clientWidth) * 2 - 1; pickPosition.y = (y / this.canvas.clientHeight) * -2 + 1; raycaster.setFromCamera(pickPosition, this.camera); // console.log("movePICK IN POINTERMOVE",movePick) if (isDragging) { raycaster.ray.intersectPlane(plane, planeIntersect); dragObject.position.addVectors(planeIntersect, shift); } } const mousedown = () => { const intersects = raycaster.intersectObjects(this.scene.children); for (let i = 0; i < this.mesharr.length; i++) { if (intersects[0].object.name == this.mesharr[i].name && intersects[0].object.name != "Rail") { pIntersect.copy(intersects[0].point); plane.setFromNormalAndCoplanarPoint(pNormal, pIntersect); shift.subVectors(intersects[0].object.position, intersects[0].point); isDragging = true; dragObject = intersects[0].object; } } } const pointerup = () => { isDragging = false; dragObject = null; } if (movePick) { document.addEventListener("pointermove", pointermove); document.addEventListener("mousedown", mousedown); document.addEventListener("pointerup", pointerup); } else { document.removeEventListener("pointermove", pointermove); document.removeEventListener("mousedown", mousedown); document.removeEventListener("pointerup", pointerup); } }
问题根源
每次调用moveObject函数时,都会重新创建pointermove、mousedown、pointerup这三个事件处理函数。添加监听器时用的是本次调用创建的函数引用,而移除时用的是下一次调用创建的新函数引用——两个引用指向不同的函数实例,removeEventListener无法匹配到之前添加的监听器,因此移除失败。
而在同一if代码块内移除有效,是因为此时添加和移除用的是同一个函数引用。
解决方案
将事件处理函数和拖动相关的变量(如plane、isDragging、dragObject等)提升为类的成员变量,确保每次调用moveObject时使用的是同一个函数引用。修改后的代码如下:
// 类成员变量 private plane = new THREE.Plane(); private pNormal = new THREE.Vector3(0, 1, 0); private planeIntersect = new THREE.Vector3(); private pIntersect = new THREE.Vector3(); private shift = new THREE.Vector3(); private isDragging = false; private dragObject: any; private raycasterRef: THREE.Raycaster; // 保存raycaster引用 // 事件处理函数作为类成员方法 private pointermove = (event: PointerEvent) => { const rect = this.canvas.getBoundingClientRect(); const x = event.clientX - rect.left const y = event.clientY - rect.top const pickPosition = { x: 0, y: 0 }; pickPosition.x = (x / this.canvas.clientWidth) * 2 - 1; pickPosition.y = (y / this.canvas.clientHeight) * -2 + 1; this.raycasterRef.setFromCamera(pickPosition, this.camera); if (this.isDragging) { this.raycasterRef.ray.intersectPlane(this.plane, this.planeIntersect); this.dragObject.position.addVectors(this.planeIntersect, this.shift); } } private mousedown = () => { const intersects = this.raycasterRef.intersectObjects(this.scene.children); if (!intersects[0]) return; // 避免空指针 for (let i = 0; i < this.mesharr.length; i++) { if (intersects[0].object.name == this.mesharr[i].name && intersects[0].object.name != "Rail") { this.pIntersect.copy(intersects[0].point); this.plane.setFromNormalAndCoplanarPoint(this.pNormal, this.pIntersect); this.shift.subVectors(intersects[0].object.position, intersects[0].point); this.isDragging = true; this.dragObject = intersects[0].object; } } } private pointerup = () => { this.isDragging = false; this.dragObject = null; } public moveObject(movePick: boolean, raycaster: THREE.Raycaster): void { this.raycasterRef = raycaster; // 更新raycaster引用 if (movePick) { document.addEventListener("pointermove", this.pointermove); document.addEventListener("mousedown", this.mousedown); document.addEventListener("pointerup", this.pointerup); } else { document.removeEventListener("pointermove", this.pointermove); document.removeEventListener("mousedown", this.mousedown); document.removeEventListener("pointerup", this.pointerup); } }
关键改动说明
- 将所有拖动相关的状态变量和事件处理函数改为类的成员,确保每次调用
moveObject时使用的是同一个函数实例。 - 使用箭头函数定义事件处理方法,避免
this指向问题,同时保证函数引用的一致性。 - 添加
raycasterRef成员变量保存传入的raycaster引用,供事件处理函数使用。 - 在
mousedown中增加空值判断,避免intersects[0]不存在时的报错。
内容的提问来源于stack exchange,提问作者Russix
相关产品推荐
相关产品推荐

