有趣但不太实用的方法
SVG 规范实际上有许多动画选项,包括沿路径移动对象的能力。路径的定义形式与 <path> 元素的定义相同,因此您可以使用 d3.svg.arc 函数来创建路径。
一旦定义了路径,就可以很容易地使用 d3 在动画中添加:
http://fiddle.jshell.net/RnNsE/1/
虽然你会想阅读SVG animation elements and attributes。
但是,这个精彩的动画有一个限制:poor browser support。因此,如果这是针对网站的,您将需要使用 d3 和 Javascript 来制作动画。
生产就绪方法
让 d3 为您创建流畅动画的关键是在过渡上使用自定义 "tween" function。
当您进行转换时,d3 会为每个元素的每次更改初始化一个补间函数,并启动计时器函数以触发更新。在计时器的每个“滴答”处,d3 调用适当的“补间”函数,并提供有关其过渡距离的信息。因此,如果滴答声在 2000 毫秒转换中出现 500 毫秒,补间函数将给出值 0.25(假设是线性缓动函数,其他缓动函数会使经过的时间与沿转换的预期“距离”之间的关系复杂化)。
现在,对于大多数更改,补间函数相当简单,d3 会自动为您计算出一个。如果将“cx”值从 100 更改为 200,则 tween 函数将在转换值为 25% 时返回 125,在转换为 50% 时返回 150,依此类推。如果您将“填充”值从红色更改为黄色,它将计算这些颜色的数值并在它们之间进行转换。
tween 函数在每个刻度处返回的值随后用于更新元素的属性或样式。由于更新每秒发生多次,因此通常会产生流畅的动画。以改变圆的“cx”值为例,圆从起点到终点直线运动。
但您不希望它沿直线移动。您希望它在一个圆圈中移动(或沿着您选择的任何路径)。因此,您将需要创建一个自定义函数,告诉圆圈它应该在过渡的 25% 的位置,以及在过渡的 50% 的位置,等等。
如果您担心必须自己解决,请不要害怕。就像这样,so 和 D3 一样,Mike Bostock has done the hard work for you。但即使是他也不必做艰苦的艰苦的工作。他的方法为 SVG 路径使用了两个内置的 Javascript 函数,getTotalLength() 和 getPointAtLength()。第一个告诉您路径的总长度,第二个为您提供距离路径起点一定距离的点的坐标。
有了这两个值,如果您想在路径上达到一定百分比,就可以很容易地计算出您应该在的坐标:在 25% 时,您想在path.getPointAtLength(0.25*path.getTotalLength() )。
下面是 Mike 实现这一目标的函数:
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
有点混乱,不是吗?函数返回函数返回函数。
这是因为当您为过渡指定“补间”时,您实际上必须指定的是“补间工厂”——该函数将为您选择的每个元素返回适当的补间函数。
现在,在他的示例中,他只有一条路径和一个沿路径移动的对象,因此不会使用那些额外的层。但在一般情况下,您的补间工厂函数将采用参数d(选择中该元素的数据对象)、i(该元素的索引)和a(属性的初始值或您正在改变的风格)。使用这些值,您必须返回自定义补间函数,该函数采用过渡状态值t(介于 0 或 1 之间的数字,或者对于某些缓动函数可能略大于 1)并计算该状态下的属性值过渡。
你会注意到这个函数返回一个翻译指令。与使用cx 和cy 相比,这通常是移动对象的一种更简单的方法,因为您可以在一个变换属性调用中同时指定水平和垂直移动,因此您只需要一个补间函数即可两者都有。
这是我上面的示例,已更新为使用 d3 补间沿路径移动圆圈:
http://fiddle.jshell.net/RnNsE/2/
关键代码:
circles.transition().ease("linear")
.duration(5000)
.delay(function(d,i){return i*5000;})
.attrTween("transform", createPathTween);
//creates a tween function to translate an element
//along the path that is a sibling to the element
function createPathTween(d, i, a) {
var path = this.parentNode.getElementsByTagName("path")[0];
//i.e., go from this <circle> -> parent <g> -> array of child <path> elements
-> first (and only) element in that array
var l = path.getTotalLength();
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
}
我的版本从 Mike 的版本中去掉了最外层的嵌套函数,但它添加了一些 Javascript 来为每个圆形元素找到正确的 <path> 元素。
请注意,您需要一个 SVG 路径元素才能使用 getTotalLength() 和 getPointAtLength() 函数;但是,如果您不希望它显示在屏幕上,则此路径可能是不可见的(CSS 中的fill:none; stroke:none;)。同样,虽然我的路径定义是硬编码的,但您可以使用 d3 的 arc 或 line 生成器之一为您构建它。
只是为了好玩,这是我的示例,带有不同的 easing function:
http://fiddle.jshell.net/RnNsE/3/
请注意,我没有更改补间函数的任何内容——所有更改的是 d3 在过渡过程中传递给该函数的 t 值。
附:这是关于 d3 自定义补间函数的另一个很好的资源:
http://blog.safaribooksonline.com/2013/07/11/reusable-d3-js-using-attrtween-transitions-and-mv/