换句话说:当您通过单击圆圈外缘附近的某个位置启动圆圈拖动时,代码中的什么会保留在拖动开始时暗示的偏移量(相对于圆圈中心)?
我看到这些.attr()调用:
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y)
但我期望d3.event.x
(和.y
)是鼠标的坐标-没有考虑偏移-因此,我认为圆的中心将会(从用户体验的角度来看不正确地)恰好在鼠标下方。
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y)
但我期望d3.event.x
(和.y
)是鼠标的坐标-没有考虑偏移-因此,我认为圆的中心将会(从用户体验的角度来看不正确地)恰好在鼠标下方。
我相信这个问题与d3拖动主题方法有关:
如果指定了主题,则将主题访问器设置为指定的对象或函数,并返回拖动行为。如果未指定主题,则返回当前主题访问器,默认为:
function subject(d) { return d == null ? {x: d3.event.x, y: d3.event.y} : d; }
拖动手势的主题表示被拖动的东西。当接收到初始输入事件时(例如mousedown或touchstart),立即在拖动手势开始前计算主题。然后,在此手势的后续拖动事件中将主题公开为event.subject。(链接)
我们可以看到,如果我们既没有提供一个主题函数,也没有提供具有x和y属性的数据,那么拖动事件将导致圆形居中/捕捉到拖动起始点:
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var datum = {x:250,y:150}
var g = svg.append("g")
g.append("rect")
.attr("width",500)
.attr("height",300)
.attr("fill","#ddd");
g.append("circle")
.datum(datum)
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",10);
g.call(d3.drag().on("drag", dragged))
function dragged(d) {
d3.select(this)
.select("circle")
.attr("cx", d3.event.x)
.attr("cy", d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
以相同的示例为例,将数据分配给父g
元素,使拖动可以访问主题的x和y属性(在上面的示例中不存在)。 在这里,拖动与初始数据相对(保持不变),并且节点将使用在数据中指定的初始x和y属性作为每次拖动的起点重新居中(拖动超过一次以查看):
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var datum = {x:250,y:150}
var g = svg.append("g")
.datum(datum);
g.append("rect")
.attr("width",500)
.attr("height",300)
.attr("fill","#ddd");
g.append("circle")
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",10);
g.call(d3.drag().on("drag", dragged))
function dragged(d) {
d3.select(this)
.select("circle")
.attr("cx", d3.event.x)
.attr("cy", d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
那么我们可以更新主体的数据,这样每个拖动事件都相对于圆的当前位置而不是初始位置:
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var datum = {x:250,y:150}
var g = svg.append("g")
.datum(datum);
g.append("rect")
.attr("width",500)
.attr("height",300)
.attr("fill","#ddd");
g.append("circle")
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",10);
g.call(d3.drag().on("drag", dragged))
function dragged(d) {
d3.select(this)
.select("circle")
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
dx = s.x - p[0] || 0;
dy = s.y - p[1] || 0;
p 是起始鼠标位置,s 是主题。
这就解释了为什么当没有提供 x 或 y 属性时,圆形会吸附到拖动开始的地方。在计算输出时,d3 将 x 和 y 的值设置为:
p[0] + dx,
p[1] + dy
这里的 p 是当前鼠标位置。
因此,d3.event.x/.y 不应该是鼠标的绝对位置,而是给定拖动中相对位置变化后圆圈的绝对位置。通过 subject,将鼠标位置的相对变化转化为被拖动项目的绝对位置。
以下是一个使用自定义 subject 的示例,其中拖动将相对于 [100,100],并且圆圈将在每个拖动事件开始时固定在那里:
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var datum = {x:250,y:150}
var g = svg.append("g")
.datum(datum);
g.append("rect")
.attr("width",500)
.attr("height",300)
.attr("fill","#ddd");
g.append("circle")
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",10);
g.call(d3.drag()
.on("drag", dragged)
.subject({x:100,y:100})
)
function dragged(d) {
d3.select(this)
.select("circle")
.attr("cx", d3.event.x)
.attr("cy", d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>