关于transition的几个基本点:
1. transition()是针对与每个DOM element的,每个DOM element的transition并不会影响其他DOM element的transition操作;
2. 对于每一个DOM element的transition每次只能执行一个,如果在一个DOM element上添加了许多transition操作,只有最后一个会起作用,前面的都会被覆盖掉。但是例外是,当这些transition是chaining的形式连接的时候,这些transition会按照先后顺序依次执行(大致形式就是:.....transition()....transition()....)。具体可以查看这个例子:http://bl.ocks.org/mbostock/3943967
3. 在对每个transition动作除了用duration()函数指定持续时间之外,也可以通过delay()函数来对transition动作加一定时间的时间延迟,2中提到的例子有相关的代码。
4. transition比较核心的一点就是,必须要有两个核心的frame:起始frame是怎么样,终止frame是什么样。这些都要在schedule一个transition动作的时候指定好。因此有些操作就没法加transition,例如,创建一个新的DOM element,因为没有起始frame.
5. 并不是所有的属性都能加transition,目前D3支持四种数据/属性的transition:
- numbers
- colors
- geometric transforms
- strings with embedded numbers (e.g.,
"96px"
) (注意,这是目前能够应用transition的几种属性)
因此,如果要多一个文本的创建加transition就是不可能的。
6. Transition 和join操作相结合的一个常用的模板:
var bar = svg.selectAll(".bar")
.data(data, function(d) { return d.key; });
bar.enter().append("rect")
.attr("class", "bar")
… // initialize entering bars
bar.transition()
… // transition entering + updating bars
bar.exit().transition()
… // transition exiting bars
.remove();
主要参考文章:working with transitions: http://bost.ocks.org/mike/transition/
以下是Mike Bostock的博客原文:
Working with Transitions
D3’s selection.transition method makes it easy to animate transitions when changing the DOM. For example, to change the text color to red instantaneously, you can select the body element and set the color style:
d3.select("body").style("color", "red");
To instead animate the change over time, derive a transition:
d3.select("body").transition().style("color", "red");
This ease-of-use comes from D3’s transition interface mirroring the selection interface: nearly everything you can do to a selection of elements, you can also do to a transition, causing the change to be animated over time rather than instantaneous.
Yet looks can be deceiving because selections and transitions are not perfectly equivalent: transitions provide only a subset of selection functionality. These nuances are due to the long-running, asynchronous nature of transitions. Before you attempt more complicated uses of transitions, it is helpful to understand the system’s underlying design.
Note: This article discusses transitions as implemented in version 3.0, which was released in December 2012. You can download D3 3.0 from d3js.org or GitHub.
#Transitions Are a Form of Animation
For more general uses, consider implementing animations withd3.timer rather than as transitions.
Transitions are a limited form of key frame animation with only two key frames: start and end. The starting key frame is typically the current state of the DOM, and the ending key frame is a set of attributes, styles and other properties you specify. Transitions are thus well-suited for transitioning to a new view without complicated code that depends on the starting view.
Look again at this example transition:
d3.select("body").transition().style("color", "red");
Although only a single attribute value is specified—the color red—a second value is implied. This starting value is computed from the DOM via getComputedStyle or getAttribute.
In some cases, the computed starting value may not be what you want. For example, it can be different than what was set previously due to browsers converting values to canonical representations, say converting the color string "red"
to "rgb(255,0,0)"
. Depending on the interpolator you use (more on that in a bit), this may cause the transition to stutter. As another example, transparent colors are reported as "rgba(0,0,0,0)"
(transparent black), which is treated as opaque black when using RGB interpolation.
One way to make the starting value more explicit is to set it in the DOM before creating the transition. This won’t fix problems related to value conversion, but it can be useful if the inherited or default value isn’t what you want.
d3.select("body")
.style("color", "green") // make the body green
.transition()
.style("color", "red"); // then transition to red
If the transition has a delay, then the starting value should be set only when the transition starts. You can do this by listening for the start event:
This is a new feature in 3.0; older versions of D3 dispatched the start event after constructing tweens.
d3.select("body").transition()
.delay(750)
.each("start", function() { d3.select(this).style("color", "green"); })
.style("color", "red");
The most explicit approach to set the starting value is to use transition.styleTween. This skips computing the starting value from the DOM by specifying the both values and an interpolator.
d3.select("body").transition()
.styleTween("color", function() { return d3.interpolate("green", "red"); });
This last variation gives a hint as to how transitions work under the hood. When you use transition.style, D3 creates a style tween for you automatically by retrieving the starting value from the DOM and constructing an interpolator to the specified ending value. If you want to override the start value or the interpolator, do this using styleTween, attrTween or tween.
#Transitions Interpolate Values over Time
Given start and end key frames, how do we get from here to there? To perform a smooth animated transition, D3 needs to know how to interpolate—or blend—from a given starting value to its corresponding ending value. The d3.interpolate method determines an appropriate interpolator by inferring a type for each pair of starting and ending values. D3 supports several common types, including:
The same interpolation subsystem is used by D3’squantitative scales. You can extend the set of built-in interpolators by adding to thed3.interpolators array. Or create a custom tween.
- numbers
- colors
- geometric transforms
- strings with embedded numbers (e.g.,
"96px"
) (注意,这是目前能够应用transition的几种属性)
The string interpolator is particularly useful: it finds numbers embedded within strings, pairing up numbers in the starting string with the corresponding number in the ending string. Those numbers are interpolated separately, and the resulting string is then reconstructed. String interpolators have myriad applications, such as interpolating path data (e.g., "M0,0L20,30"
) and CSS font specifications (e.g., "300 12px/100% Helvetica"
).
String interpolation isn’t always appropriate. If the starting and ending path data have a different number of control points, for example, it’s no longer meaningful to pair up numbers. Instead, you need to resample the path prior to interpolation (or apply more advanced shape blending algorithms). Likewise, arcs require interpolation in polar coordinates so that the angles are interpolated, rather than the positions.
If you want to implement your own interpolator, you need a function that takes a single argument tranging from 0 to 1. For t = 0, the interpolator returns the starting value; for t = 1, the interpolator returns the ending value; and for values in-between, the interpolator returns a blending value. For example, to interpolate two numbers:
When rolling your own interpolator, you may still be able to reuse some of D3’sbuilt-in interpolators.
function interpolateNumber(a, b) {
return function(t) {
return a + t * (b - a);
};
}
When interpolating to or from zero, some interpolated values may be very small. JavaScript formats small numbers in exponential notation, which unfortunately is not supported by CSS. For example, when transitioning opacity to fade in or out, the number 0.0000001
is converted to the string "1e-7"
and then ignored, giving the default value of 1
! To avoid distracting flicker, start or end the transition at 1e-6
rather than 0
; this is the smallest value not formatted in exponential notation.
Some Things Cannot Be Interpolated
When modifying the DOM, use selections for any changes that cannot be interpolated; only use transitions for animation. For example, it is impossible to interpolate the creation of an element: it either exists or it doesn’t. Furthermore, in order to schedule a transition, the element must exist and so element creation cannot be deferred to start. Selection methods related to data joins (data, enter, exit) and creating elements (append, insert) can therefore only be performed on selections.
When using transitions in conjunction with the general update pattern, first perform the data-join on selections using enter and exit. Then derive transitions on each subselection as desired.
To perform a special transition on updating nodes, derive a transition before appending to the enter selection.
(以下是一个常用的使用transition加enter(), exit()集合的一个模板)var bar = svg.selectAll(".bar")
.data(data, function(d) { return d.key; });
bar.enter().append("rect")
.attr("class", "bar")
… // initialize entering bars
bar.transition()
… // transition entering + updating bars
bar.exit().transition()
… // transition exiting bars
.remove();
For convenience, there are a few exceptions to this rule. You can transition.remove to remove an element at the end of the transition; likewise, transition.text sets the text content at the start of the transition, without interpolating. In the future, transitions may support additional non-interpolatable operations, such as classed and html.
#The Life of a Transition
As with concurrent programming, perhaps the trickiest aspect of transitions is that they happen over time rather than instantaneously. The code does not proceed in a single straightforward path, as when the page loads, but as a complex sequence of recurring callbacks. While you can safely ignore this complexity in many cases, you must understand the rules which govern the evaluation of transitions if you want to harness their full power.
A useful mnemonic is that transitions are conceived, born,live and die. But perhaps that takes the metaphor too literally.
Transitions have a four-phase life cycle:
- The transition is scheduled.
- The transition starts.
- The transition runs.
- The transition ends.
A transition is scheduled when it is created: when you call selection.transition, you are scheduling a transition. This is also when you call attr, style, and other transition methods to define the ending key frame. Scheduling happens in your code (for example, in response to the user clicking a button), meaning that the code so far is fully synchronous. This makes it easier to debug, and easier to use ending values that depend on changing global state, such as a scale’s domain.
A transition starts based on its delay, which was specified when the transition was scheduled. If no delay was specified, then the transition starts as soon as possible, which is typically after a few milliseconds. The start event is then dispatched, and the transition initializes its tweens, which may involve retrieving starting values from the DOM and constructing interpolators. Deferring the initialization of tweens to start is necessary because starting values aren’t known until the transition starts. Therefore, if you use attrTween, styleTween and other tween methods, keep in mind that your code will be evaluated asynchronously when the transition starts!
While the transition runs, its tweens are repeatedly invoked with values of t ranging from 0 to 1. In addition to delay and duration, transitions have easing to control timing. Easing distorts time, such as for slow-in and slow-out. Some easing functions may temporarily give values of t greater than 1 or less than 0; however, the ending time is always exactly 1 so that the ending value is set exactly when the transition ends. A transition ends based on the sum of its delay and duration. When a transition ends, the tweens are invoked a final time with t = 1, and then the end event is dispatched.
#Transitions Are per-Element and Exclusive
There is no end event when all selected elements finish transitioning, although you can simulate this by counting.
Each element transitions independently. When you create a transition from a selection, think of it as a set of transitions, one per element, rather than a single mega-transition running on multiple elements. Different elements can have different delays and duration, and even different easing and tweens. Additionally, transition events are dispatched separately for each element. When you receive an end event for a given element, its transition has ended, but other transitions may still be running on other elements.
For a given element, transitions are exclusive: only one transition can be running on the element at the same time. Starting a new transition on the element stops any transition that is already running. Interrupting a transition on the element has no effect on other elements, and multiple transitions can run concurrently on different elements. While only one transition can be running simultaneously on the element, multiple transitions can be scheduled. For example, you can schedule successive transitions using transition.transition, which creates a new transition whose delay immediately follows the existing transition.
For each element, sequences of transitions only advance forward in time. Transitions are assigned a monotonically-increasing identifier (id
) when they are scheduled; each new transition id
is greater than the last. When a transition starts on the element, it can only run if the transition is newer than whatever previously ran on the same element. Thus, starting a new transition implicitly cancels any previous transitions—even if those old transitions have not yet started. This design eliminates the need to cancel transitions explicitly. An excellent illustration of this behavior is thestacked-to-grouped bar transition. This uses two chained transitions: the bars first slide right and narrow; then the bars drop to the baseline. The first transition also has a staggered delay. If you quickly toggle between stacked and grouped, notice the old transition is only interrupted when the new one starts, not when the new one is scheduled.
Similar to how data is bound to an element’s __data__
property, transitions are bound to a__transition__
property. When a transition is first scheduled on an element, this property is created; when the last scheduled transition ends, this property is likewise deleted. Inspecting this property in the console can be useful to debug which transitions are scheduled to run on which elements, as well as to inspect computed tweens and transition timing parameters. Because transitions are bound to elements, you can also reselect elements within a transition and modify tweens or timing. This is most common in conjunction with component-driven transitions, such as the axis component; use post-selection to customize the appearance of elements after invoking the component.
#Additional Reading
This tutorial covered most of the important details in the mechanics of transitions. I omitted an explanation of transition inheritance using transition.each; perhaps I’ll cover that in the future, though see #400 for an example. For more on how transitions should be used (and not just implemented), see my earlier post on object constancy and Heer & Robertson’s excellent paper,“Animated Transitions in Statistical Data Graphics”.