首页
/ d3 selection 元素修改(Modifying Elements)完全指南:attr、classed、style、append 等方法的用法与实现细节

d3 selection 元素修改(Modifying Elements)完全指南:attr、classed、style、append 等方法的用法与实现细节

2026-09-06 12:44:45作者:鲍丁臣Ursa

本篇基于 docs/d3-selection/modifying.md 系统讲解 d3 中「修改元素」这一核心环节:完成选择后,如何使用 selection 的 attr、classed、style、property、text、html、append、insert、remove、sort、order、raise、lower、create、creator 等方法精确操作 DOM,理解函数式取值约定(datum/index/nodes)、空值语义、命名空间规则以及选择集不可变(immutable)的设计原则。掌握这些方法后,你就能写出与数据绑定(data join)无缝衔接、可链式复用的 d3 图形代码。

1. 选择之后:用链式调用修改元素

选中元素后,selection 本身就是操作 DOM 的入口。官方文档给出的最简示例是给当前文档中所有段落设置 class 与颜色样式:

d3.selectAll("p")
    .attr("class", "graf")
    .style("color", "red");

selection 方法通常返回当前选择或一个新的选择,因此可以通过**方法链(method chaining)**在一个选择上简洁地施加多个操作。上例等价于:

const p = d3.selectAll("p");
p.attr("class", "graf");
p.style("color", "red");

一个关键的 API 设计约束(见 modifying.md 原文)是:选择集是不可变的。所有会影响「哪些元素被选中(或其顺序)」的方法都返回新的 selection,而不是修改当前 selection。但要注意:元素本身必然是可变的——因为 selection 驱动的就是对文档的变换!换句话说,sort() 不会改变原 selection,而 style() 不改变 selection 的组成,却直接改变 DOM。

docs/d3-selection/selecting.md 中还约定了一种排版惯例:返回当前选择的方法(如 attr)使用四格空格缩进,返回新选择的方法(如 append)使用两格空格缩进,让链中的上下文切换在视觉上「跳出来」:

d3.select("body")
  .append("svg")
    .attr("width", 960)
    .attr("height", 500)
  .append("g")
    .attr("transform", "translate(20,20)")
  .append("rect")
    .attr("width", 920)
    .attr("height", 460);

这些方法在本仓库中的来源:d3 主包是一个聚合包,package.json 声明了 "d3-selection": "^3.0.0" 依赖,并由 src/index.jsexport * from "d3-selection" 一行将其全部 API 重新导出,因此 d3.attr 相关的 selection 方法直接可用。各方法的实现源码位于 d3-selection 子项目的 src/selection/*.js 文件(如 attr.jsclassed.jsstyle.jsappend.js 等),原文档每个方法小节开头的 Source 链接即指向这些文件。

2. 修改属性:selection.attr(name, value)

attr 方法 支持三种调用形态:

// 设置:常量值
selection.attr("color", "red")

// 读取:返回选择中第一个(非 null)元素该属性的当前值
selection.attr("color") // "red"

规则要点:

  • 指定了 value 时,将所选元素上指定名称的属性设置为该值,并返回当前 selection(可继续链式调用);
  • value 是常量,所有元素获得相同的属性值;若 value 是函数,则按顺序对每个被选元素求值,函数参数依次为当前数据(d)、当前索引(i)、当前组(nodes),this 指向当前 DOM 元素(即 nodes[i]),函数返回值用于设置每个元素的属性。这是 d3 中最重要的通用约定,classedstyletextappend 等方法全部复用同一套签名;
  • 传入 null移除该属性;
  • 不传 value 时,返回第一个非 null 元素的属性当前值——这一「读取」形态通常只在你知道选择恰好只含一个元素时才实用;
  • 指定的 name 可以带命名空间前缀,例如 xlink:href 表示 XLink 命名空间中的 href 属性。已注册前缀见第 7 节的 namespaces 说明。

3. 管理 CSS 类:selection.classed(names, value)

classed 方法 通过设置 class 属性或修改 classList 属性来分配/取消分配 CSS 类:

selection.classed("foo", true)

规则要点:

  • names空格分隔的类名串。要给元素同时分配 foobar 两个类:
selection.classed("foo bar", true)
  • value 为真值(truthy)时所有元素获得指定类,否则取消分配;
  • value 可以是函数,同样按 (d, i, nodes)、this 为当前元素的约定逐个求值,返回值决定每个元素是否拥有该类。例如随机半数以做交替着色:
selection.classed("foo", () => Math.random() > 0.5)
  • 不传 value 时,返回第一个非 null 被选元素是否带有指定类(true/false):
selection.classed("foo") // true, perhaps

4. 修改样式:selection.style(name, value, priority)

style 方法 的设置/读取语义与 attr 完全一致:常量或函数赋值、null 移除样式、省略 value 时读取第一个非 null 元素的当前值。当前值的定义是:若元素存在内联样式则返回内联值,否则返回其计算值(computed value)

style 多出一个可选参数 priority,取值为 null 或字符串 "important"不带感叹号),用于生成 !important 声明。

文档中有一段值得注意的警告(modifying.md 原文):

与许多 SVG 属性不同,CSS 样式通常带有单位。例如 3px 是合法的 stroke-width 值,而 3 不合法。部分浏览器会隐式为数值补上 px 单位,但并非所有浏览器都这样做:IE 甚至会抛出 “invalid arguments” 错误。

这条警告的实战含义是:对 SVG 图形属性(cxrstroke-width 的 attribute 形式)可以放心用裸数值;而一旦改用 style() 设置,就应显式带上单位,避免跨浏览器不一致。

5. 修改属性之外的 DOM 属性:selection.property(name, value)

property 方法 用于处理属性(attribute)和样式都无法触达的 HTML 特殊属性,最典型的就是表单文本域的 value 与复选框的 checked 布尔量:

selection.property("checked", true)
selection.property("checked") // true, perhaps

规则与前几个方法一致:常量或函数(d, i, nodes 签名)赋值、null 删除该属性、省略 value 时读取第一个非 null 元素的属性值。attr/style/property 的选型经验法则:

目标 方法 典型场景
XML 属性、SVG 图形属性 attr() widthxlink:hrefd
内联/计算样式 style() fillcolor(注意单位)
DOM 属性对象成员 property() checkedvalueselected

6. 替换内容:text 与 html

selection.text(value)

text 方法 设置元素的文本内容(text content),会替换所有现有子元素:

selection.text("Hello, world!")
selection.text() // "Hello, world!"

常量或函数(同一 d/i/nodes 约定)赋值;null 清空内容;省略 value 时返回第一个非 null 元素的文本内容。

selection.html(value)

html 方法 设置元素的内层 HTML(inner HTML),同样替换现有子元素:

selection.html("Hello, <i>world</i>!")
selection.html() // "Hello, <i>world</i>!"

文档对此方法给出了明确的使用边界:

  • 数据驱动的内容应改用 append/inserthtml 的定位是「需要一小段富格式 HTML」的场景;
  • selection.html 仅在 HTML 元素上受支持。SVG 元素及其他非 HTML 元素没有 innerHTML 属性,因此与 html 不兼容;需要把 DOM 子树转为文本时可考虑 XMLSerializer,或借助 innersvg 这类 polyfill 为 SVG 元素补上 innerHTML 支持。

7. 插入与创建元素:append、insert、create、creator

selection.append(type)

append 方法 把新元素作为每个被选元素的最后一个子节点追加;若当前是 enter selection(进入选择,见 docs/d3-selection/joining.md),则插入到该进入组内下一个兄弟节点之前——这一行为使插入顺序与新绑定数据保持一致。但要注意:若更新(update)元素之间发生了顺序变化(新数据顺序与旧数据不一致),仍可能需要调用 order() 修正。

type 为字符串时即标签名,等价于每步展开:

d3.selectAll("div").append("p");
// 等价于
d3.selectAll("div").append(() => document.createElement("p"));
// 进一步等价于
d3.selectAll("div").select(function() {
  return this.appendChild(document.createElement("p"));
});

方法返回一个包含被追加元素的新选择;每个新元素按 select 的方式继承父元素的已有数据。type 也可以是函数,按 (d, i, nodes) 约定求值并返回要追加的元素(通常是新建的,也可以返回已有元素)。

命名空间规则:名称可带前缀,如 svg:text 表示 SVG 命名空间的 text;不带前缀时命名空间从父元素继承,或者名称本身是已知前缀时按对应命名空间解析(例如 svg 隐含 svg:svg)。

selection.insert(type, before)

insert 方法 在每个被选元素中,插入到匹配 before 选择器的第一个元素之前

d3.selectAll("div").insert("p");
// 等价于
d3.selectAll("div").insert(() => document.createElement("p"));
// 进一步等价于
d3.selectAll("div").select(function() {
  return this.insertBefore(document.createElement("p"), null);
});

要点:

  • before 选择器如 :first-child 可实现「前置」效果;before 缺省时默认为 null(等价于 append 的位置);
  • typebefore 都支持函数形式,type 函数返回要插入的元素,before 函数返回插入目标之前的子元素;
  • 与 append 相同,返回新选择、继承父元素数据、支持命名空间前缀;若要与绑定数据保持一致的顺序,优先用 append

remove 与 clone

remove 方法 把所选元素从文档中移除,返回当前 selection(被移除的元素,此时已脱离 DOM)。目前没有专门 API 把移除的元素「加回」文档,但可以传函数给 append/insert 重新加入。

clone 方法 在所选元素之后插入其克隆体,返回新增克隆的选择;deep 为真值时连同后代节点一起克隆,否则只克隆元素本身。文档给出了其等价实现,可直接看出底层就是 cloneNode

selection.select(function() {
  return this.parentNode.insertBefore(this.cloneNode(deep), this.nextSibling);
});

create 与 creator

create 方法 给定元素名,返回包含一个尚未接入文档的元素的单元素选择:

d3.create("svg")     // 等价于 svg:svg
d3.create("svg:svg") // 更显式
d3.create("svg:g")   // SVG G 元素
d3.create("g")       // HTML 中的 G(未知)元素

注意 create 默认假设 HTML 命名空间,因此创建 SVG 等非 HTML 元素必须显式指定命名空间前缀——d3.create("g") 得到的是 HTML 里一个未知的 G 元素,而不是 SVG 的 g。

creator 方法 给定元素名,返回一个「以 this 为父元素创建该元素」的函数。它被 appendinsert 内部用于创建新元素,即:

selection.append("div");
// 等价于
selection.append(d3.creator("div"));

这也解释了命名空间的第二层继承逻辑:creator 生成元素时会参考父元素所在命名空间,从而让 svg 父级下 append("g") 自动创建 SVG 的 g 元素。

命名空间前缀的注册表见 docs/d3-selection/namespaces.md,其初始值为:

{
  svg: "http://www.w3.org/2000/svg",
  xhtml: "http://www.w3.org/1999/xhtml",
  xlink: "http://www.w3.org/1999/xlink",
  xml: "http://www.w3.org/XML/1998/namespace",
  xmlns: "http://www.w3.org/2000/xmlns/"
}

可按需向该表追加前缀,从而在其他命名空间中创建元素或属性。典型的混用场景是把 HTML 塞进 SVG 的 foreignObject:

d3.create("svg")
  .append("foreignObject")
    .attr("width", 300)
    .attr("height", 100)
  .append("xhtml:div")
    .text("Hello, HTML!");

8. 调整文档顺序:sort、order、raise、lower

这四个方法不改变选择的内容,只重排元素在父节点中的位置,在更新可视化(数据顺序变了但 DOM 要跟上)时必不可少。

selection.sort(compare)

sort 方法 返回一个新 selection,其中每组元素按 compare 函数排序;排序后按 order 的语义重新插入元素以匹配结果顺序。比较函数(默认使用 d3-array 的 ascending,见 docs/d3-array/sort.md)接收两个元素的数据 ab,返回负值表示 a 在前、正值表示 a 在后、零表示相等(顺序任意)。

selection.order()

order 方法 把元素重新插入文档,使每组的文档顺序与选择顺序一致。若数据已经有序,它就等价于调用 sort,但速度快得多——因此常见写法是先改变节点数据的顺序,再轻量地 order()

selection.raise() 与 selection.lower()

raise 方法 依次把每个被选元素重新插入为其父节点的最后一个子节点,等价于:

selection.each(function() {
  this.parentNode.appendChild(this);
});

lower 方法 相反,把元素重插为第一个子节点,等价于:

selection.each(function() {
  this.parentNode.insertBefore(this, this.parentNode.firstChild);
});

在 SVG 中「后绘制者覆盖先绘制者」,所以 raise/lower 是最直接的图层控制手段(例如把选中高亮的柱形置顶);而在 HTML 布局中它们影响的是兄弟节点排列顺序。

9. 小结:方法总览与适用边界

方法 返回 空值语义 关键注意点
attr(name, value) 当前 selection 设值时 null 移除属性 支持命名空间前缀(如 xlink:href
classed(names, value) 当前 selection 空格分隔多类名;true/false 或函数
style(name, value, priority) 当前 selection 设值时 null 移除样式 prioritynull/"important";注意单位(3px vs 3
property(name, value) 当前 selection 设值时 null 删除属性 用于 checkedvalue 等属性/样式触达不了的成员
text(value) 当前 selection null 清空内容 读取返回首个非 null 元素值
html(value) 当前 selection null 清空内容 仅 HTML 元素支持;SVG 请用 XMLSerializer 等
append(type) 新 selection enter selection 中插在组内下一兄弟前;支持函数与命名空间
insert(type, before) 新 selection before 缺省为 null;数据有序场景优先 append
remove() 当前 selection(已脱离 DOM) 重加回文档用函数形式的 append/insert
clone(deep) 新 selection(克隆体) deep 决定是否连同后代克隆
sort(compare) 新 selection 默认 ascending,按数据比较
order() 当前 selection 已排序数据时比 sort 快得多
raise() / lower() 当前 selection SVG 中即图层置顶/置底
create(name) 单元素选择(未接入文档) 默认 HTML 命名空间,SVG 需写 svg: 前缀
creator(name) 元素创建函数 append/insert 内部创建元素的基础

两条贯穿全文的设计原则:其一,选择集不可变、元素可变——改变组成(append/insert/sort)返回新选择,改变外观/内容(attr/style/text)原地作用于 DOM;其二,所有函数式取值共享 (d, i, nodes) + this 约定,一旦掌握,全部修改方法的心智模型是统一的。配合 docs/d3-selection/selecting.md 的选择、docs/d3-selection/joining.md 的数据绑定与 docs/d3-selection/events.md 的事件监听,即构成 d3 数据驱动可视化的完整操作闭环。

登录后查看全文
热门项目推荐
相关项目推荐