首页
/ D3 d3-zoom 缩放行为完全指南:从手势交互到 ZoomTransform 变换矩阵

D3 d3-zoom 缩放行为完全指南:从手势交互到 ZoomTransform 变换矩阵

2026-09-06 14:04:29作者:郁楠烈Hubert

d3-zoom 是 D3 中负责"平移与缩放"(pan & zoom)的官方行为模块:通过拖拽平移、滚轮缩放、触摸捏合等直接操作,让用户聚焦到感兴趣的区域。本指南基于本仓库文档 docs/d3-zoom.md 完整梳理 d3-zoom 的 API——从创建与挂载缩放行为、*zoom* 的全部配置方法、zoomTransform 变换矩阵的读写,到编程式控制(zoom.transform 及三个便捷方法)与底层平滑缩放插值(van Wijk & Nuij 算法),并结合本仓库 src/index.jstest/d3-test.js 说明 d3-zoom 在 d3 v7 中的组织方式。读完后你能独立为 SVG/Canvas/HTML 可视化搭建完整的可交互缩放方案,并能用代码驱动带过渡动画的缩放。

一、d3-zoom 的定位与能力边界

平移和缩放是 Web 地图交互的核心,同样适用于密集时间序列、散点图等可视化场景。根据 docs/d3-zoom.md,zoom 行为是一个"灵活的抽象",它处理了令人意外的多种输入模态和浏览器怪癖:

  • DOM 无关:可以在 HTML、SVG 或 Canvas 上直接使用;
  • 与其他模块组合:可与 d3-scaled3-axis 组合实现坐标轴缩放联动,与 d3-drag 组合实现拖拽,与 d3-brush 组合实现 focus + context(焦点+上下文)视图;
  • 可编程控制:通过 *zoom*.transform 可以以代码驱动缩放,实现缩放按钮 UI 或数据漫游动画;
  • 平滑过渡:基于 Jarke J. van Wijk 和 Wim A.A. Nuij 的论文 "Smooth and efficient zooming and panning" 实现的 interpolateZoom 插值。

本仓库中的位置。当前仓库是 D3 的"全家桶"集成包,版本 7.9.0(见 package.json)。package.json 中声明了依赖 "d3-zoom": "^3.0.0",而 src/index.js 通过一行 export * from "d3-zoom"; 将 zoom 的全部导出(zoomzoomIdentityZoomTransform 等)并入 d3 命名空间。因此你不需要单独安装 d3-zoom——安装 d3 后即可使用 d3.zoom() 等全部 API。这一"全量再导出"关系由测试 test/d3-test.js 强制保障:该测试遍历 package.json 的每个依赖模块,断言 d3 导出其中每一个属性(version 除外),保证 d3.zoom 等符号永远存在。

// 本仓库 test/d3-test.js 的核心断言逻辑(节选)
for (const moduleName in packageData.dependencies) {
  it(`d3 exports everything from ${moduleName}`, async () => {
    const module = await import(moduleName);
    for (const propertyName in module) {
      if (propertyName !== "version") {
        assert(propertyName in d3, `${moduleName} exports ${propertyName}`);
      }
    }
  });
}

从源码结构看,zoom 的具体实现位于 d3-zoom 独立包(src/zoom.jssrc/transform.js),本仓库作为上层聚合包只做再导出;这意味着本仓库文档 docs/d3-zoom.md 中每个方法标注的 "Source: d3-zoom/src/zoom.js" 指向的就是那个上游实现。

二、创建并挂载缩放行为:zoom() 与 zoom(selection)

2.1 zoom() 创建行为

d3.zoom() 创建一个新的缩放行为。返回的 zoom 既是对象又是函数:

  • 作为对象,它提供一整套链式配置方法(scaleExtentfilteron 等,后文逐一讲解);
  • 作为函数,即 *zoom*(*selection*),它把行为应用到选择集上。

典型用法是通过 selection.call 把行为挂到选中的元素上:

selection.call(d3.zoom().on("zoom", zoomed));

2.2 zoom(selection) 的挂载细节

调用 *zoom*(*selection*) 时会发生三件事:

  1. 绑定事件监听器:内部通过 selection.on 绑定平移和缩放所需的全部监听器;
  2. 初始化变换状态:若尚未定义,把每个选中元素的 zoom transform 初始化为恒等变换(identity transform);
  3. 禁用 iOS 点击高亮:设置 -webkit-tap-highlight-color 样式为 transparent。如果你想用别的高亮颜色,在挂载行为之后移除或重新设置该样式即可。

事件监听器带 .zoom 名称前缀,这给了你精确卸载的能力:

// 完全移除 zoom 行为的所有监听器
selection.on(".zoom", null);

// 只禁用滚轮缩放(例如避免与页面原生滚动冲突)
selection
    .call(zoom)
    .on("wheel.zoom", null);

文档同时给出了更优雅的方案:用 zoom.filter 精细控制哪些事件可以发起缩放手势(见第四节)。

状态存储位置是理解 zoom 行为的关键。zoom 行为把缩放状态存储在"行为所应用的元素"上,而不是存储在 zoom 行为对象本身。这个设计允许同一个 zoom 行为同时应用到多个元素上,且每个元素拥有独立的缩放状态。缩放状态会随用户交互或 zoom.transform 的编程式调用而变化。

读取缩放状态有两条途径:

  • 在 zoom 事件监听器内,使用 event.transform(见第七节"Zoom 事件");
  • 对任意给定节点,调用 zoomTransform——后者特别适合编程式修改缩放状态,例如实现放大/缩小按钮。

三、ZoomTransform:缩放变换的数学模型

3.1 变换矩阵与属性

zoomTransform(node) 返回指定 node 当前的变换。注意 node 通常是 DOM 元素而非 selection(一个 selection 可能包含多个处于不同状态的节点,而本函数只返回单个变换)。如果手里是 selection,先调用 selection.node

var transform = d3.zoomTransform(selection.node());

// 在事件监听器内部,node 通常就是接收输入事件的元素(即 this)
var transform = d3.zoomTransform(this);

实现细节:元素的变换内部存储为 element.__zoom,但文档明确建议通过 zoomTransform 读取而不是直接访问该属性。若给定 node 没有定义变换,则返回其最近祖先的变换;若都不存在,返回恒等变换。

返回的变换对象表示一个二维仿射变换矩阵

k   0   tx
0   k   ty
0   0   1

该矩阵只能表示缩放和平移(文档注明:未来版本可能支持旋转,但那将是非向后兼容的变更)。位置 ⟨x, y⟩ 被变换为 ⟨xk + tx, yk + ty⟩。变换对象暴露三个只读属性:

属性 含义
transform.k 缩放因子 k
transform.x x 轴平移量 tx
transform.y y 轴平移量 ty

不要直接修改这三个属性,而应使用 transform.scaletransform.translate 派生新变换。构造变换的标准写法:

var t = d3.zoomIdentity.translate(x, y).scale(k);

3.2 zoomIdentity 与 new ZoomTransform(k, x, y)

  • d3.zoomIdentity:恒等变换,k = 1,tx = ty = 0。它是所有变换链的起点;
  • new d3.ZoomTransform(k, x, y):构造器形式,返回缩放为 k、平移为 (x, y) 的变换。

3.3 派生变换:scale 与 translate 的复合规则

// 返回一个新变换,其缩放 k1 = k0 * k
transform.scale(k)

// 返回一个新变换,其平移 tx1 = tx0 + tx * k、ty1 = ty0 + ty * k
// 注意:平移量会乘以当前缩放因子 k(文档原文记作 t_k,即本变换的缩放)
transform.translate(x, y)

两个方法都返回新对象而不修改原变换,这使得变换组合可以像函数式一样层层叠加。

3.4 点的正变换与逆变换

这组方法是"屏幕坐标 ↔ 数据坐标"换算的核心,也是实现坐标轴联动缩放的基础:

方法 结果
transform.apply([x, y]) [xk + tx, yk + ty]
transform.applyX(x) xk + tx
transform.applyY(y) yk + ty
transform.invert([x, y]) [(x - tx) / k, (y - ty) / k]
transform.invertX(x) (x - tx) / k
transform.invertY(y) (y - ty) / k

apply 系列把数据平面上的点映射到屏幕(视口)位置invert 系列做相反方向——把屏幕位置还原为数据平面上的点。*zoom*.constrain 的默认实现(见第四节)正是靠 invertX/invertY 来比较视口范围与平移范围的。

3.5 把变换应用到不同渲染目标

Canvas 2D 上下文:先 translatescale(顺序不能反):

context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);

HTML 元素(CSS transform)

div.style("transform", "translate(" + transform.x + "px," + transform.y + "px) scale(" + transform.k + ")");
div.style("transform-origin", "0 0");

SVG 元素

g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");

// 更简洁:直接利用 toString()
g.attr("transform", transform);

transform.toString 的实现即:

function toString() {
  return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")";
}

文档特别强调:变换顺序很重要,translate 必须先于 scale 应用

四、zoom 的九个配置方法

以下方法均为 getter/setter 双模式:传入参数则设置并返回 zoom 行为(支持链式调用),不传参数则返回当前值。

4.1 zoom.filter(filter)——事件过滤器

若指定 filter,设置为过滤函数;否则返回当前 filter。默认 filter 为:

function filter(event) {
  return (!event.ctrlKey || event.type === 'wheel') && !event.button;
}

filter 接收当前事件(event)和数据 dthis 为当前 DOM 元素。只要 filter 返回 falsy,发起事件就被忽略,不启动任何缩放手势——因此 filter 决定了哪些输入事件被忽略。默认 filter 忽略次级鼠标按钮的 mousedown(右键通常用于上下文菜单)。

从默认实现可以读出两条规则:按住 Ctrl 的普通事件被忽略,但 Ctrl + 滚轮例外(这是浏览器原生"Ctrl+滚轮缩放"习惯的保留);任何非左键(event.button 非 0)的鼠标事件一律忽略。

4.2 zoom.wheelDelta(delta)——滚轮灵敏度

默认实现:

function wheelDelta(event) {
  return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * (event.ctrlKey ? 10 : 1);
}

返回值 Δ 决定对一次 WheelEvent 应用的缩放幅度:缩放因子 k 乘以 2^Δ——Δ = +1 使 k 翻倍,Δ = -1 使 k 减半。三个系数各有含义:

  • 乘以 -event.deltaY:向上滚(deltaY 为负)放大,向下滚缩小;
  • deltaMode 分支:deltaMode === 1 是"按行"计量的滚轮(0.05/行),deltaMode === 2 是"按页"(系数 1),其余(像素模式,deltaMode === 0)用 0.002/像素;
  • ctrlKey ? 10 : 1:按住 Ctrl 时缩放速度快 10 倍。

4.3 zoom.touchable(touchable)——触摸支持检测

默认检测器:

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

只有当检测器对相应元素返回 truthy 时,才会注册触摸事件监听器——这避免在纯桌面环境注册无用的 touch 监听。文档指出默认检测器对大多数支持触摸的浏览器有效,但对个别环境会失效(例如 Chrome 的移动设备模拟器)。此时可以传入自定义检测函数覆盖。

4.4 zoom.extent(extent)——视口范围

zoom.extent([[x0, y0], [x1, y1]]);

设置为视口范围:[x0, y0] 是视口左上角,[x1, y1] 是右下角。extent 也可以是返回该数组的函数(按每个选中元素调用,this 为 DOM 元素)。不指定时返回当前 accessor,默认是 [[0, 0], [width, height]],其中 widthheight 取元素的 client width/height;对 SVG 元素则取最近祖先 SVG 元素的 viewBox 或 width/height 属性。文档也提示可考虑改用 element.getBoundingClientRect。

视口范围影响多处逻辑:*zoom*.scaleBy / *zoom*.scaleTo 变化时视口中心保持固定;interpolateZoom 选择平滑缩放路径时依赖视口中心和尺寸;强制可选的 translate extent 也需要它。

4.5 zoom.scaleExtent(extent)——缩放上下限

zoom.scaleExtent([k0, k1]); // k0 最小缩放,k1 最大缩放;默认 [0, ∞]

scale extent 限制放大/缩小范围,在交互以及 *zoom*.scaleBy*zoom*.scaleTo*zoom*.translateBy 时强制执行;但用 *zoom*.transform 显式设置变换时不强制。一个值得注意的交互行为:当用户已经在 scale extent 的某一极限处继续滚动滚轮时,wheel 事件会被忽略而不发起缩放手势——这让"放大后继续向下滚动页面"或"缩小后向上滚动"成为可能。如果你希望无论如何都阻止滚轮触发的页面滚动,可以自行注册 wheel 监听器来阻止浏览器默认行为:

selection
    .call(zoom)
    .on("wheel", event => event.preventDefault());

4.6 zoom.translateExtent(extent)——平移边界

zoom.translateExtent([[x0, y0], [x1, y1]]); // 世界的左上角/右下角;默认 [[-∞,-∞],[+∞,+∞]]

translate extent 限制平移范围,且缩小视口时可能引起额外平移(把缩回来的画面拉回边界内)。与 scale extent 相同,它在交互和 scaleBy/scaleTo/translateBy 时强制执行,但 *zoom*.transform 显式设置时不强制。

4.7 zoom.constrain(constrain)——自定义约束函数

constrain 必须是一个给定当前 transform视口范围平移范围 后返回 transform 的函数。默认实现是:

function constrain(transform, extent, translateExtent) {
  var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
      dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
      dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
      dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
  return transform.translate(
    dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
    dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
  );
}

这段默认实现的逻辑值得逐行拆解:

  1. invertX/invertY视口四角从屏幕坐标换算回世界坐标,再减去 translateExtent 的对应角,得到视口相对边界的"越界量" dx0/dx1、dy0/dy1;
  2. dx1 > dx0(视口比世界还宽,无法填满边界),取中点 (dx0 + dx1) / 2 居中显示;
  3. 否则 Math.min(0, dx0) || Math.max(0, dx1):视口左边界越界(dx0 > 0)则拉回,右边界越界(dx1 < 0)则推回,均未越界则保持不动。

其语义就是"尽力保证视口范围不超出平移范围"。你传入自定义 constrain 即可完全接管约束策略(例如只约束 x 轴、或实现环绕平移)。

4.8 距离、时长与插值:clickDistance / tapDistance / duration / interpolate

  • *zoom*.clickDistance(distance):设置 mousedown 与 mouseup 之间鼠标允许移动的最大距离(以 client 坐标 event.clientX/clientY 计)。若期间移动距离达到该值,mouseup 后的 click 事件将被抑制。默认值为 0(任何移动都抑制后续点击)。
  • *zoom*.tapDistance(distance):双击手势中第一次 touchstart 与第二次 touchend 之间允许移动的最大距离,超过则不触发双触事件。默认值为 10
  • *zoom*.duration(duration):设置双击/双触触发的缩放过渡时长(毫秒),默认 250。若时长不大于 0,双击和双触将触发即时(无动画)的变换。若要彻底禁用双击/双触缩放,可在挂载后移除 dblclick 监听器:
selection
    .call(zoom)
    .on("dblclick.zoom", null);
  • *zoom*.interpolate(interpolate):设置缩放过渡使用的插值工厂。默认是 interpolateZoom(实现"平滑缩放");若要两个视图之间的直接线性插值,可改用 d3-interpolate 的通用 interpolate

4.9 zoom.on(typenames, listener)——监听缩放事件

*zoom*.on(*typenames*, *listener*) 为指定 typenames 设置事件 listener;同类型同名监听器会先移除旧监听再添加新的;listener 为 null 时移除;不传 listener 时返回当前匹配的第一个监听器。监听器被调用时的上下文与参数和 selection.on 一致:当前事件(event)、数据 dthis 为当前 DOM 元素。

typenames 是空格分隔的若干 typename;每个 typename 是一个 type,可选地跟一个点和 name(如 zoom.foozoom.bar 可以为同一 type 注册多个具名监听)。type 必须是以下三者之一:

type 触发时机
start 缩放开始后(如 mousedown)
zoom 缩放变换发生变化后(如 mousemove)
end 缩放结束后(如 mouseup)

更多细节参见 dispatch.on(zoom 行为的事件分发底层即 d3-dispatch)。

五、编程式控制:zoom.transform 与三个便捷方法

5.1 zoom.transform(selection, transform, point)

这是所有编程式缩放的基石:

  • selection选择:立即把选中元素的当前缩放变换设置为 transform,并立即发出 start、zoom、end 三个事件;
  • selection过渡(transition):用 interpolateZoom 定义一个 "zoom" tween 过渡到 transform——过渡开始时发 start,过渡的每一 tick 发 zoom 事件,过渡结束(或被中断)时发 end。过渡会尝试最小化指定 point 附近的视觉移动量point 未指定时默认为视口 extent 的中心。

transform 可以是 zoom transform 本身,或返回它的函数;point 可以是 [x, y] 二元组或返回二元组的函数。作为函数时,对每个选中元素调用,传入当前事件(event)和数据 dthis 为当前 DOM 元素。

同样建议通过 selection.calltransition.call 调用。示例:

// 立即重置为恒等变换
selection.call(zoom.transform, d3.zoomIdentity);

// 750 毫秒平滑过渡回恒等变换
selection.transition().duration(750).call(zoom.transform, d3.zoomIdentity);

重要限制*zoom*.transform 要求你完整指定新变换,且不强制执行已定义的 scale extent 和 translate extent。若要从现有变换派生新变换并强制执行两个范围,请使用下面的便捷方法。

5.2 三个便捷方法:translateBy / translateTo / scaleBy / scaleTo

这四个便捷方法都以 *zoom*.transform 为基础,区别在于"以当前变换为起点增量派生",因此自动遵守 scale/translate extent:

方法 语义
*zoom*.translateBy(selection, x, y) 平移当前变换:新 tx1 = tx0 + kx,ty1 = ty0 + ky(平移量乘当前缩放因子)
*zoom*.translateTo(selection, x, y, p) 让数据平面坐标 ⟨x,y⟩ 显示到屏幕点 p:新 tx = pxkxty = pykyp 默认为视口 extent 中心
*zoom*.scaleBy(selection, k, p) 缩放当前变换 k 倍:新 k1 = k0 × k;参考点 p 保持不动,默认为视口 extent 中心
*zoom*.scaleTo(selection, k, p) 把缩放设为绝对值:新 k1 = k;参考点 p 保持不动,默认为视口 extent 中心

四个方法的参数都支持"数值或返回数值的函数"两种形式;p 支持二元组 [px, py] 或返回二元组的函数。作为函数时传入当前数据 d 和索引 ithis 为当前 DOM 元素。且当 selection 是 transition 时,四个方法都会定义对应的 "zoom" tween 平滑过渡。

典型实战是缩放按钮——这也正是 zoomTransform 读取当前状态 + scaleBy/scaleTo 派生新状态的经典组合:

// 按钮放大 2 倍(过渡 250ms),scaleExtent 自动生效
selection.transition().duration(250)
  .call(zoom.scaleBy, 2);

六、Zoom 事件详解

zoom 事件监听器被调用时,第一个参数是当前 zoom 事件对象,暴露四个字段:

字段 含义
event.target 关联的 zoom 行为
event.type 字符串 "start" / "zoom" / "end"
event.transform 当前的 zoom transform
event.sourceEvent 底层输入事件(如 mousemove、touchmove)

zoom 行为处理的完整交互事件矩阵(照录自 docs/d3-zoom.md):

事件 监听元素 触发的 Zoom 事件 是否 preventDefault
mousedown⁵ selection start 否¹
mousemove² window¹ zoom
mouseup² window¹ end
dragstart² window -
selectstart² window -
click³ window -
dblclick selection 多个
wheel⁸ selection zoom⁷
touchstart selection 多个 否⁴
touchmove selection zoom
touchend selection end 否⁴
touchcancel selection end 否⁴

脚注解读(原文脚注的中文化说明):

  1. 必须在 window 上监听,才能捕获 iframe 外溢到视口之外部分的事件;
  2. 仅在实际进行的鼠标手势期间适用(mousemove/mouseup/dragstart/selectstart 均监听在 window 上,防止拖出元素后事件丢失);
  3. click 抑制仅在刚发生过鼠标手势后立即生效,阈值由 zoom.clickDistance 控制;
  4. touchstart/touchend/touchcancel 不 preventDefault,是为了允许触摸输入上的 click 模拟(Safari 等浏览器的点击兼容机制);
  5. mousedown 在 500ms 内(刚结束 touch 手势)会被忽略——假设浏览器做了 click 模拟;
  6. 双击/双触发起一次过渡,依次发出 start、zoom、end 事件;双触移动距离阈值由 zoom.tapDistance 控制;
  7. 第一个 wheel 事件发出 start,150ms 内再无 wheel 事件时发出 end(即滚轮"按住连滚"被视为一次持续手势);
  8. 若已处于 scale extent 对应极限,wheel 事件被忽略。

所有被"消费"的事件其传播都会立即停止stopImmediatePropagation),这是 zoom 能与 drag、brush 等行为共存的底层机制——文档在 CHANGES.md 中亦明确记录了这一设计意图("zoom 行为消费已处理的事件,便于与 drag 等其他交互行为组合",见 CHANGES.md)。

最常用的一行式监听:

const zoom = d3.zoom()
  .on("zoom", (event) => {
    g.attr("transform", event.transform); // 把整组图形按变换平移+缩放
  });
selection.call(zoom);

七、rescaleX / rescaleY:让坐标轴跟随缩放

这是 d3-zoom 与 d3-scaled3-axis 联动缩放的核心。

*transform*.rescaleX(x) 返回连续比例尺 x副本,其 domain 被变换。实现分两步:先对比例尺的 range 逐点施加 inverse x-transforminvertX),再用 inverse scalex.invert)把 range 值反算成新的 domain:

function rescaleX(x) {
  var range = x.range().map(transform.invertX, transform),
      domain = range.map(x.invert, x);
  return x.copy().domain(domain);
}

*transform*.rescaleY(y) 完全对称:

function rescaleY(y) {
  var range = y.range().map(transform.invertY, transform),
      domain = range.map(y.invert, y);
  return y.copy().domain(domain);
}

两条硬约束

  1. 比例尺 x/y 必须使用 interpolateNumber(即普通数值插值),不要使用 continuous.rangeRound——取整会降低 continuous.invert 的精度,导致重算后的 domain 不准确;
  2. 该方法不修改传入的比例尺x 代表未变换的比例尺,返回值才是它的"变换后视图"。

标准"缩放坐标轴"模式(在 zoom 回调里重建轴):

function zoomed(event) {
  g.attr("transform", event.transform);
  // 用当前变换重算 x 轴,再重绘
  gx.call(d3.axisBottom(event.transform.rescaleX(x)));
}

八、平滑缩放插值 interpolateZoom 与版本说明

*zoom*.transform 作用于 transition 时,以及双击/双触触发的过渡,默认都经由 interpolateZoom 实现"平滑缩放"。它源自 van Wijk & Nuij 的论文 "Smooth and efficient zooming and panning":两个视图各用三元组 [cx, cy, width] 描述(视口中心 + 视口宽度),插值器沿一条曲率最优的路径过渡,并暴露 interpolate.duration 属性——推荐时长(毫秒)由 xy 空间曲线路径长度决定;想要更快/更慢就乘以一个任意缩放系数(论文中的 V 参数)。interpolateZoom.rho(rho) 可调曲率:rho 接近 0 时轨迹接近线性,默认曲率为 √2(详见 docs/d3-interpolate/zoom.md)。

版本与兼容性前提。本文所有 API 行为以本仓库 package.json 声明的依赖为准:d3 7.9.0,d3-zoom ^3.0.0,运行环境 Node >= 12,模块系统为 ESM("type": "module"src/index.js 即入口)。从 CHANGES.md 可以确认 zoom 行为在 d3 v4 时代的主要 API 定型事实:事件监听器直接收到 event、默认 filter 支持 Ctrl+滚轮、默认 wheelDelta 在 Ctrl 下加速、新增 zoom.tapDistance、以及"wheel 在 scale extent 极限处被忽略"的滚动穿透行为——这些正是上文各节描述的当前默认行为的历史由来。

九、完整可运行示例

下面把本文 API 串成一个最小可运行的可缩放散点图骨架(ESM 方式,等价于本仓库 src/index.js 导出的命名空间):

import * as d3 from "d3"; // 或浏览器 <script> 引入 dist/d3.min.js 后用全局 d3

const width = 600, height = 400;
const g = d3.select("svg").append("g");

// 1. 创建 zoom 行为并配置
const zoom = d3.zoom()
  .scaleExtent([0.5, 8])                          // 缩放范围:0.5x ~ 8x
  .translateExtent([[0, 0], [width, height]])     // 平移不超出画布
  .on("zoom", (event) => {
    // 2. 用 event.transform 驱动整个分组
    g.attr("transform", event.transform);
    // 如需坐标轴联动:
    // gx.call(d3.axisBottom(event.transform.rescaleX(x)));
  });

d3.select("svg").call(zoom);

// 3. 编程式控制:双击后仍可继续用代码缩放
d3.select("#reset").on("click", () => {
  d3.select("svg").transition().duration(750)
    .call(zoom.transform, d3.zoomIdentity);   // 平滑复位
});

// 4. 按钮放大 2 倍(自动遵守 scaleExtent)
d3.select("#in").on("click", () => {
  d3.select("svg").transition().duration(250)
    .call(zoom.scaleBy, 2);
});

// 5. 如需关闭滚轮缩放(不影响拖拽平移)
// d3.select("svg").on("wheel.zoom", null);

要点回顾:状态存在元素上(__zoom),用 d3.zoomTransform(node)event.transform 读取;显式 zoom.transform 不强制 extent,增量操作一律走 scaleBy/scaleTo/translateBy/translateTo;监听器用 .zoom 命名空间前缀可被 selection.on(".zoom", null) 一键摘除。

十、参考索引

主题 仓库内位置
d3-zoom 完整 API(本文主体来源) docs/d3-zoom.md
interpolateZoom 平滑插值 docs/d3-interpolate/zoom.md
选择与 selection.call / .on docs/d3-selection/control-flow.mddocs/d3-selection/events.md
过渡与 transition.call docs/d3-transition/control-flow.md
连续比例尺 domain/range/invert docs/d3-scale/linear.md
通用插值 interpolate docs/d3-interpolate/value.md
d3-dispatch(zoom.on 底层) docs/d3-dispatch.md
与 zoom 组合的 drag / brush docs/d3-drag.mddocs/d3-brush.md
版本依赖声明 package.json
全量导出测试 test/d3-test.js
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388