繁体   English   中英

如何链接 d3 转换?

[英]How to chain d3 transitions?

我有一组当用户移动滑块时发生的动画。 滑块条的每个增量都会创建一个过渡。 然而,每当用户非常快速地移动滑块时(即他们增加滑块的速度比过渡可以完成的更快),过渡上就会出现竞争条件,旧的会被中断,并且动画的“流程”很奇怪。 我希望有一系列转换,并让它们始终按照它们被调用的顺序发生。 即下一个不会开始,直到最后一个完成。 jsfiddle (按住“a”键与缓慢按下几次以查看差异)

var svg = d3.select("svg"),
    margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = svg.attr("width") - margin.left - margin.right,
    height = svg.attr("height") - margin.top - margin.bottom,

g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var y = d3.scalePoint()
    .domain(d3.range(50))
    .range([0, height]);

g.selectAll("circle")
  .data(y.domain())
  .enter().append("circle")
  .attr("r", 25)
  .attr("cx", 50)
  .attr("cy", y);

var radius = 25;
function animate() {
    radius = (radius+5)%50;
    g.selectAll("circle")
    .transition()
    .duration(500)
    .attr("r", radius);
}
document.addEventListener('keydown', e => e.code === "KeyA" ? animate() : 0);

您可以使用.on("end", callback)来收听动画的结束。 由于您有 50 个正在动画的对象,并且每个对象都将获得该事件,因此您需要一个计数器来了解其中最后一个何时完成。

为确保所有按键都产生动画,但只有在前一个按键完成后,您不能仅在按键事件上调用animate() 而是跟踪必须执行多少个调用,并在触发关键事件时增加该调用。 仅当该计数器为零时才调用animate()

请注意, animate()调用的这种排队也可能会产生不自然的行为:动画可能会一直持续到最后一个关键事件之后很久。 为了改善用户体验,您可以降低持续时间参数,以便在仍有大量关键事件需要处理其相应的animate()调用时动画加速。

以下是如何调整您的代码以完成所有这些工作:

var svg = d3.select("svg"),
    margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = svg.attr("width") - margin.left - margin.right,
    height = svg.attr("height") - margin.top - margin.bottom,

g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Use a variable for the number of items:    
var size = 50;
var y = d3.scalePoint()
    .domain(d3.range(size)) // <--- use that variable
    .range([0, height]);

g.selectAll("circle")
  .data(y.domain())
  .enter().append("circle")
  .attr("r", 25)
  .attr("cx", 50)
  .attr("cy", y);

var radius = 25;
var count = 0; // <--- the number of queued calls of animate
function animate() {
    let i = size; // <-- the number of items that will be animating
    console.log("anim");
    radius = (radius+5)%50;
    g.selectAll("circle")
    .transition()
    .duration(500 / count) // <--- reduce the duration when count is high
    .attr("r", radius)
    .on("end", () => {
        i--;
        // when all objects stopped animating and more animate() calls needed:
        if (i === 0 && --count > 0) animate(); // on to the next...
    });
}
document.addEventListener('keydown', e =>
    // only call animate when no animation is currently ongoing, else increment
    e.code === "KeyA" ? count++ || animate() : 0
);

如果使用最新的 D3 版本,即 v5(顺便说一句,您的代码可以按原样使用 v5,无需更改),您可以使用已接受的答案中的方法,但使用transition.end()而不是transition.on("end",...)

transition.on("end",...)transition.end() (强调我的):

返回在每个选定元素完成转换时解析的承诺。 如果任何元素的转换被取消或中断,promise 就会拒绝。

这样,您可以删除animate()中的i变量,从而节省几行。 它成为了:

function animate() {
  radius = (radius + 5) % 50;
  g.selectAll("circle")
    .transition()
    .duration(500 / count)
    .attr("r", radius)
    .end()
    .then(() => {
      if (--count > 0) animate()
    });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM