javascript - 使用DOM对象跟踪路径

标签 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);
            }

[另外:我听说,与计算总延迟相比,queue()会更好。注释?]但是,过渡的缓和属性使其以断断续续的方式运行。我想我可以不指定放松,但是我确信必须有一种更好的方法来实现我想要的效果,这仅仅是使初始DOM对象(圆)沿着特定的轨迹平滑移动。

最后,我想对多个DOM对象执行此操作,这些对象最终将链接到数据,每个对象都有一条特定的曲线。关于如何执行此操作的任何提示?

在此先感谢您的帮助,我们将乐意接受任何建议,包括引用。

最佳答案

有趣但并非十分实用的方法

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会调用适当的“补间”功能,并提供有关过渡沿多远的信息。因此,如果滴答声发生在500ms到2000ms的过渡中,则补间函数将获得值0.25(假设是线性缓动函数,其他缓动函数会使经过的时间与过渡之间预期的“距离”之间的关系复杂化)。

现在,对于大多数更改而言,补间功能非常简单,并且d3将自动为您解决一个问题。如果将“cx”值从100更改为200,则当过渡值25%时,补间函数将返回125;在过渡值为50%时,补间函数将返回150,依此类推。如果将“填充”值从红色更改为黄色,它将计算这些颜色的数值并在它们之间转换。

然后,补间函数在每个刻度处返回的值将用于更新元素的属性或样式。由于更新每秒发生多次,因此通常会产生平滑的动画。对于更改圆的“cx”值的简单示例,圆从起点到终点沿直线移动。

但是您不希望它直线移动。您希望它绕圆运动(或沿着您选择的任何路径)。因此,您将需要创建一个自定义函数,该函数告诉圆在过渡过程中应占25%的位置,在过渡过程中应占50%的位置,依此类推。

而且,如果您担心自己必须自己弄清楚这一点,请不要害怕。像这样,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以外的位),并在过渡中计算该状态下的属性值。

您会注意到该函数返回翻译指令。与使用cxcy相比,这通常将是一种更容易移动对象的方法,因为您可以在一个transform属性调用中同时指定水平和垂直移动,因此只需要一个补间功能即可完成这两个操作。

这是我上面的示例,已更新为使用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来为每个circle元素找到正确的<path>元素。

注意,为了使用getTotalLength()getPointAtLength()函数,您需要一个SVG路径元素。但是,如果您不希望该路径显示在屏幕上,则该路径可以是不可见的(CSS中的fill:none; stroke:none;)。同样,虽然我的路径定义是硬编码的,但是您可以使用d3的arcline生成器之一为您构造它。

只是为了好玩,这是我用另一个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/

关于javascript - 使用DOM对象跟踪路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21226523/

相关文章:

javascript - ClearInterval不会停止

javascript - React - 如何给每个子组件一个唯一的引用?

php - 如果表单验证失败,请不要提交表单

javascript - 使 D3 响应 : viewBox vs resize()?

xml - 寻找 XSLT 库来创建 SVG 图表

java - 如何使用 SVG 创建具有以下 HTML 字符串的 PDF 文档?

javascript - 在 jquery 中使用 css 设置背景大小

Javascript - 当我在输入表单中按 Enter 键时代码正在运行

javascript - 使用另一个数组从数组中删除条目

javascript - 为什么有些对象在 IE 中没有构造函数?