d3-transition
transition
是一个类 selection 的接口,用来对 DOM
进行动画修改。这种修改不是立即修改,而是在规定的事件内平滑过渡到目标状态。
应用过渡,首先要选中元素,然后调用 selection.transition,并且设置期望的改变,例如:
d3.select("body")
.transition()
.style("background-color", "red");
过渡支持大多数选择集的方法(比如 Installing
NPM
安装: npm install d3-transition
. 此外还可以下载 latest release. 可以直接从 d3js.org 以 standalone library 或作为 D3 4.0 的一部分载入. 支持 AMD
, CommonJS
以及基本的标签引入形式,如果使用标签引入形式则会暴露全局 d3
变量:
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-ease.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script>
var transition = d3.transition();
</script>
API Reference
- Selecting Elements
过渡通过 selection.transition 派生自 selections。你也可以使用 d3.transition 在文档根元素上创建一个过渡。
selection.transition([name]) <源码>
在指定的 selection 上返回指定的 name 的过渡。如果没有指定 name 则会使用
null
。新的过渡仅仅与其他相同名字的过渡相排斥。如果 name 是一个 transition 实例,则返回的过渡与指定的过渡具有相同的
id
和name
。如果已经选中的元素上已经存在相同id
和name
的过渡,则返回该元素已有的过渡。否则,返回的过渡的时间会从已经选中的每个选定元素的具有相同id
的最近祖先继承。因此,这个方法可以用来同步多个选择集的过渡,或者重新选择特定元素的过渡并修改其配置。例如:var t = d3.transition() .duration(750) .ease(d3.easeLinear); d3.selectAll(".apple").transition(t) .style("fill", "red"); d3.selectAll(".orange").transition(t) .style("fill", "orange");
如果指定的 transition 没有在已选中元素的祖先元素上找到(比如如果过渡 already ended),则默认的时间参数会被使用;但是在未来的版本中,这种情况可能会抛出错误。参考 "https://github.com/d3/d3-transition/blob/master/src/selection/interrupt.js" title="Source" target="_blank" rel="noopener noreferrer"><源码>
中断指定 name 的活动的过渡,并且取消指定 name 未执行的过渡(如果存在的话)。如果没有指定 name 则默认使用
null
。在元素上中断过渡不会影响其后代的任何过渡。例如,axis transition 由多个独立的,同步的过渡组成,这些过渡是对 G element 的后代元素进行过渡(
tick lines
,tick labels
,domain path
, etc.)。如果要中断axis
的过渡,必须中断其所有后代元素的过渡:selection.selectAll("*").interrupt();
universal selector(通配选择符)
*
表示选择所有的后代元素. 如果你也要中断G
元素自身的过渡,则:selection.interrupt().selectAll("*").interrupt();
d3.interrupt(node[, name]) <源码>
中断指定 node 上指定 name 的活跃的过渡,并取消未执行的指定 name 的过渡(如果存在的话)。如果没有指定 name 则使用
null
。参考 selection.interrupt。d3.transition([name]) <源码>
在根元素
document.documentElement
上返回一个新的过渡,并指定 name。如果没有指定 name,则使用null
。新的过渡只与同名的过渡相排斥。name 也可以是一个 transition 实例;参考 selection.transition。这个方法等价于:d3.selection() .transition(name)
这个函数也可以被用来测试是否是过渡实例 (
instanceof d3.transition
) 或者用来扩展过渡原型链。transition.select(selector) <源码>
对于每一个选中的元素,选中匹配指定 selector 的第一个后代元素(如果存在的话) 并在结果选择集上返回一个过渡。selector 可以是一个字符串也可以是一个函数。如果是函数的话,会为每一个选中的元素依次调用,并传递当前数据
d
以及索引i
, 函数内部this
指向当前DOM
元素。新的过渡拥有相同的id
,name
以及时间;如果选中的元素已经存在相同id
的过渡,则已存的过渡会被返回。这个方法等价于通过 transition.selection 从过渡中获取选择集,通过 selection.select 创建一个子选择集 然后通过 selection.transition 创建一个新的过渡:
transition .selection() .select(selector) .transition(transition)
transition.selectAll(selector) <源码>
对于每一个选中的元素,选中匹配指定 selector 的所有后代元素(如果存在的话) 并在结果选择集上返回一个过渡。selector 可以是一个字符串也可以是一个函数。如果是函数的话,会为每一个选中的元素依次调用,并传递当前数据
d
以及索引i
, 函数内部this
指向当前DOM
元素。新的过渡拥有相同的id
,name
以及时间;如果选中的元素已经存在相同id
的过渡,则已存的过渡会被返回。这个方法等价于通过 transition.selection 从过渡中获取选择集,通过 selection.selectAll 创建一个子选择集 然后通过 selection.transition 创建一个新的过渡:
transition .selection() .selectAll(selector) .transition(transition)
transition.filter(filter) <源码>
对于每一个选中的元素,选中匹配指定 filter 的元素并在结果选择集上返回一个过渡。filter 可以是一个字符串也可以是一个函数。如果是函数的话,会为每一个选中的元素依次调用,并传递当前数据
d
以及索引i
, 函数内部this
指向当前DOM
元素。新的过渡拥有相同的id
,name
以及时间;如果选中的元素已经存在相同id
的过渡,则已存的过渡会被返回。这个方法等价于通过 transition.selection 从过渡中获取选择集,通过 selection.filter 创建一个子选择集 然后通过 selection.transition 创建一个新的过渡:
transition .selection() .filter(filter) .transition(transition)
transition.merge(other) <源码>
返回一个将当前过渡与指定的 other 过渡合并的新的过渡,并且指定的过渡必须与当前过渡有相同的
id
。返回的过渡与当前过渡有相同的分组数量,相同的父节点以及相同的id
。任何当前过渡中缺失(null
)的元素将会被来自 other 过渡中对应的元素填充(如果非空)。这个方法等价于通过 transition.selection 从过渡中获取选择集,通过 selection.merge 创建一个子选择集然后通过 selection.transition 创建一个新的过渡:
transition .selection() .merge(other.selection()) .transition(transition)
transition.transition() <源码>
当前过渡结束时,在相同的选定元素上返回一个新的过渡,这个过渡将在当前过渡结束时开始。新的过渡继承了参考时间,也就是新的过渡的开始时间等于之前过渡的 delay 和 duration] 之和。新的过渡也继承了当前过渡的
name
,duration
以及 easing. 这个方法可以用来创建一些列的链式过渡, 例如:d3.selectAll(".apple") .transition() // First fade to green. .style("fill", "green") .transition() // Then red. .style("fill", "red") .transition() // Wait one second. Then brown, and remove. .delay(1000) .style("fill", "brown") .remove();
每个过渡的延时都与前序过渡有关。上述例子中,
apples
将在过渡为brown
之前保持1
秒的red
。transition.selection() <源码>
返回当前过渡对应的 selection.
d3.active(node[, name]) <源码>
返回指定 node 上指定 name 的活动的过渡(如果存在的话)。如果没有指定 name 则使用
null
。如果指定的 node 上没有对应的过渡则返回null
。这个方法在创建循环过渡时很有用,比如:d3.selectAll("circle").transition() .delay(function(d, i) { return i * 50; }) .on("start", function repeat() { d3.active(this) .style("fill", "red") .transition() .style("fill", "green") .transition() .style("fill", "blue") .transition() .on("start", repeat); });
参考 chained transitions 获取更多例子.
Modifying Elements
在选中元素并使用 selection.transition 创建过渡之后,使用过渡的变换方法来影响文档的内容。
transition.attr(name, value) <源码>
对于每个选中的元素,将为指定的属性 name 分配 attribute tween(属性补间) 值。补间的初始值为过渡开始时的属性值,目标值可以通过常量和函数指定。如果是函数的话,会立即为每个选中的元素调用,并传递当前的数据
d
以及索引i
, 函数内部this
指向当前DOM
元素。如果目标值为
null
则当过渡开始时此属性会被移除。否则会根据目标值的类型依据以下算法选择恰当的插值器:- 如果 value 为数值, 使用 interpolateNumber.
- 如果 value 为 color 或可以被强制转为颜色的字符串, 使用 interpolateRgb.
- 使用 interpolateString.
如果想使用其他插值器,则使用 transition.attrTween 方法.
transition.attrTween(name[, factory]) <源码>
如果指定了 factory 并且不为
null
, 则将指定 name 的属性 tween 设置为指定的插值器 factory。factory 是一个返回 interpolator 的函数; 当过渡开始时,factory 会为每个选中的元素进行调用,并依次传递当前元素绑定的数据d
以及索引i
, 函数内部this
指向当前DOM
元素。返回的插值器会在过渡过程中的每一帧进行调用,并依次传入 eased(缓动) 时间 t, 通常情况下在 [0, 1] 范围内。最后插值器返回的值将会被用来设置为当前属性值。差孩子气必须返回字符串。(在过渡开始时移除属性使用transition.attr; 在过渡结束时移除属性使用 transition.on 来监听 end 事件.)如果指定的 factory 为
null
, 则表示移除之前改属性名对应的属性补间(如果存在的话)。如果 factory 没有指定则返回当前指定 name 的插值器工厂函数,如果不存在则返回undefined
。例如,在
red
和blue
之间进行插值:transition.attrTween("fill", function() { return d3.interpolateRgb("red", "blue"); });
或者从当前填充颜色过渡到
blue
,与 transition.attr 类似:transition.attrTween("fill", function() { return d3.interpolateRgb(this.getAttribute("fill"), "blue"); });
或者应用一个自定义的
rainbow
插值器:transition.attrTween("fill", function() { return function(t) { return "hsl(" + t * 360 + ",100%,50%)"; }; });
这个方法在指定自定义插值器时非常有用,比如可以对 SVG paths 进行过渡。一个很有用的是对 data interpolation,利用 d3.interpolateObject 对两个数据值进行插值, 然后使用插值过程中的值(比如使用 shape) 可以被用来设置为属性值。
transition.style(name, value[, priority]) <源码>
对于每个选中的元素,将为指定的样式 name 分配 style tween(样式补间) 值, 并设置指定的 priority(优先级)。补间的初始值为过渡开始时的内联样式值(如果存在),否则为计算值,目标值可以通过常量和函数指定。如果是函数的话,会立即为每个选中的元素调用,并传递当前的数据
d
以及索引i
, 函数内部this
指向当前DOM
元素。如果目标值为
null
则当过渡开始时此属性会被移除。否则会根据目标值的类型依据以下算法选择恰当的插值器:- 如果 value 为数值, 使用 interpolateNumber.
- 如果 value 为 color 或可以被强制转为颜色的字符串, 使用 interpolateRgb.
- 使用 interpolateString.
如果想使用其他插值器,则使用 transition.styleTween 方法.
transition.styleTween(name[, factory[, priority]])) <源码>
如果指定了 factory 并且不为
null
, 则将指定 name 的样式 tween(补间) 设置为指定的插值器 factory。插值器工厂是一个返回 interpolator 的函数; 当过渡开始时, factory 会为每个选中的元素调用并依次传入当前元素绑定的数据d
以及索引i
, 函数内部this
指向当前DOM
元素。返回的插值器会在过渡过程中的每一帧进行调用并依次传入 eased(缓动) 时间 t, 通常处于 [0, 1] 范围之内。最后插值器返回的值会被设置为带有指定 priority 的样式值。插值器必须返回一个字符串。(在过渡开始时移除样式使用 transition.style; 在过渡结束时移除样式使用 transition.on 来监听 end 事件。)如果指定的 factory 为
null
, 则表示移除之前改属性名对应的样式补间(如果存在的话)。如果 factory 没有指定则返回当前指定 name 的插值器工厂函数,如果不存在则返回undefined
。例如,在
red
和blue
之间进行填充样式插值:transition.styleTween("fill", function() { return d3.interpolateRgb("red", "blue"); });
或者从当前填充颜色过渡到
blue
,与 transition.style 类似:transition.styleTween("fill", function() { return d3.interpolateRgb(this.style.fill, "blue"); });
或者应用一个自定义的
rainbow
插值器:transition.styleTween("fill", function() { return function(t) { return "hsl(" + t * 360 + ",100%,50%)"; }; });
这个方法在自定义插值器时非常有用,比如对 data interpolation,d3.interpolateObject 可以用来对数据值进行插值,插值过程中的补间值可以用来设置样式值。
transition.text(value) <源码>
对于每个选中的元素,在过渡开始时设置 text content 为指定的目标 value。value 可以是一个常量也可以是一个函数。如果是函数则会立即为每个选中的元素调用,并依次传递当前数据
d
以及索引i
,函数内部this
指向当前DOM
元素。函数的返回值将会被作为每个元素的文本内容。null
表示清空内容。使用 transition.tween (for example(例子)) 对文本插值要比在开始时就设置内容要好很多,也可以使用一个淡入淡出的元素来替代 (for example(例子))。文本默认情况下不会被插值因为通常情况下是不能被插值的。
transition.remove() <源码>
对于每个选中的元素,如果在过渡结束后没有活动的过渡或者没有还未执行的过渡则 removes 此元素,否则什么都不做。
transition.tween(name[, value]) <>源码
对每个选中的元素,使用指定的 value 函数作为指定 name 的补间。value 必须是一个返回函数的函数。在过渡开始时,指定的函数会为每一个选中的元素进行调用,并传递当前元素绑定的数据
d
以及索引i
,函数内部this
指向当前DOM
元素。返回的函数会在过渡中的每一帧进行调用,并传递当前 eased 时间 t, 通常情况下处于 [0, 1] 之间。如果指定的 value 为null
则表示移除之前指定 name 对应的 tween 补间(如果存在的话)。例如,将
fill
属性进行插值,类似于 transition.attr:transition.tween("attr.fill", function() { var node = this, i = d3.interpolateRgb(node.getAttribute("fill"), "blue"); return function(t) { node.setAttribute("fill", i(t)); }; });
这个方法在自定义插值器或进行其他操作时很有用, 比如对 scroll offset 进行过渡。
Timing
过渡的 easing, delay 和 duration 都是可配置的。例如在元素排序时候每个元素的 stagger the reordering(交错排序) 可以提高感知能力。参考 Animated Transitions in Statistical Data Graphics(统计数据图形中的动画转换) 获取过渡动画的相关建议。
transition.delay([value]) <源码>
对于每个选中的元素,将当前元素的过渡的延时设置为指定的 value(毫秒)。value 可以是一个常量也可以是一个函数,如果是函数则会为每个元素立即调用并依次传递当前数据
d
以及索引i
,函数内部this
指向当前DOM
元素。函数的返回值被用来设置为该元素的过渡延时。如果没有指定延时,则默认为0
。如果没有指定 value 则返回当前第一个非空元素的延时时间。在已知过渡仅仅包含一个元素的情况下,这种获取形式通常有用。
将当前元素过渡的延时设置为当前元素索引
i
的倍数是一种方便的创建交错过渡的方式。例如:transition.delay(function(d, i) { return i * 10; });
当然,你可以以函数的形式动态计算延时,或者在计算基于索引的延时之前 sort the selection(对选择集排序)。
transition.duration([value]) <源码>
对于每个选中的元素,设置过渡时长为指定的 value(毫秒)。value 可以是一个常量也可以是一个函数。如果是函数则会为每个元素立即调用并依次传递当前数据
d
以及索引i
,函数内部this
指向当前DOM
元素。函数的返回值被用来设置为该元素的过渡时长。如果没有指定过渡时长,则默认为250ms
。如果没有指定 value 则返回当前第一个非空元素的过渡时长。在已知过渡仅仅包含一个元素的情况下,这种获取形式通常有用。
transition.ease([value]) <源码>
为所有选中的元素指定过渡的 easing function(缓动函数)。value 必须为函数。缓动函数会在过渡中的每一帧进行调用,并传递归一化的时间 t,处于 [0, 1] 之内;并且必须返回缓动时间 tʹ, tʹ 通常处于 [0, 1] 之间。一个好的缓动函数在 t = 0 时候返回
0
并且 t = 1 时候返回1
。如果没有指定缓动函数则默认为 d3.easeCubic如果没有指定 value 则返回当前过渡中第一个非空元素的缓动函数。在已知过渡只包含一个元素的情况下通常是有用的。
Control Flow
高级用法,过渡提供了一些自定义控制流的方法。
transition.on(typenames[, listener]) <源码>
为每个选中的元素的指定的事件: typenames 添加或移除一个 listener。typenames 为以下字符串类型中的一种:
start
- 过渡开始.end
- 过渡结束.interrupt
- 过渡被中断.
参考 The Life of a Transition 获取更多信息。要注意的是这些不是由 selection.on 和 selection.dispatch 实现的原生
DOM
事件,而是过渡独有的事件。type
可以是一个.
与后面跟着的name
组成; 可选的name
允许在同一个类型上注册多个回调。比如start.foo
和start.bar
。也可以多个typenames
通过空格隔开,比如interrupt end
或start.foo start.bar
。当指定的过渡事件在选中的节点上触发的时候,对应的 listener 将会为被过渡的元素调用,并传递当前数据
d
以及索引i
,函数内部this
指向当前DOM
元素。事件监听器总是读取元素上绑定的最新的数据,而索引则是在分配监听器的时候就被固定;可以重新注册监听器的方式更新索引。如果已选中的元素上已经存在相同 typename 的事件监听器,则注册新的监听器之间会将旧的移除。如果要移除事件监听器则将 listener 为
null
即可。移除指定name
所有的事件监听器则将typename
设置为.foo
形式并将 listener 设置为null
;移除所有没有name
的事件监听器则将typename
设置为.
并将 listener 设置为null
。如果没有指定 listener 则返回当前第一个非空元素上对应 typename 的事件监听器(如果存在的话)。如果元素上注册了多个 typenames 则返回第一个匹配的监听器。
transition.each(function) <源码>
为过渡中的每个选中的元素调用指定的 function,并传递当前元素
d
以及索引i
,函数内部this
指向当前DOM
元素。这个方法可以被用来为每个选中的元素调用任意代码,并且创建了一个能访问当前元素父节点和子节点数据的上下文。等价于 selection.each。transition.call(function[, arguments…]) <源码>
调用一次指定的 function,并传递可选的参数。返回当前过渡。这等价于手动调用函数,但简化了方法链。例如以可服用函数的形式过渡设置属性:
function color(transition, fill, stroke) { transition .style("fill", fill) .style("stroke", stroke); }
然后可以:
d3.selectAll("div").transition().call(color, "red", "blue");
等价于:
color(d3.selectAll("div").transition(), "red", "blue");
等价于 selection.call.
transition.empty() <源码>
如果当前过渡中不包含任何非空元素则返回
true
。等价于 selection.empty.transition.nodes() <源码>
返回当前过渡中所有的非空元素。等价于 selection*.nodes.
transition.node() <源码>
返回当前过渡中第一个非空元素。如果不包含任何元素则返回
null
。等价于 selection*.node.transition.size() <源码>
返回当前过渡中元素总数。等价于 selection.size.
The Life of a Transition
在通过 selection.transition 或 transition.transition 创建一个过渡之后,你可以使用 transition.delay, transition.duration, transition.attr 和 transition.style 来配置过渡。指定目标值(比如 transition.attr) 是同步执行的。但是需要初始值的方法,比如 transition.attrTween 和 transition.styleTween 必须被推迟到过渡开始时执行。
在过渡创建后不久,要么是当前帧的结尾,要么是下一帧,过渡会被安排到队列中。此时
delay
以及start
事件监听器不能再被改变,如果试图这么做会抛出错误信息 “too late: already scheduled” (如果在过渡已经结束则会抛出错误信息 “transition not found”).当过渡开始后,会中断元素上同名的其他过渡(如果存在的话),并且触发已注册的
interrupt
事件。(要注意的是,中断发生在过渡开始时,而不是创建时,即便是一个零延时的过渡也不会立即中断活动的过渡: 旧的过渡还会执行最后一帧。使用 selection.interrupt 来立即中断过渡)。过渡开始也会取消相同元素上同名未执行的过渡。过渡会为注册的事件监听器派发一个start
事件。这是过渡可能被修改的最后时刻: 过渡一旦开始,过渡时间,补间以及监听器不会再被改变。如果试图修改则会抛出错误信息 “too late: already started” (如果过渡已经结束则会抛出 “transition not found”)。过渡在开始后会立即初始化补间。在过渡开始的这一帧,而不是所有的过渡都开始后,过渡会首次调用补间。补间初始化 (通常从
DOM
中读取) 会避免交错的DOM
读写来提高性能。过渡活动的每一帧,都会使用一个 eased 时间 t-位于
0
到1
之内的值调用补间。在每一帧内,过渡会依次调用注册的补间。在过渡结束时,会以 t 等于
1
去调用它的补间。并派发一个end
时间到注册的事件监听器。这是过渡可以被检测到的最后时刻:结束之后,过渡会从元素上被删除,并且相关的配置也会被销毁。(一个过渡的配置在销毁或中断时也会被销毁)。如果在过渡被销毁之后试图去操作这个过渡则会抛出错误信息 “transition not found”。