我是javascript和d3js的新手。我想要一个DOM对象来追踪由参数化曲线(x(t),y(t))指定的路径。以下是此类参数化的示例:
var theta = [];
for(var i = 0; i <= N; i++){
theta.push(2*Math.PI*i/N);
}
var points = [];
for(var i = 0; i <= N; i++){
points.push([Math.cos(theta[i]),Math.sin(theta[i])]);
}
以上是曲线的参数化 - 在这种情况下,也是一个圆 - 我希望我的DOM对象遵循这条曲线的轨迹。 [旁白:有没有更好的方法来定义points
?运行for循环似乎很荒谬。]
实现我正在寻找的那种效果的粗略方法是在d3的update()部分中运行for循环。首先,我只是在svg变量上附加一个圆圈,这样就不需要将它链接到任何数据。然后选择并更新它,无需进入/退出。
for (var i = 0; i <= N; i++){
svg.selectAll("circle")
.transition()
.attr("cx",points[i][0]+w/2) // w: width
.attr("cy",points[i][1]+h/2) // h: height
.duration(dt) //
.delay(dt*i);
}
[旁白:我听说队列()会更好,而不是计算总延迟。注释?]然而,过渡的缓和属性使它以波涛汹涌的方式运行。我想我可以指定不缓和,但我确信必须有更好的方法来实现我想要的,这只是初始DOM对象(圆圈)沿着特定轨迹平滑移动。
最后,我想对多个DOM对象执行此操作,这些对象最终将链接到数据,每个对象都有一个特定的曲线。关于如何做到这一点的任何提示?
提前感谢您的帮助,我很乐意接受任何建议,包括参考。
答案 0 :(得分:7)
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会为每个元素的每个更改初始化一个补间函数,并启动计时器函数以触发更新。在每个&#34; tick&#34;在计时器中,d3调用适当的&#34; tween&#34;函数包含有关转换的距离的信息。因此,如果勾号在2000ms过渡期间发生500ms,则补间函数将给出值0.25(假设线性缓动函数,其他缓动函数使经过的时间与预期的&#34;距离&#34;沿着过渡)之间的关系变得复杂。 。
现在,对于大多数更改,补间功能相当简单,d3将自动为您计算一个。如果您更改&#34; cx&#34;值从100到200,那么当转换值为25%时,补间函数将返回125,当转换为50%时,返回150,依此类推。如果您更改&#34;填写&#34;从红色到黄色的值,它将计算这些颜色的数值并在它们之间进行转换。
然后,使用每个tick处的补间函数返回的值来更新元素的属性或样式。由于更新每秒发生多次,因此通常会产生平滑的动画。对于更改&#34; cx&#34;的简单示例圆的值,圆从起点到终点沿直线移动。
但你不希望它直线前进。您希望它以圆圈(或沿您选择的任何路径)移动。因此,您需要创建一个自定义函数,告诉圆圈它应该在过渡的25%的位置,以及在过渡期间它应该是50%的位置,依此类推。
如果你担心你必须自己解决这个问题,就不要害怕。就像这样,所以很多D3,Mike Bostock has done the hard work for you。但即使他没有必要做 hard 努力工作。他的方法使用两个内置的Javascript函数用于SVG路径,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 + ")";
};
};
}
有点混乱,不是吗?返回返回函数的函数的函数。
那是因为当你指定一个&#34; tween&#34;对于过渡,你实际需要指定的是一个&#34; tween工厂&#34; - 将为您选择中的每个元素返回适当补间函数的函数。
现在,在他的例子中,他只有一条路径和一条物体沿着它移动,因此这些额外的层不会被使用。但在一般情况下,补间工厂函数将采用参数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
值。
P.S。这是d3自定义补间函数的另一个好资源: http://blog.safaribooksonline.com/2013/07/11/reusable-d3-js-using-attrtween-transitions-and-mv/