[英]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.