d3-drag
拖放是一种流行且易于学习的指向手势:将指针移动到对象,保持按下来抓住它,将对象拖动到新的位置,然后释放。D3的拖拽行为提供了一个方便但灵活的抽象,用于在选集上启用拖放式交互。例如,您可以使用d3拖动来促进与力导向图的交互或模拟圆的碰撞:
你还可以使用d3-drag来实现自定义用户界面元素,例如滑块。但拖拽行为不仅仅是为了移动元素;有多种方式可以响应拖动手势。例如,你可以将其用于在散点图中以套索的形式选择元素,或者在画布上绘制线条:
拖动行为可以与其他行为相结合,例如用于缩放的d3-zoom。
拖动行为与DOM无关,因此你可以将其与SVG,HTML甚至Canvas一起使用!你可以使用高级选择技术来扩展它,例如Voronoi overlay或closest-target search:
最重要的是,拖动行为自动统一鼠标和触摸输入,并避免浏览器的特性。当指针事件更广泛可用时,拖动行为也会支持这些事件。
Installing
如果你使用npm,请键入npm install d3-drag
。否则,请下载最新版本。你也可以直接从d3js.org加载,作为standalone library(独立库)或D3 4.0的一部分来使用。支持AMD,CommonJS和vanilla环境。在vanilla环境下,会输出一个全局的d3
:
<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>
API Reference
此表描述了拖动行为如何解释本地事件:
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。
¹需要在iframe之外捕获事件;见#9。
²只适用于基于鼠标的活动手势;见#9。
³仅在一些基于鼠标的手势后才立即使用;请参阅drag.clickDistance。
⁴触摸输入需要点击仿真;见#9。
⁵如果在触摸手势在500毫秒内结束则忽略;假定点击仿真。
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);
应用拖动行为还会将-webkit-tap-highlight-color样式设置为透明,从而禁用iOS上的轻按突出显示。如果你需要不同的突出显示颜色,请在应用拖动行为后移除或重新应用此样式。
drag.container([container])
如果指定了container,则设置container accessor为指定的对象或函数,并返回此拖动行为。如果不指定container,则返回当前的container accessor,默认为:
function container() {
return this.parentNode;
}
拖动手势的container决定了后续拖动事件的坐标系,从而影响event.x和event.y。随后将container accessor返回的元素传递到d3.mouse或d3.touch,以确定指针的本地坐标。
默认container accessor返回接收到启动输入事件的原始选集中的元素的父节点(请参见drag)。拖动SVG或HTML元素时,这通常是合适的,因为这些元素通常相对于父级定位。但是,对于使用Canvas拖动的图形元素,你可能需要将容器重新定义为启动元素本身:
function container() {
return this;
}
或者,容器可以被直接指定为元素,例如drag.container(canvas)
。
drag.filter([filter])
如果指定了filter,设置过滤器为指定的函数,并返回此拖拽行为。如果不指定filter,则返回当前过滤器,默认为:
function filter() {
return !d3.event.button;
}
如果过滤器返回false,则启动事件将被忽略,并且不会启动拖动手势。因此,过滤器决定了忽略哪些输入事件;默认过滤器会忽略辅助按钮上的mousedown事件,因为这些按钮通常用于其他用途,例如上下文菜单。
drag.touchable([touchable])
如果指定了touchable,则设置触摸支持检测器为指定的函数并返回此拖动行为。如果不指定touchable,则返回当前的触摸支持检测器,默认为:
function touchable() {
return "ontouchstart" in this;
}
触摸事件监听器仅在检测器在应用拖动行为时相应元素返回true时才被注册。默认检测器适用于大多数能够触摸输入的浏览器,但不是全部;例如,Chrome的移动设备模拟器检测失败。
drag.subject([subject])
如果指定了subject,则设置subject accessor为指定的对象或函数并返回此拖拽行为。如果不指定subject,则返回当前的subject accessor,默认为:
function subject(d) {
return d == null ? {x: d3.event.x, y: d3.event.y} : d;
}
拖动手势的subject代表被拖动的东西。它在拖动手势开始之前,收到启动输入事件(例如mousedown或touchstart)时进行计算。然后主体在此手势的后续拖动事件中暴露为event.subject。
默认的主体是接收发起输入事件的原始选集中的元素的数据(请参见拖动);如果此数据未定义,则会创建一个代表指针坐标的对象。在SVG中拖动圆形元素时,默认主体是被拖动的圆的数据。在Canvas中,默认主体是画布元素的数据(无论你单击画布上的哪个位置)。在这种情况下,自定义subject accessor会更合适,例如在给定搜索半径内选择与鼠标最接近的圆:
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加速上述操作)。
返回的主体应该是一个对象,暴露出x
和y
属性,以便在拖动手势过程中可以保留主体和指针的相对位置。如果subject为null或undefined,则此指针不会启动拖动手势;然而,其他起始触摸可能启动拖动手势。另请参阅drag.filter。
手势开始后,拖动手势的主体可能不会更改。使用与selection.on监听器相同的上下文和参数调用subject accessor:当前数据d
和索引i
,this
上下文为当前DOM元素。在subject accessor求值期间,d3.event是一个拖动事件的启动前事件。可以使用event.sourceEvent访问启动输入事件,event.identifier访问触摸标识符。event.X和event.Y是相对于container的,并且适当的时候使用d3.mouse和d3.touch进行计算。
drag.clickDistance([distance])
如果指定了distance,则设置mousedown和mouseup过程中能触发后续点击事件之间移动的最大距离。如果在mousedown和mouseup之间的任何点,鼠标大于或等于从它按下位置的distance,mouseup后面的点击事件将被抑制。如果不指定distance,则返回当前距离阈值,默认为0。距离阈值是在客户端坐标(event.clientX和event .clientY)中测量的。
drag.on(typenames, [listener])
如果指定了listener,则为给定typenames的事件设置listener并返回此拖拽行为。如果已经注册了一个同类型及名称的监听器,,则会在添加新监听器之前移除现有的监听器。如果listener为null,则移除指定typenames当前的事件监听器(如果有的话)。如果不指定listener,返回第一个当前匹配typenames的 监听器(如果有的话)。当指定的事件派发时,将使用与selection.on监听器相同的上下文和参数调用每个监听器:当前数据d
和索引i
,this
上下文为当前DOM元素。
typenames是含有一个或多个由空格分隔的typename的字符串。每个typename都是一个type,可以选择跟一个句点(.
)和一个名称,比如drag.foo
和drag.bar;
该名称允许针对相同type注册多个监听器。type必须是以下之一:
start
- 新指针变为激活状态后(在mousedown或touchstart时)。drag
- 活动指针移动后(在mousemove或touchmove时)。end
- 活动指针变为非激活状态后(在mouseup,touchend或touchcancel时)。
另请参阅dispatch.on以了解更多。
在拖动手势期间通过drag.on更改已注册的监听器不会影响当前的拖动手势。相反,你必须使用event.on,它还允许你为当前拖动手势注册临时事件监听器。在拖动手势期间为每个活动指针派发单独的事件。例如,如果同时用多个手指拖动多个主体,则即使两个手指同时开始触摸,也会为每个手指派发启动事件。请参阅拖动事件以了解更多。
d3.dragDisable(window)
防止在指定的window上进行本地拖放和文本选择。作为防止mousedown事件默认行为的替代方法(另请参阅#9),此方法可防止mousedown后发生不需要的默认行为。在受支持的浏览器中,这意味着捕获dragstart和selectstart事件,阻止相关的默认操作,并立即停止它们的传播。在不支持选择事件的浏览器中,将CSS属性user-select在文档元素上设置为none。此方法旨在在mousedown上调用,然后在mouseup上调用d3.dragEnable。
d3.dragEnable(window[, noclick])
允许在指定的window上进行本地拖放和文本选择;撤销d3.dragDisable的效果。此方法旨在在mouseup上调用,在mousedown上以d3.dragDisable开头。如果noclick为true,则此方法也会暂时禁止click事件。抑制click事件在零毫秒超时后终止,这样它只会抑制紧跟在当前mouseup事件之后的click事件(如果有的话)。
Drag Events
当调用拖动事件监听器时,d3.event被设置为当前拖动事件。该event对象暴露出几个字段:
target
- 相关的拖动行为type
- “start”,“drag”,或“end”字符串;另请参阅drag.onsubject
- 拖动主体,由drag.subject定义x
- 主体的新x坐标;另请参阅drag.containery
- 主体的新y坐标;另请参与drag.containerdx
- 自上次拖动事件以来x坐标的变化dy
- 自上次拖动事件以来y坐标的变化identifier
- 字符串“mouse”,或可数的触摸标识符active
- 当前激活拖动手势的数量(在开始和结束时,不包括这一个)。sourceEvent
- 底层输入事件,如mousemove或touchmove
event.active字段对于在一系列并发拖拽手势中检测第一个开始事件和最后一个结束事件非常有用:第一个拖动手势开始时它为0,最后一个拖动手势结束时也为0。
event对象也暴露了event.on方法。
event.on(typenames, [listener])
等同于drag.on,但仅适用于当前的拖动手势。在拖动手势开始之前,制作当前拖动事件监听器的副本。该副本绑定到当前拖动手势,并通过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);
}
}