简体   繁体   English

如何链接 d3 转换?

[英]How to chain d3 transitions?

I have a set of animations that happen when a user moves a slider bar.我有一组当用户移动滑块时发生的动画。 Each increment of the slider bar creates a transition.滑块条的每个增量都会创建一个过渡。 However, whenever the user moves the slider bar very quickly (ie they increment the slider faster than the transition can complete), there is a race condition on the transitions, old ones get interrupted, and the "flow" of the animation is weird.然而,每当用户非常快速地移动滑块时(即他们增加滑块的速度比过渡可以完成的更快),过渡上就会出现竞争条件,旧的会被中断,并且动画的“流程”很奇怪。 I would like to have a sequence of transitions and have them always occur in the sequence that they were called .我希望有一系列转换,并让它们始终按照它们被调用的顺序发生。 Ie The next one doesn't start until the last one is finished.即下一个不会开始,直到最后一个完成。 jsfiddle (hold down the "a" key vs press it several times slowly to see the difference) 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);

You can use .on("end", callback) to listen to the end of an animation.您可以使用.on("end", callback)来收听动画的结束。 As you have 50 objects being animated, and will get that event for each one of them, you need a counter to know when the last one of those has finished.由于您有 50 个正在动画的对象,并且每个对象都将获得该事件,因此您需要一个计数器来了解其中最后一个何时完成。

To make sure all keypresses result in the animation, but only after the previous one has finished, you cannot just call animate() on the key event.为确保所有按键都产生动画,但只有在前一个按键完成后,您不能仅在按键事件上调用animate() Instead keep track of how many of those calls have to be performed, and increment that when a key event is fired.而是跟踪必须执行多少个调用,并在触发关键事件时增加该调用。 Only call animate() when that counter was zero.仅当该计数器为零时才调用animate()

Note that this queuing of animate() calls may also give an unnatural behaviour: the animation may keep on going until long after the last key event.请注意, animate()调用的这种排队也可能会产生不自然的行为:动画可能会一直持续到最后一个关键事件之后很久。 To improve the user experience, you could lower the duration parameter, so the animation speeds up when still lots of key events need their corresponding call of animate() processed.为了改善用户体验,您可以降低持续时间参数,以便在仍有大量关键事件需要处理其相应的animate()调用时动画加速。

Here is how your code could be adapted to do all that:以下是如何调整您的代码以完成所有这些工作:

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
);

If using the newest D3 version, which is v5 (by the way, your code works with v5 the way it is, no change needed) you can use the approach in the accepted answer , but with transition.end() instead of transition.on("end",...) .如果使用最新的 D3 版本,即 v5(顺便说一句,您的代码可以按原样使用 v5,无需更改),您可以使用已接受的答案中的方法,但使用transition.end()而不是transition.on("end",...)

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

Returns a promise that resolves when every selected element finishes transitioning.返回在每个选定元素完成转换时解析的承诺。 If any element's transition is cancelled or interrupted, the promise rejects.如果任何元素的转换被取消或中断,promise 就会拒绝。

That way, you can remove the i variable inside animate() , saving a couple of lines.这样,您可以删除animate()中的i变量,从而节省几行。 It becomes:它成为了:

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