Skip to content

d3-drag

示例 · 拖放 是一种流行的用于操作空间元素的交互方式:将指针移动到一个对象上,按住不放以抓取它,“拖动”对象到新位置,然后释放以“放下”。D3 的拖动行为为拖放提供了灵活的抽象。例如,你可以在 力导向图 中拖动节点:

或者模拟圆形碰撞:

🌐 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 应用于选定的元素。

js
const drag = d3.drag();

拖动(选择)

🌐 drag(selection)

来源 · 将此拖动行为应用于指定的 选择。此函数通常不直接调用,而是通过 选择.call 调用。例如,要实例化一个拖动行为并将其应用于一个选择:

js
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:

js
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)

来源 · 如果指定了 container,则将容器访问器设置为指定的对象或函数,并返回拖拽行为。如果未指定 container,则返回当前容器访问器,默认值为:

js
function container() {
  return this.parentNode;
}

拖拽手势的容器决定后续拖拽事件的坐标系统,影响event.x 和 event.y。容器访问器返回的元素随后被传递给指针以确定指针的本地坐标。

🌐 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:

js
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)

来源 · 如果指定了 filter,则将事件过滤器设置为指定的函数,并返回拖拽行为。如果未指定 filter,则返回当前过滤器,默认值为:

js
function filter(event) {
  return !event.ctrlKey && !event.button;
}

如果过滤器返回假值,则启动事件将被忽略,并且不会开始拖动手势。因此,过滤器决定哪些输入事件会被忽略;默认过滤器会忽略次要按钮的鼠标按下事件,因为这些按钮通常用于其他用途,例如上下文菜单。

🌐 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)

来源 · 如果指定了 touchable,则将触摸支持检测器设置为指定的函数,并返回拖动行为。如果未指定 touchable,则返回当前的触摸支持检测器,默认值为:

js
function touchable() {
  return navigator.maxTouchPoints || ("ontouchstart" in this);
}

只有当检测器在拖动行为应用时对相应元素返回真值时,才会注册触摸事件监听器。默认检测器对于大多数支持触摸输入的浏览器工作良好,但并非全部;例如,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)

来源 · 如果指定了 subject,则将主题访问器设置为指定的对象或函数,并返回拖拽行为。如果未指定 subject,则返回当前的主题访问器,默认值为:

js
function subject(event, d) {
  return d == null ? {x: event.x, y: event.y} : d;
}

拖动手势的主体表示被拖动的事物。当接收到一个启动输入事件(例如 mousedown 或 touchstart)时,会计算主体,就在拖动手势开始之前。随后,该主体会在此手势的后续拖动事件中以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.

默认主题是接收初始输入事件的源选择中元素的数据(参见拖动);如果该数据未定义,则会创建一个表示指针坐标的对象。在 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:

js
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;
}

TIP

如果有必要,上述操作可以使用 quadtree.findsimulation.finddelaunay.find 加速。

返回的主体应该是一个公开 xy 属性的对象,以便在拖动手势过程中可以保持主体和指针的相对位置。如果主体为 null 或 undefined,则不会为该指针启动拖动手势;然而,其他起始触摸仍可能启动拖动手势。另请参见 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 拖拽事件。使用 event.sourceEvent 访问触发输入事件,使用 event.identifier 访问触控标识符。event.x 和 event.y 相对于 容器,并使用 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)

来源 · 如果指定了 distance,则设置鼠标在 mousedown 和 mouseup 之间允许移动的最大距离,以触发后续的点击事件。如果在 mousedown 和 mouseup 之间的任何时刻,鼠标与 mousedown 时的位置的距离大于或等于 distance,则 mouseup 后的点击事件将被抑制。如果未指定 distance,则返回当前的距离阈值,默认为零。距离阈值以客户端坐标(event.clientXevent.clientY)来衡量。

drag.on(typenames, listener)

来源 · 如果指定了 listener,则为指定的 typenames 设置事件 listener 并返回拖动行为。如果同一类型和名称的事件监听器已注册,则在添加新的监听器之前会先移除现有的监听器。如果 listener 为 null,则移除指定 typenames 的当前事件监听器(如果有)。如果未指定 listener,则返回匹配指定 typenames 的第一个当前分配的监听器(如果有)。当分发指定事件时,每个 listener 将以与 selection.on 监听器相同的上下文和参数被调用:当前事件 (event) 和数据 d,其中 this 作为当前 DOM 元素的上下文。

typenames 是一个包含一个或多个 typename 的字符串,它们由空格分隔。每个 typename 是一个 type,可选地后跟一个句点(.)和一个 name,例如 drag.foodrag.bar;该名称允许为相同的 type 注册多个监听器。type 必须是以下之一:

🌐 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 时)。
  • drag - 活动指针移动后(mousemove 或 touchmove 时)。
  • end - 活动指针变为非活动状态后(mouseup、touchend 或 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(窗口)

🌐 dragDisable(window)

来源 · 防止指定 窗口 上的原生拖放和文本选择。作为阻止 mousedown 事件默认行为(参见 #9)的替代方法,此方法防止 mousedown 后出现不希望的默认操作。在支持的浏览器中,这意味着捕获 dragstart 和 selectstart 事件,阻止相关的默认操作,并立即停止它们的传播。在不支持选择事件的浏览器中,会将文档元素的 user-select CSS 属性设置为 none。此方法旨在在 mousedown 时调用,然后在 mouseup 时调用 dragEnable

dragEnable(窗口, 无点击)

🌐 dragEnable(window, noclick)

来源 · 允许在指定的 窗口 上进行本地拖放和文本选择;撤销 dragDisable 的效果。该方法旨在在 mouseup 时调用,之前在 mousedown 时调用 dragDisable。如果 noclick 为 true,该方法还会临时抑制点击事件。点击事件的抑制将在零毫秒超时后失效,因此它只会抑制紧随当前 mouseup 事件之后可能发生的点击事件。

拖动事件

🌐 Drag events

当调用 拖拽事件监听器 时,它会将当前的拖拽事件作为第一个参数传入。event 对象包含多个字段:

🌐 When a drag event listener is invoked, it receives the current drag event as its first argument. The event object exposes several fields:

  • target - 相关的 拖动行为
  • type - 字符串 “start”、“drag” 或 “end”;参见 drag.on
  • subject - 拖拽对象,由 drag.subject 定义。
  • x - 主体的新 x 坐标;参见 drag.container
  • y - 主体的新 y 坐标;参见 drag.container
  • dx - 自上一次拖动事件以来 x 坐标的变化。
  • dy - 自上次拖动事件以来 y 坐标的变化。
  • identifier - 字符串“mouse”,或一个数字 触控标识符
  • active - 当前活动的拖动手势数量(起始和结束,不包括本次)。
  • sourceEvent - 底层输入事件,例如 mousemove 或 touchmove。

event.active 字段对于检测一系列并发拖动手势中的第一个开始事件和最后一个结束事件非常有用:当第一个拖动手势开始时它为零,当最后一个拖动手势结束时它也为零。

🌐 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 对象还公开了 event.on 方法。

🌐 The event object also exposes the event.on method.

下表描述了拖动行为如何解释原生事件:

🌐 This table describes how the drag behavior interprets native events:

事件监听元素拖拽事件是否阻止默认行为?
mousedown⁵selectionstart否¹
mousemove²window¹drag
mouseup²window¹end
dragstart²window-
selectstart²window-
click³window-
touchstartselectionstart否⁴
touchmoveselectiondrag
touchendselectionend否⁴
touchcancelselectionend否⁴

所有已消费事件的传播都会立即停止。如果你想防止某些事件启动拖动手势,请使用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 毫秒内,则忽略;假定 点击模拟

event.on(typenames, listener)

来源 · 等同于 drag.on,但仅适用于当前的拖动手势。在拖动手势开始之前,会创建当前拖动 事件监听器 的一个 副本。这个副本绑定到当前的拖动手势,并通过 event.on 进行修改。这对于只接收当前拖动手势事件的临时监听器非常有用。例如,这个 start 事件监听器以闭包的形式注册了临时的拖动和结束事件监听器:

js
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);
}