Angular 升级至8版本后使用D3拖拽节点出现异常问题
我刚把项目从Angular 6升级到Angular 8(连带一堆依赖包更新),结果D3的节点拖拽功能直接炸了——拖拽节点时控制台抛出这个错误:
breadcrumbs.js:64 TypeError: Cannot read property 'ctrlKey' of null at SVGRectElement.defaultFilter (drag.js:10) at SVGRectElement.mousedowned (drag.js:51) at SVGRectElement.<anonymous> (on.js:27) at SVGRectElement.sentryWrapped (helpers.js:74) at ZoneDelegate.invokeTask (zone-evergreen.js:391) at Object.onInvokeTask (core.js:39679) at ZoneDelegate.invokeTask (zone-evergreen.js:390) at Zone.runTask (zone-evergreen.js:168) at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:465) at invokeTask (zone-evergreen.js:1603) at SVGRectElement.globalZoneAwareCallback (zone-evergreen.js:1629)
查了drag.js的代码,发现导入的event明明是包含正确目标的事件对象,但defaultFilter()函数里的event却变成了null:
import {dispatch} from "d3-dispatch"; import {event, customEvent, select, mouse, touch} from "d3-selection"; import nodrag, {yesdrag} from "./nodrag.js"; import noevent, {nopropagation} from "./noevent.js"; import constant from "./constant.js"; import DragEvent from "./event.js"; // Ignore right-click, since that should open the context menu. function defaultFilter() { return !event.ctrlKey && !event.button; }
我这边是通过指令里的这段代码绑定拖拽的:
this.d3.select(this.el.nativeElement).call(this.d3.drag().on('drag', onDrag))
折腾了一阵,整理了几个可行的排查方向,你可以挨个试试:
检查D3版本兼容性:Angular 8和旧版D3可能存在Zone机制的兼容性问题,建议把D3及子包(d3-drag、d3-selection等)升级到和Angular 8同期的稳定版本(比如D3 v5.x),同时确保所有D3子包版本统一,避免混用不同版本导致的变量指向混乱。
避开Angular Zone的事件拦截:Angular的Zone.js会拦截浏览器事件,可能导致D3的全局
event对象丢失。可以把D3的拖拽绑定操作放到Angular Zone之外执行:import { NgZone } from '@angular/core'; constructor(private zone: NgZone) {} // 在初始化拖拽绑定的时候 this.zone.runOutsideAngular(() => { this.d3.select(this.el.nativeElement).call(this.d3.drag().on('drag', onDrag)); });替换全局event为参数传递:D3 v4+更推荐通过回调参数传递事件对象,而非依赖全局
event。你可以自定义filter函数,直接使用D3传递的事件参数:// 自定义过滤函数,直接接收事件参数 const customFilter = (event) => { return !event.ctrlKey && !event.button; }; // 绑定拖拽时指定自定义filter this.d3.drag().filter(customFilter).on('drag', onDrag)清理重复依赖:检查node_modules里是否存在多个版本的d3-selection或d3-drag,这类重复依赖可能导致
event变量的指向混乱。可以删除node_modules和package-lock.json,然后重新执行npm install来清理依赖。隔离拖拽逻辑与变更检测:拖拽过程中Angular的变更检测可能触发DOM状态变化,间接影响D3事件。可以把拖拽回调逻辑也放到Zone之外,若需要更新视图再手动触发变更检测:
onDrag = () => { this.zone.runOutsideAngular(() => { // 你的拖拽处理逻辑 }); // 如需更新Angular视图,手动触发变更检测 // this.cdRef.detectChanges(); };
内容的提问来源于stack exchange,提问作者Sean Tycho

