d3-drag
示例 · 拖放 是一种用于操作空间元素的常用交互方法:将指针移动到一个对象上,按住以抓取它,将对象“拖动”到新位置,然后松开以“放下”。D3 的拖动行为为拖放操作提供了灵活的抽象。例如,你可以在 力导向图 中拖动节点:
¥Examples · Drag-and-drop is a popular interaction method for manipulating spatial elements: move the pointer to an object, press and hold to grab it, “drag” the object to a new location, and release to “drop”. D3’s drag behavior provides a flexible abstraction for drag-and-drop. For example, you can drag nodes in a force-directed graph:
或者模拟圆形碰撞:
¥Or a simulation of colliding circles:
拖动行为不仅仅用于移动元素;响应拖动手势的方式多种多样。例如,你可以使用它来套索散点图中的元素,或在画布上绘制线条:
¥The drag behavior isn’t just for moving elements around; there are a variety of ways to respond to a drag gesture. For example, you can use it to lasso elements in a scatterplot, or to paint lines on a canvas:
拖动行为可以与其他行为结合使用,例如用于缩放的 d3-zoom 行为:
¥The drag behavior can be combined with other behaviors, such as d3-zoom for zooming:
拖动行为与 DOM 无关,因此你可以将其与 SVG、HTML 甚至 Canvas 一起使用!你可以使用高级选择技术(例如 Voronoi 叠加或最近目标搜索)来扩展它:
¥The drag behavior is agnostic about the DOM, so you can use it with SVG, HTML or even Canvas! And you can extend it with advanced selection techniques, such as a Voronoi overlay or a closest-target search:
拖动行为统一了鼠标和触摸输入,并避免了浏览器怪异现象。将来,拖动行为也将支持 指针事件。
¥The drag behavior unifies mouse and touch input and avoids browser quirks. In the future the drag behavior will support Pointer Events, too.
drag()
源代码 · 创建一个新的拖动行为。返回的行为 drag 既是对象又是函数,通常通过 selection.call 应用于选定元素。
¥Source · Creates a new drag behavior. The returned behavior, drag, is both an object and a function, and is typically applied to selected elements via selection.call.
const drag = d3.drag();
drag(selection) {#_drag}
源代码 · 将此拖动行为应用于指定的 selection。此函数通常不直接调用,而是通过 selection.call 调用。例如,实例化拖动行为并将其应用于选择对象:
¥Source · Applies this drag behavior to the specified selection. This function is typically not invoked directly, and is instead invoked via selection.call. For example, to instantiate a drag behavior and apply it to a selection:
d3.selectAll(".node").call(d3.drag().on("start", started));
在内部,拖动行为使用 selection.on 绑定拖动所需的事件监听器。监听器使用名称 .drag
,因此你可以按如下方式解除绑定拖动行为:
¥Internally, the drag behavior uses selection.on to bind the necessary event listeners for dragging. The listeners use the name .drag
, so you can subsequently unbind the drag behavior as follows:
selection.on(".drag", null);
应用拖动行为还会将 -webkit-tap-highlight-color 样式设置为透明,从而禁用 iOS 上的点击高亮。如果你想要不同的点击高亮颜色,请在应用拖动行为后移除或重新应用此样式。
¥Applying the drag behavior also sets the -webkit-tap-highlight-color style to transparent, disabling the tap highlight on iOS. If you want a different tap highlight color, remove or re-apply this style after applying the drag behavior.
drag.container(container) {#drag_container}
源代码 · 如果指定了 container,则将容器访问器设置为指定的对象或函数并返回拖动行为。如果未指定 container,则返回当前容器访问器,默认为:
¥Source · If container is specified, sets the container accessor to the specified object or function and returns the drag behavior. If container is not specified, returns the current container accessor, which defaults to:
function container() {
return this.parentNode;
}
拖动手势的容器决定了后续 拖动事件 的坐标系,影响 event.x 和 event.y。容器访问器返回的元素随后会传递给 pointer 来确定指针的本地坐标。
¥The container of a drag gesture determines the coordinate system of subsequent drag events, affecting event.x and event.y. The element returned by the container accessor is subsequently passed to pointer to determine the local coordinates of the pointer.
默认容器访问器返回接收初始输入事件的原始选择集(参见 drag)中元素的父节点。这通常适用于拖动 SVG 或 HTML 元素,因为这些元素通常相对于父元素定位。但是,对于使用 Canvas 拖动图形元素,你可能需要将容器重新定义为初始元素本身:
¥The default container accessor returns the parent node of the element in the originating selection (see drag) that received the initiating input event. This is often appropriate when dragging SVG or HTML elements, since those elements are typically positioned relative to a parent. For dragging graphical elements with a Canvas, however, you may want to redefine the container as the initiating element itself:
function container() {
return this;
}
或者,可以直接将容器指定为元素,例如 drag.container(canvas)
。
¥Alternatively, the container may be specified as the element directly, such as drag.container(canvas)
.
drag.filter(filter) {#drag_filter}
源代码 · 如果指定了 filter,则将事件过滤器设置为指定的函数并返回拖动行为。如果未指定 filter,则返回当前滤镜,默认为:
¥Source · If filter is specified, sets the event filter to the specified function and returns the drag behavior. If filter is not specified, returns the current filter, which defaults to:
function filter(event) {
return !event.ctrlKey && !event.button;
}
如果过滤器返回 false,则忽略启动事件,并且不会启动任何拖动手势。因此,过滤器决定忽略哪些输入事件;默认过滤器会忽略辅助按钮上的鼠标按下事件,因为这些按钮通常用于其他用途,例如上下文菜单。
¥If the filter returns falsey, the initiating event is ignored and no drag gestures are started. Thus, the filter determines which input events are ignored; the default filter ignores mousedown events on secondary buttons, since those buttons are typically intended for other purposes, such as the context menu.
drag.touchable(touchable) {#drag_touchable}
源代码 · 如果指定了 touchable,则将触摸支持检测器设置为指定的函数并返回拖动行为。如果未指定 touchable,则返回当前的触摸支持检测器,默认为:
¥Source · If touchable is specified, sets the touch support detector to the specified function and returns the drag behavior. If touchable is not specified, returns the current touch support detector, which defaults to:
function touchable() {
return navigator.maxTouchPoints || ("ontouchstart" in this);
}
仅当拖动行为为 applied 时,检测器返回相应元素的真值时,才会注册触摸事件监听器。默认检测器适用于大多数支持触摸输入的浏览器,但并非所有浏览器都适用;例如,Chrome 的移动设备模拟器检测失败。
¥Touch event listeners are only registered if the detector returns truthy for the corresponding element when the drag behavior is applied. The default detector works well for most browsers that are capable of touch input, but not all; Chrome’s mobile device emulator, for example, fails detection.
drag.subject(subject) {#drag_subject}
源代码 · 如果指定了 subject,则将 subject 访问器设置为指定的对象或函数,并返回拖动行为。如果未指定主体,则返回当前主体访问器,默认为:
¥Source · If subject is specified, sets the subject accessor to the specified object or function and returns the drag behavior. If subject is not specified, returns the current subject accessor, which defaults to:
function subject(event, d) {
return d == null ? {x: event.x, y: event.y} : d;
}
拖动手势的主体代表被拖动的对象。它在拖动手势开始前立即接收到初始输入事件(例如鼠标按下或触摸启动)时计算。主体随后在该手势的后续 拖动事件 中以 event.subject 的形式暴露。
¥The subject of a drag gesture represents the thing being dragged. It is computed when an initiating input event is received, such as a mousedown or touchstart, immediately before the drag gesture starts. The subject is then exposed as event.subject on subsequent drag events for this gesture.
默认主体是接收初始输入事件的原始选择(参见 drag)中元素的 datum;如果此日期未定义,则创建一个表示指针坐标的对象。因此,在 SVG 中拖动圆形元素时,默认主体是被拖动圆形的基准面。对于 Canvas,默认主题是画布元素的基准(无论你在画布上点击什么位置)。在这种情况下,自定义主题访问器可能更合适,例如,在给定的搜索半径内选择距离鼠标最近的圆:
¥The default subject is the datum of the element in the originating selection (see drag) that received the initiating input event; if this datum is undefined, an object representing the coordinates of the pointer is created. When dragging circle elements in SVG, the default subject is thus the datum of the circle being dragged. With Canvas, the default subject is the canvas element’s datum (regardless of where on the canvas you click). In this case, a custom subject accessor would be more appropriate, such as one that picks the closest circle to the mouse within a given search radius:
function subject(event) {
let n = circles.length,
i,
dx,
dy,
d2,
s2 = radius * radius,
circle,
subject;
for (i = 0; i < n; ++i) {
circle = circles[i];
dx = event.x - circle.x;
dy = event.y - circle.y;
d2 = dx * dx + dy * dy;
if (d2 < s2) subject = circle, s2 = d2;
}
return subject;
}
提示
如有必要,可以使用 quadtree.find、simulation.find 或 delaunay.find 加速上述操作。
¥If necessary, the above can be accelerated using quadtree.find, simulation.find or delaunay.find.
返回的主体节点应为一个公开 x
和 y
属性的对象,以便在拖动手势期间可以保留主体节点和指针的相对位置。如果 subject 为空或未定义,则不会为此指针启动任何拖动手势;但是,其他起始触摸仍可能启动拖动手势。另请参阅 drag.filter。
¥The returned subject should be an object that exposes x
and y
properties, so that the relative position of the subject and the pointer can be preserved during the drag gesture. If the subject is null or undefined, no drag gesture is started for this pointer; however, other starting touches may yet start drag gestures. See also drag.filter.
拖动手势的主体在手势开始后不得更改。主体访问器使用与 selection.on 监听器相同的上下文和参数调用:当前事件 (event
) 和数据 d
,以 this
上下文作为当前 DOM 元素。在评估主题访问器期间,event
是 beforestart 拖动事件。Use event.sourceEvent to access the initiating input event and event.identifier to access the touch identifier.event.x 和 event.y 相对于 container,并使用 pointer 计算。
¥The subject of a drag gesture may not be changed after the gesture starts. The subject accessor is invoked with the same context and arguments as selection.on listeners: the current event (event
) and datum d
, with the this
context as the current DOM element. During the evaluation of the subject accessor, event
is a beforestart drag event. Use event.sourceEvent to access the initiating input event and event.identifier to access the touch identifier. The event.x and event.y are relative to the container, and are computed using pointer.
drag.clickDistance(distance) {#drag_clickDistance}
源代码 · 如果指定了 distance,则设置鼠标在 mousedown 和 mouseup 之间可以移动的最大距离,此距离会触发后续的点击事件。如果在 mousedown 和 mouseup 之间的任何时间点,鼠标距离 mousedown 位置的距离大于或等于 distance ,则 mouseup 之后的点击事件将被抑制。如果未指定距离,则返回当前距离阈值,默认为零。距离阈值以客户端坐标(event.clientX 和 event.clientY)为单位进行测量。
¥Source · If distance is specified, sets the maximum distance that the mouse can move between mousedown and mouseup that will trigger a subsequent click event. If at any point between mousedown and mouseup the mouse is greater than or equal to distance from its position on mousedown, the click event following mouseup will be suppressed. If distance is not specified, returns the current distance threshold, which defaults to zero. The distance threshold is measured in client coordinates (event.clientX and event.clientY).
drag.on(typenames, listener) {#drag_on}
源代码 · 如果指定了 listener,则为指定的 typenames 设置事件监听器并返回拖动行为。如果已为相同类型和名称注册了事件监听器,则在添加新监听器之前,会先移除现有监听器。如果 listener 为 null,则移除指定 typenames 的当前事件监听器(如果有)。如果未指定 listener,则返回第一个当前已分配的、与指定 typenames 匹配的监听器(如果有)。当指定事件被调度时,每个监听器都将使用与 selection.on 监听器相同的上下文和参数进行调用:当前事件 (event
) 和数据 d
,以 this
上下文作为当前 DOM 元素。
¥Source · If listener is specified, sets the event listener for the specified typenames and returns the drag behavior. If an event listener was already registered for the same type and name, the existing listener is removed before the new listener is added. If listener is null, removes the current event listeners for the specified typenames, if any. If listener is not specified, returns the first currently-assigned listener matching the specified typenames, if any. When a specified event is dispatched, each listener will be invoked with the same context and arguments as selection.on listeners: the current event (event
) and datum d
, with the this
context as the current DOM element.
typenames 是一个字符串,包含一个或多个用空格分隔的 typename。每个类型名称都是一种类型,后面可以跟一个句点 (.
) 和一个名称,例如 drag.foo
和 drag.bar
;该名称允许为同一类型注册多个监听器。类型必须是以下之一:
¥The typenames is a string containing one or more typename separated by whitespace. Each typename is a type, optionally followed by a period (.
) and a name, such as drag.foo
and drag.bar
; the name allows multiple listeners to be registered for the same type. The type must be one of the following:
start
- 新指针变为活动状态后(mousedown 或 touchstart 时)。¥
start
- after a new pointer becomes active (on mousedown or touchstart).drag
- 活动指针移动后(mousemove 或 touchmove 时)。¥
drag
- after an active pointer moves (on mousemove or touchmove).end
- 活动指针变为非活动状态后(mouseup、touchend 或 touchcancel 时)。¥
end
- after an active pointer becomes inactive (on mouseup, touchend or touchcancel).
更多信息请参阅 dispatch.on。
¥See dispatch.on for more.
在拖动手势期间通过 drag.on 更改已注册的监听器不会影响当前拖动手势。相反,你必须使用 event.on,它还允许你为当前拖动手势注册临时事件监听器。在拖动手势期间,会为每个活动指针调度单独的事件。例如,如果同时用多根手指拖动多个对象,即使两根手指同时开始触摸,也会为每个手指调度一个 start 事件。更多信息请参阅 拖动事件。
¥Changes to registered listeners via drag.on during a drag gesture do not affect the current drag gesture. Instead, you must use event.on, which also allows you to register temporary event listeners for the current drag gesture. Separate events are dispatched for each active pointer during a drag gesture. For example, if simultaneously dragging multiple subjects with multiple fingers, a start event is dispatched for each finger, even if both fingers start touching simultaneously. See Drag Events for more.
dragDisable(window)
源代码 · 阻止在指定窗口上进行原生拖放和文本选择。作为阻止 mousedown 事件默认操作(参见 #9)的替代方法,此方法可阻止 mousedown 事件后出现不良的默认操作。在支持的浏览器中,这意味着捕获 dragstart 和 selectstart 事件,阻止相关的默认操作,并立即停止它们的传播。在不支持选择事件的浏览器中,文档元素上的 user-select CSS 属性会被设置为 none。此方法应在鼠标按下时调用,然后在鼠标弹起时调用 dragEnable。
¥Source · Prevents native drag-and-drop and text selection on the specified window. As an alternative to preventing the default action of mousedown events (see #9), this method prevents undesirable default actions following mousedown. In supported browsers, this means capturing dragstart and selectstart events, preventing the associated default actions, and immediately stopping their propagation. In browsers that do not support selection events, the user-select CSS property is set to none on the document element. This method is intended to be called on mousedown, followed by dragEnable on mouseup.
dragEnable(window, noclick)
源代码 · 允许在指定窗口上进行原生拖放和文本选择;撤消 dragDisable 的效果。此方法应在鼠标弹起时调用,并在鼠标按下时先调用 dragDisable。如果 noclick 为 true,此方法还会暂时抑制点击事件。点击事件的抑制在零毫秒超时后到期,因此它只会抑制紧随当前鼠标悬停事件之后的点击事件(如果有)。
¥Source · Allows native drag-and-drop and text selection on the specified window; undoes the effect of dragDisable. This method is intended to be called on mouseup, preceded by dragDisable on mousedown. If noclick is true, this method also temporarily suppresses click events. The suppression of click events expires after a zero-millisecond timeout, such that it only suppress the click event that would immediately follow the current mouseup event, if any.
拖动事件
¥Drag events
调用 拖动事件监听器 时,它会接收当前的拖动事件作为其第一个参数。事件对象暴露了几个字段:
¥When a drag event listener is invoked, it receives the current drag event as its first argument. The event object exposes several fields:
target
- 相关的 拖动行为。¥
target
- the associated drag behavior.type
- 字符串“start”、“drag”或“end”;参见 drag.on。¥
type
- the string “start”, “drag” or “end”; see drag.on.subject
- 拖动对象,由 drag.subject 定义。¥
subject
- the drag subject, defined by drag.subject.x
- 目标对象新的 x 坐标;参见 drag.container。¥
x
- the new x-coordinate of the subject; see drag.container.y
- 目标对象新的 y 坐标;参见 drag.container。¥
y
- the new y coordinate of the subject; see drag.container.dx
- 自上次拖动事件以来 x 坐标的变化。¥
dx
- the change in x-coordinate since the previous drag event.dy
- 自上次拖动事件以来 y 坐标的变化。¥
dy
- the change in y coordinate since the previous drag event.identifier
- 字符串“mouse”或数值 触摸标识符。¥
identifier
- the string “mouse”, or a numeric touch identifier.active
- 当前活动的拖动手势数量(起始和结束,不包括本次)。¥
active
- the number of currently active drag gestures (on start and end, not including this one).sourceEvent
- 底层输入事件,例如 mousemove 或 touchmove。¥
sourceEvent
- the underlying input event, such as mousemove or touchmove.
event.active 字段用于检测一系列并发拖动手势中的第一个开始事件和最后一个结束事件:当第一个拖动手势开始时,它为 0,当最后一个拖动手势结束时,它为 0。
¥The event.active field is useful for detecting the first start event and the last end event in a sequence of concurrent drag gestures: it is zero when the first drag gesture starts, and zero when the last drag gesture ends.
事件对象也暴露了 event.on 方法。
¥The event object also exposes the event.on method.
下表描述了拖动行为如何解释原生事件:
¥This table describes how the drag behavior interprets native events:
事件 | 监听元素 | 拖拽事件 | 是否阻止默认设置? |
---|---|---|---|
鼠标按下⁵ | selection | start | 否¹ |
鼠标移动² | window¹ | drag | yes |
鼠标弹起² | window¹ | end | yes |
dragstart² | window | * | yes |
selectstart² | window | * | yes |
点击³ | window | * | yes |
touchstart | selection | start | 否⁴ |
touchmove | selection | drag | yes |
touchend | selection | end | 否⁴ |
touchcancel | selection | end | 否⁴ |
所有消费事件的传播都是 立即停止。如果你想阻止某些事件触发拖动手势,请使用 drag.filter。
¥The propagation of all consumed events is immediately stopped. If you want to prevent some events from initiating a drag gesture, use drag.filter.
¹ 捕获 iframe 外部事件所必需的;参见 #9。
² 仅在鼠标手势活动期间生效;参见 #9。
³ 仅在某些鼠标手势之后立即生效;参见 drag.clickDistance。
⁴ 触摸输入时允许 点击模拟 的必要条件;参见 #9。
⁵ 如果在触摸手势结束后 500 毫秒内,则忽略;假设 点击模拟。
¥¹ Necessary to capture events outside an iframe; see #9.
² Only applies during an active, mouse-based gesture; see #9.
³ Only applies immediately after some mouse-based gestures; see drag.clickDistance.
⁴ Necessary to allow click emulation on touch input; see #9.
⁵ Ignored if within 500ms of a touch gesture ending; assumes click emulation.
event.on(typenames, listener) {#event_on}
源代码 · 等同于 drag.on,但仅适用于当前拖动手势。在拖动手势开始之前,会生成当前拖动 事件监听器 的 copy。此副本绑定到当前拖动手势,并由 event.on 修改。这对于仅接收当前拖动手势事件的临时监听器很有用。例如,此开始事件监听器将临时拖动和结束事件监听器注册为闭包:
¥Source · Equivalent to drag.on, but only applies to the current drag gesture. Before the drag gesture starts, a copy of the current drag event listeners is made. This copy is bound to the current drag gesture and modified by event.on. This is useful for temporary listeners that only receive events for the current drag gesture. For example, this start event listener registers temporary drag and end event listeners as closures:
function started(event) {
const circle = d3.select(this).classed("dragging", true);
const dragged = (event, d) => circle.raise().attr("cx", d.x = event.x).attr("cy", d.y = event.y);
const ended = () => circle.classed("dragging", false);
event.on("drag", dragged).on("end", ended);
}