d3-drag
示例 · 拖放 是一种流行的交互方式,用于操作空间元素:将指针移动到某个对象上,按下并按住以抓取它,“拖动”该对象到新位置,然后释放以“放置”。D3 的拖动行为为拖放提供了一个灵活的抽象。例如,您可以在 力导向图 中拖动节点
或一个模拟碰撞圆圈的动画
拖动行为不仅仅用于移动元素;还有很多方法可以响应拖动操作。例如,您可以使用它在散点图中套索元素,或在画布上绘制线条
拖动行为可以与其他行为结合使用,例如 d3-zoom 用于缩放
拖动行为对 DOM 并不关心,因此您可以将其用于 SVG、HTML 甚至 Canvas!您可以使用高级选择技术扩展它,例如 Voronoi 叠加或最近目标搜索
拖动行为统一了鼠标和触摸输入,并避免了浏览器问题。将来,拖动行为也将支持 指针事件。
drag()
源代码 · 创建一个新的拖动行为。返回的行为,drag,既是一个对象,也是一个函数,通常通过 selection.call 应用于选定的元素。
const drag = d3.drag();
drag(selection)
源代码 · 将此拖动行为应用于指定的 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,则返回当前容器访问器,其默认值为
function container() {
return this.parentNode;
}
拖动操作的 container 决定后续 拖动事件 的坐标系,影响 event.x 和 event.y。容器访问器返回的元素随后将传递给 pointer 以确定指针的本地坐标。
默认容器访问器返回触发初始输入事件的元素(参见 drag)在源选择中的父节点。当拖动 SVG 或 HTML 元素时,这通常是合适的,因为这些元素通常相对于父节点定位。但是,对于使用 Canvas 拖动图形元素,您可能需要将容器重新定义为触发元素本身
function container() {
return this;
}
或者,容器可以指定为元素本身,例如 drag.container(canvas)
。
drag.filter(filter)
源代码 · 如果指定了 filter,则将事件过滤器设置为指定函数,并返回拖动行为。如果未指定 filter,则返回当前过滤器,其默认值为
function filter(event) {
return !event.ctrlKey && !event.button;
}
如果过滤器返回假值,则会忽略初始事件,并且不会启动任何拖动操作。因此,过滤器决定哪些输入事件会被忽略;默认过滤器会忽略辅助按钮上的 mousedown 事件,因为这些按钮通常用于其他目的,例如上下文菜单。
drag.touchable(touchable)
源代码 · 如果指定了 touchable,则将触摸支持检测器设置为指定函数,并返回拖动行为。如果未指定 touchable,则返回当前触摸支持检测器,其默认值为
function touchable() {
return navigator.maxTouchPoints || ("ontouchstart" in this);
}
仅当检测器在 应用 拖动行为时针对对应元素返回真值时,才会注册触摸事件监听器。默认检测器适用于大多数能够进行触摸输入的浏览器,但并非全部;例如,Chrome 的移动设备模拟器无法检测到。
drag.subject(subject)
源代码 · 如果指定了 subject,则将主体访问器设置为指定的对象或函数,并返回拖动行为。如果未指定 subject,则返回当前主体访问器,其默认值为
function subject(event, d) {
return d == null ? {x: event.x, y: event.y} : d;
}
拖动操作的 subject 代表 被拖动的对象。它是在接收初始输入事件(如 mousedown 或 touchstart)时计算的,就在拖动操作开始之前。然后,主体作为 event.subject 在随后的 拖动事件 中被公开,以用于此操作。
默认主体是触发初始输入事件的元素(参见 drag)在源选择中的 datum;如果此 datum 未定义,则会创建一个表示指针坐标的对象。因此,当在 SVG 中拖动圆形元素时,默认主体是被拖动的圆形的 datum。对于 Canvas,默认主体是画布元素的 datum(无论您在画布上单击何处)。在这种情况下,自定义主体访问器会更合适,例如,一个在给定的搜索 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 加速。
返回的主体应是一个对象,公开 x
和 y
属性,以便在拖动操作期间可以保留主体和指针的相对位置。如果主体为 null 或 undefined,则不会为该指针启动拖动操作;但是,其他开始触摸仍可以启动拖动操作。另请参见 drag.filter。
拖动操作的主体在操作开始后不可更改。主体访问器使用与 selection.on 监听器相同的上下文和参数进行调用:当前事件 (event
) 和 datum d
,其中 this
上下文为当前 DOM 元素。在主体访问器评估期间,event
是一个 beforestart 拖动事件。使用 event.sourceEvent 访问初始输入事件,使用 event.identifier 访问触摸标识符。event.x 和 event.y 相对于 container,并且使用 pointer 计算。
drag.clickDistance(distance)
源代码 · 如果指定了 distance,则设置鼠标在 mousedown 和 mouseup 之间可以移动的最大距离,该距离将触发后续的单击事件。如果在 mousedown 和 mouseup 之间的任何时间点,鼠标距离其在 mousedown 时的位置大于或等于 distance,则后续 mouseup 的单击事件将被抑制。如果未指定 distance,则返回当前距离阈值,其默认值为零。距离阈值以客户端坐标测量 (event.clientX 和 event.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.foo
和 drag.bar
;该名称允许为相同type 注册多个监听器。type 必须是以下之一
start
- 新指针变为活动状态后(鼠标按下或触控开始)。drag
- 活动指针移动后(鼠标移动或触控移动)。end
- 活动指针变为非活动状态后(鼠标抬起、触控结束或触控取消)。
有关更多信息,请参阅 dispatch.on。
在拖动操作期间,通过drag.on 对注册的监听器进行的更改不会影响当前的拖动操作。相反,您必须使用 event.on,它也允许您为当前拖动操作注册临时事件监听器。在拖动操作期间,每个活动指针都会分发单独的事件。例如,如果用多个手指同时拖动多个主题,则每个手指都会分发一个开始事件,即使两个手指同时开始触摸也是如此。有关更多信息,请参阅 拖动事件。
dragDisable(window)
源代码 · 在指定的window 上阻止本机拖放和文本选择。作为阻止鼠标按下事件的默认操作(请参阅 #9)的替代方法,此方法阻止鼠标按下后出现的意外默认操作。在支持的浏览器中,这意味着捕获 dragstart 和 selectstart 事件,阻止其关联的默认操作,并立即停止其传播。在不支持选择事件的浏览器中,将在文档元素上将 user-select CSS 属性设置为 none。此方法旨在在鼠标按下时调用,随后在鼠标抬起时调用 dragEnable。
dragEnable(window, noclick)
源代码 · 允许在指定的window 上进行本机拖放和文本选择;撤消dragDisable 的效果。此方法旨在在鼠标抬起时调用,在鼠标按下时调用dragDisable 之前调用。如果noclick 为 true,此方法还会暂时抑制点击事件。点击事件的抑制将在零毫秒超时后过期,因此它只会抑制紧随当前鼠标抬起事件的点击事件(如果有)。
拖动事件
当调用 拖动事件监听器 时,它会接收当前拖动事件作为其第一个参数。event 对象公开了几个字段
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
- 底层输入事件,例如鼠标移动或触控移动。
event.active 字段用于检测一系列并发拖动操作中的第一个开始事件和最后一个结束事件:当第一个拖动操作开始时它为零,当最后一个拖动操作结束时它为零。
event 对象还公开了 event.on 方法。
此表描述了拖动行为如何解释本机事件
事件 | 监听元素 | 拖动事件 | 阻止默认操作? |
---|---|---|---|
mousedown⁵ | 选择 | start | 否¹ |
mousemove² | window¹ | drag | 是 |
mouseup² | window¹ | end | 是 |
dragstart² | window | - | 是 |
selectstart² | window | - | 是 |
click³ | window | - | 是 |
touchstart | 选择 | start | 否⁴ |
touchmove | 选择 | drag | 是 |
touchend | 选择 | end | 否⁴ |
touchcancel | 选择 | end | 否⁴ |
所有已消耗事件的传播都已立即停止。如果您想阻止某些事件启动拖动操作,请使用 drag.filter。
¹ 需要捕获 iframe 外部的事件;请参阅 #9。
² 仅适用于活动鼠标操作;请参阅 #9。
³ 仅适用于某些鼠标操作之后立即发生的操作;请参阅 drag.clickDistance。
⁴ 需要允许 点击模拟 触控输入;请参阅 #9。
⁵ 如果在触控手势结束后的 500 毫秒内发生,则会忽略;假设 点击模拟。
event.on(typenames, listener)
源代码 · 等效于 drag.on,但仅适用于当前拖动操作。在拖动操作开始之前,会创建一个当前拖动 事件监听器 的 副本。此副本绑定到当前拖动操作,并通过 event.on 进行修改。这对于仅接收当前拖动操作事件的临时监听器非常有用。例如,此开始事件监听器注册了临时拖动和结束事件监听器作为闭包
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);
}