简述
Drag-and-drop(拖拽) 是一种很流行的交互方式。 D3的 drag behavior 为[selections](https://github.com/d3/d3-selection交互, 例如你可以使用拖拽交互来增强 力导向效果,或者拖拽力学仿真中相互碰撞的圆:
JS Bin on jsbin.com你也可以使用拖拽交互来做一些传统的控件,比如滑块等。但是拖拽交互并不局限于移动元素,有很多种响应拖拽操作的方式,比如可以使用它来套索散点图中的元素,或者使用canvas画图。
拖拽交互对元素不敏感,所以你可以将其应用在SVG,HTML甚至Canvas上。并且可以通过其他技术手段对其进行扩展,比如可以将Voronoi叠加到目标元素上:
有一点好处,那就是拖拽操作统一了鼠标和触摸输入,并且屏蔽了浏览器的差异性。如果支持Pointer Events 那么也支持拖拽操作。
W3C对Pointer的定义为:以任何输入设备在屏幕上做出接触点的效果,比如鼠标,笔,触摸等。
A pointer can be any point of contact on the screen made by a mouse cursor, pen, touch (including multi-touch), or other pointing input device
Installing
NPM等安装方法略
<script src="https://d3js.org/d3-dispatch.v1.min.js">script>
<script src="https://d3js.org/d3-selection.v1.min.js">script>
<script src="https://d3js.org/d3-drag.v1.min.js">script>
<script>
var drag = d3.drag();
script>
Try d3-drag in your browser.
API Reference
下面的表格描述了拖拽事件与原始Pointer Event之间的对应关系:
Event | Listening Element | Drag Event | Default Prevented? |
---|---|---|---|
mousedown? | selection | start | no¹ |
mousemove² | window¹ | drag | yes |
mouseup² | window¹ | end | yes |
dragstart² | window | - | yes |
selectstart² | window | - | yes |
click³ | window | - | yes |
touchstart | selection | start | no? |
touchmove | selection | drag | yes |
touchend | selection | end | no? |
touchcancel | selection | end | no? |
所有的事件传播会被阻止,如果要防止某些非拖拽事件触发拖拽事件监听器,则要设置 drag.filter来对事件进行过滤.
# d3.drag() <>
创建一个新的拖拽操作,返回一个drag, 它是一个对象方法。一般通过 selection.call将其应用在指定的选择集上.
# drag(selection) <>
将拖拽操作应用到指定的选择集。这个方法并不会直接使用,在内部通过使用 selection.call间接调用. 比如使用如下方法为选择集添加拖拽事件:
d3.selectAll(".node").call(d3.drag().on("start", started));
在内部,拖拽操作通过selection.on 来为元素添加监听事件. 事件监听器使用 .drag
来标识这是一个拖拽事件, 所以你可以通过如下方法来取消拖拽操作:
selection.on(".drag", null);
应用拖拽操作时,在IOS上也会同步改变 -webkit-tap-highlight-color 来高亮显示拖拽的元素,如果你想自定义高亮颜色,则需要在应用拖拽交互后移除或重新应用这个样式.
# drag.container([container]) <>
拖拽事件中,当前的鼠标坐标是常常被使用到的,那么当前的鼠标位置如何计算,是相对于哪个祖先元素?这就需要通过container来设置。
如果指定了container,则设置拖拽事件的相对祖先元素,并返回当前的拖拽对象。如果没有设置container则返回当前的祖先元素访问器,默认:
function container() {
return this.parentNode;
}
container指定了拖拽事件的坐标系统,对 event.x 和 event.y有影响. 由container访问器返回的元素最后会被传给d3.mouse 或 d3.touch 来计算事件的具体坐标.
默认的容器访问器返回选择集的父级元素,这种默认设置经常被用来操作SVG和HTML,因为这些元素通常使用相对父元素的定位来获取坐标。如果拖拽Canvas上的图元元素,则需要设置容器为元素自身:
function container() {
return this;
}
当然也可以直接设置,而不是使用访问器的形式,比如drag.container(canvas)
.
# drag.filter([filter]) <>
如果指定了 filter,则设置事件过滤器,并返回拖拽行为,如果没有指定 filter 则返回当前的过滤器,默认为:
function filter() {
return !d3.event.button;
}
如果过滤器返回false,则会忽略这个事件,并没有拖拽行为发生。过滤器决定了哪些输入事件应当被忽略。默认的过滤器会忽略辅助按钮上的鼠标按下事件,因为这些按钮通常有其他的目的,比如菜单按钮.
# drag.subject([subject]) <>
如果subject指定了,则设置当前的 subject 访问器为指定的对象或者函数,并返回当前的拖拽操作。如果没有指定subject,则返回当前的 subject 访问器,默认为:
function subject(d) {
return d == null ? {x: event.x, y: event.y} : d;
}
subject 表示 当前正被拖拽的东西。当接收到输入事件时被计算得到,比如鼠标按下或者开始触摸等在拖拽开始之前。然后 subject 作为event.subject被附加到drag events上。
默认的 subject 是接收拖拽事件的原始元素上绑定的datum;如果datum未定义,则表示指针坐标位置的对象会被创建。当在SVG中拖拽圆时,默认的subject就是当前被拖拽圆上绑定的数据。当使用Canvas时,默认的subject就是当前canvas元素上的datum(无论你点击的是canvas上的哪个位置)。当使用canvas时,自定义的subject访问器就显得非常重要了,比如在当前位置指定半径区域内选取一个圆表示当前被拖拽的对象:
function subject() {
var n = circles.length,
i,
dx,
dy,
d2,
s2 = radius * radius,
circle,
subject;
for (i = 0; i < n; ++i) {
circle = circles[i];
dx = d3.event.x - circle.x;
dy = d3.event.y - circle.y;
d2 = dx * dx + dy * dy;
if (d2 < s2) subject = circle, s2 = d2;
}
return subject;
}
如果需要的话,上述操作可以使用quadtree.find来加速。
返回的subject应该是一个包含x和y属性的对象,所以subject和指针在拖拽过程中的相对位置可以保持。如果subject为null或者undefined,则不会启动拖拽手势;然而,其他的起始点也可以启动 拖拽手势,参考drag.filter。
拖拽过程中的subject可能在拖拽过程中没有变化。拖拽访问器和selection.on事件会以相同的上下文和参数被调用:当前数据d,索引i以及指向当前DOM元素的this。在对subject访问器进行计算过程中,d3.event是一个先于drag event启动的事件。使用event.sourceEvent可以访问原始事件,event.identifier可以访问原始触摸标识符。event.x 和 event.y坐标是想对于container并使用d3.mouse 或 d3.touch计算的,
# drag.on(typenames, [listener]) <>
如果指定了listener,则将其设置为指定类型的回调事件,并返回拖拽对象。如果已经为执行的typename注册了监听器,则会将其覆盖。如果listener为null,则相当于移除typename对应的监听器。如果没有指定listener,则返回与typename匹配的第一个监听器。当事件发生时,对应的监听器将会被执行,并传递当前元素绑定的数据 d
和索引 i
, this
指向当前DOM元素.
typenames 是一个或多个由空格分割的字符串. 每个 typename 都是一个可以由(.
)分割的 type和name, 比如drag.foo
和 drag.bar
; name 允许为同一种type添加多个监听器,而type必须为如下几种:
start
- 开始拖拽 (对应 mousedown 或 touchstart).drag
- 拖拽中 (对应 mousemove 或 touchmove).end
- 拖拽结束 (对应 mouseup, touchend 或 touchcancel).
参考 dispatch.on 获取更多信息.
# d3.dragDisable(window) <>
阻止指定窗口上的原生拖拽和文本选择事件。
# d3.dragEnable(window[, noclick]) <>
启用指定window上的原生拖拽和文本选择事件,取消d3.dragDisable的影响.
Drag Events
When a drag event listener is invoked, d3.event is set to the current drag event. The event object exposes several fields:
target
- the associated drag behavior.type
- 事件类型, “start”, “drag” or “end”; 参考 drag.on.subject
- 由 drag.subject定义的.x
- 新的 x-坐标,鼠标当前的位置.y
- 新的 y-坐标,鼠标当前的位置.dx
- 相对与上个拖拽事件的x-方向的偏移.dy
- 相对与上个拖拽事件的y-方向的偏移.identifier
- 触发事件的设备, “mouse”, 或 touch identifier.active
- 当前活动的拖放操作数量.sourceEvent
- 底层原生事件, 比如mousemove 或 touchmove.
event.active 在多个拖放并发进行时是有用的。记录了同时进行拖放操作的数量。
The event object also exposes the event.on method.
# event.on(typenames, [listener]) <>
相当于drag.on, 但是只能应用于当前的拖拽操作。在进行拖拽之前,会创建一个event listeners 副本. 这个副本通过event.on被绑定到当前的拖拽操作。这可以用来创建一个临时的拖拽监听器,例如通过闭包创建一个临时的拖拽事件监听器:
function started() {
var circle = d3.select(this).classed("dragging", true);
d3.event.on("drag", dragged).on("end", ended);
function dragged(d) {
circle.raise().attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function ended() {
circle.classed("dragging", false);
}
}