偶尔结结巴巴地说“;堆叠的”;translate()上的转换(v4.0.0-alpha40)

Occasional stuttering with "stacked" transitions on `translate()` (v4.0.0-alpha40)

本文关键字:结结巴巴 转换 v4 0-alpha40 translate 偶尔      更新时间:2023-09-26

问题:抖动

我有一个D3v4.0.0α.40图,它周期性地(不是在每帧上):

  1. 获取新的数据点
  2. 删除旧数据点
  3. 重置转换并设置translate(null)
  4. 启动新的transition() new在获取新值时滚动
// Animation
function animateOnce(period) {
    // Data: Add new, and remove old
    data.unshift(Math.random());
    data.pop();
    // Do 2 transitions...
    path
      .attr("d", line)
      .transition() /* ...reset */
        .attr("transform", null)
        .duration(0)
      .transition()/* ...scroll across */
        .duration(period)
        .ease(d3.easeLinear)
        .attr("transform", `translate(${x(1)})`)
};
// Animation looper
function animate() {
    setTimeout(_=> {
        animateOnce(PERIOD);
    animate();
    },
  PERIOD);
}

然而,转换似乎执行得并不干净——每隔几秒钟就会有一次急动

我以前在D3v3中遇到过这个问题,但我相信我通过添加重置转换(上面的步骤3)解决了这个问题。不幸的是,我没有D3的经验,我不知道如何解决这个问题。

看看吧

这个jsFiddle是我的图的近似值,你应该能够看到偶尔的抖动。

注意:fiddle使用setTimeout,而我的实际图形是react组件,用componentDidUpdate()更新。

编辑1:使用中断改进

改进的jsFiddle

在阅读文档时(正如@Ashitaka建议的那样),我发现了interrupt()。这会正确地终止转换,并且可能是实现上述步骤3的"v4方式"(重置转换)。

// Animation
function animateOnce(period) {
    // Data: Add new, and remove old
    data.unshift(Math.random());
    data.pop();
    // Do 2 transitions...
    path
      .attr("d", line)
      .interrupt() /* ...reset */
      .attr("transform", null)
      .transition()/* ...scroll across */
        .duration(period)
        .ease(d3.easeLinear)
        .attr("transform", `translate(${x(1)})`)
};

这改善了急动(我认为是由竞争过渡引起的),将它们变成了小的口吃

我想了解(我假设是1帧)口吃是在哪里引入的。所以我暂时不谈。

在Mike Bostock的文章《使用过渡》中,他写道:

对于给定的元素,转换是排他的:只有一个转换可以同时在元素上运行。开始新的元素上的转换停止任何已经跑步

现在,我在呈现的代码中检测到两个问题:

  1. 正在为路径变换重置设置动画(即使具有0持续时间)。此新转换将取消上一个转换。这可以通过更改来修复:

    path
      .attr("d", line)
      .transition()
        .attr("transform", null)
        .duration(0)
    

    至:

    path
      .attr("d", line)
      .attr("transform", null)
    
  2. animateOnce函数的调用周期与D3的转换周期相同,并且转换周期持续约17毫秒。此新转换也取消了以前的转换。这可以通过更改来修复:

    function animate() {
      setTimeout(_=> {
        animateOnce(PERIOD);
        animate();
      },
      PERIOD);
    }
    

    至:

    function animate() {
      setTimeout(_=> {
        animateOnce(PERIOD);
        animate();
      },
      PERIOD + 20);
    }
    

    可以使用setInterval将其进一步重构为:

    function animate() {
      setInterval(animateOnce, PERIOD + 20, PERIOD);
    }
    

这两个更改应该可以解决jank问题。尽管如此,每80毫秒更新一次折线图总是会对某人的电脑或智能手机造成负担。我建议你每200毫秒左右更新一次。

编辑:我做了一些实验,注意到Firefox上仍然有一些jank。因此,还有几点需要考虑:

  1. transform属性设置为null实际上会创建一个新层。这可以通过更改来修复:

    .attr("transform", null)
    

    至:

    .attr("transform", "translate(0)")
    
  2. 每次调用animateOnce函数时,都会重新创建转换字符串。我们可以在animateOnce之外预先计算它们,然后重用它们。这可以通过更改来修复:

    .attr("transform", "translate(0)")
    .attr("transform", `translate(${x(1)})`)
    

    至:

    // outside the animateOnce function:
    let startStepAttrs = { transform: "translate(0)" },
        endStepAttrs   = { transform: `translate(${x(1)})` };
    // inside the animateOnce function:
    .attr(startStepAttrs)
    .attr(endStepAttrs)