在此 d3 示例中拖动任何圆时,是什么阻止圆的中心与鼠标对齐?
换句话说:当您通过单击圆外边缘附近的某处来启动圆拖动时,代码中的什么保留了拖动开始时隐含的偏移量(相对于圆的中心)?
我看到这些.attr()电话:
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y)
Run Code Online (Sandbox Code Playgroud)
但我希望d3.event.x(和.y) 是鼠标的坐标——不考虑偏移——因此我认为圆的中心会(错误地,从 UX 的角度来看)最终位于鼠标正下方。
我相信 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);
}Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>Run Code Online (Sandbox Code Playgroud)
以相同的示例,移动将数据分配给父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);
}Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>Run Code Online (Sandbox Code Playgroud)
然后我们可以更新主题的数据,这使得每个拖动事件都相对于圆的当前位置而不是初始位置:
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);
}Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>Run Code Online (Sandbox Code Playgroud)
深入了解拖动代码我们可以看到,当拖动开始时,如果没有为主题方法提供函数,则计算拖动 x,y start 和主题 x,y 之间的差异:
dx = s.x - p[0] || 0;
dy = s.y - p[1] || 0;
Run Code Online (Sandbox Code Playgroud)
其中 p 是鼠标的起始位置。s 是主语。
这就解释了为什么当没有提供 x 或 y 属性时,圆圈会捕捉到拖动开始的地方。在计算输出时,d3 将 x 和 y 值设置为:
p[0] + dx,
p[1] + dy
Run Code Online (Sandbox Code Playgroud)
其中 p 是当前鼠标位置。
所以 d3.event.x/.y 不应该是鼠标的绝对位置,而是给定拖动指定位置的相对变化的圆的绝对位置。正是通过主题,鼠标位置的相对变化被转换为被拖动项目的绝对位置。
这是一个带有自定义主题的示例,其中拖动将相对于 [100,100] 并且圆圈将在每个拖动事件开始时捕捉到那里:
dx = s.x - p[0] || 0;
dy = s.y - p[1] || 0;
Run Code Online (Sandbox Code Playgroud)
p[0] + dx,
p[1] + dy
Run Code Online (Sandbox Code Playgroud)