D3 v7圆形拖拽第二次触发时位置重置回初始值问题
D3 v7 圆形拖拽二次跳转问题说明
问题现象
- 基于D3 v7实现圆形拖拽交互时,首次拖拽逻辑运行正常,从第二次拖拽开始,圆形会直接重置/跳转到数据join阶段绑定的初始坐标位置
- 排查确认拖拽事件的位置取值与元素绑定数据的属性命名直接相关:数据集使用
x、y作为坐标属性名时异常稳定复现,将属性名修改为posx、posy等其他名称后,拖拽功能可正常运行 - 即使在拖拽过程中同步修改元素
cx、cyDOM属性,重置问题仍然存在;拖拽后手动更新circles数组中存储的坐标值,也不会改变跳转行为,第二次拖拽时圆形仍会回到初始化时生成的原始坐标位置
问题根因
- 绑定数据的属性名本身不会影响元素初始渲染位置:初始渲染时元素位置由编写的
.attr("cx", d=>d.x)、.attr("cy", d=>d.y)逻辑决定,只要属性名和取值逻辑对应,使用任何属性名都可以完成正确的初始渲染。异常来自D3拖拽模块的内置默认逻辑。 - 二次拖拽位置重置的核心逻辑:D3 v7的
d3.drag行为默认会自动识别拖拽主体(subject),对于绑定了数据的SVG元素,拖拽主体默认取元素绑定的datum对象;拖拽启动计算偏移基准时,会优先读取主体对象上的x、y属性作为拖拽起始坐标,不会主动读取DOM上当前已设置的cx/cy实际位置值。
复现代码中,拖拽过程仅修改了DOM的cx、cy属性,没有同步更新绑定datum对象上的x、y值,datum中存储的始终是初始化时生成的随机坐标。首次拖拽时模块还未触发基准坐标读取逻辑,操作表现正常;第二次拖拽启动时,模块直接读取datum上未更新的原始x/y值作为起始基准,就会把元素直接定位回初始坐标,出现跳转现象。
当把数据属性名改为posx/posy后,拖拽模块在datum上找不到x/y属性,就会自动读取DOM当前的实际位置作为拖拽基准,因此不会出现跳转。
可复现异常的完整代码
<!DOCTYPE html> <html lang="en"> <body> <script src="https://d3js.org/d3.v7.min.js"></script> <script> radius = 32; height = 600; width = 800; function dragStart(event,d){ d3.select(this) .style("stroke", "black") console.log("Start",[event.x,event.y]) var current = d3.select(this); console.log([current.attr("cx"),current.attr("cy")]); console.log(current.attr("translate")); } function dragging(event,d){ var current = d3.select(this); current .attr('cx', event.x) .attr('cy', event.y); } function dragEnd(event,d){ d3.select(this) .style("stroke", "") console.log("End",[event.x,event.y]) var current = d3.select(this); console.log(d3.select(this)); } drag = d3.drag() .on("start", dragStart) .on("drag", dragging) .on("end", dragEnd); var svg = d3.select("body") .append("svg") .attr("viewBox", [0, 0, width, height]) .attr("stroke-width", 2); var circles = d3.range(20).map(i => ({ x: Math.random() * (width - radius * 2) + radius, y: Math.random() * (height - radius * 2) + radius, })); console.log(circles); console.log(drag); svg.selectAll("circle") .data(circles) .join("circle") .attr("cx", (d)=>d.x) .attr("cy", (d)=>d.y) .attr("r", radius) .attr("fill", (d, i) => d3.schemeCategory10[i % 10]) .on("click",e => console.log(d3.pointer(e))) .call(drag); </script> </body> </html>
修复方案
任选以下一种方案即可解决问题:
- 方案1:保留
x/y作为数据属性名,拖拽过程中同步更新绑定datum的x/y值,保证拖拽模块读取的基准坐标和DOM实际位置一致,修改拖拽回调如下:function dragging(event,d){ // 同步更新绑定数据的坐标 d.x = event.x; d.y = event.y; d3.select(this) .attr('cx', d.x) .attr('cy', d.y); } - 方案2:自定义拖拽主体(subject)读取逻辑,强制拖拽模块从DOM属性读取当前位置作为基准,不依赖数据上的
x/y属性,拖拽配置修改如下:const drag = d3.drag() .subject(function() { const node = d3.select(this); return { x: +node.attr("cx"), y: +node.attr("cy") } }) .on("start", dragStart) .on("drag", dragging) .on("end", dragEnd); - 方案3:避开D3拖拽模块默认读取的属性名,使用
posx/posy等其他自定义名称存储坐标值,初始渲染和拖拽更新时对应修改属性读取逻辑即可。
内容的提问来源于stack exchange,提问作者Cristian Leoni
相关产品推荐
相关产品推荐

