跟踪DOM对象的路径

时间:2014-01-20 04:57:26

标签: javascript dom svg d3.js

我是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对象执行此操作,这些对象最终将链接到数据,每个对象都有一个特定的曲线。关于如何做到这一点的任何提示?

提前感谢您的帮助,我很乐意接受任何建议,包括参考。

1 个答案:

答案 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)并计算该状态的属性值在过渡期。

您将注意到此函数返回翻译指令。与使用cxcy相比,这通常会更容易移动对象,因为您可以在一个转换属性调用中指定水平和垂直移动,因此您只需要一个补间函数来同时执行这两个操作。

这是我上面的例子,更新后使用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的arcline生成器之一为您构建它。

只是为了好玩,这里有我的例子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/