Механизм переходов в D3.js построен вокруг управляемого изменения
атрибутов и стилей элементов во времени. Каждый переход представляет
собой отдельный объект состояния, который отслеживает прогресс анимации,
интерполяцию значений и набор событий жизненного цикла. Одним из
ключевых событий этого цикла является событие завершения перехода
end, фиксирующее момент, когда анимация конкретного
элемента достигла финального состояния.
Переход в D3.js проходит несколько стадий:
endСобытие end связано не с группой элементов целиком, а с
каждым отдельным DOM-элементом, участвующим в переходе. Это означает,
что завершение фиксируется индивидуально, даже если переход применяется
к набору элементов через выборку.
Обработчик события завершения перехода привязывается через метод
.on("end", callback):
d3.selectAll("circle")
.transition()
.duration(1000)
.attr("r", 20)
.on("end", function(event, d) {
console.log("Переход завершён для элемента:", this);
});
В момент вызова:
this указывает на DOM-элементevent содержит объект события D3d — связанные данные элемента (если использовался data
join)Ключевая особенность заключается в том, что callback вызывается после завершения конкретного перехода у конкретного элемента, а не после завершения всей выборки.
При применении перехода к набору элементов каждый элемент может
завершить анимацию в разные моменты времени (особенно при использовании
задержек delay или различной длительности через
функции).
d3.selectAll("rect")
.transition()
.delay((d, i) => i * 100)
.duration(500)
.attr("height", 100)
.on("end", function() {
console.log("Готово:", this);
});
Здесь событие end срабатывает последовательно, по мере
завершения анимации каждого прямоугольника.
Событие завершения перехода не срабатывает при принудительном
прерывании анимации. Если переход был остановлен новым переходом или
методом interrupt, вместо end может быть
вызвано событие interrupt.
d3.select("circle")
.transition()
.duration(2000)
.attr("r", 50)
.on("interrupt", function() {
console.log("Переход прерван");
});
Это различие критично при построении логики, зависящей от гарантированного завершения анимации.
При построении цепочек переходов событие end относится
только к текущему переходу в цепи. Новый переход создаёт новый жизненный
цикл.
d3.select("circle")
.transition()
.duration(500)
.attr("r", 30)
.on("end", function() {
d3.select(this)
.transition()
.duration(500)
.attr("fill", "red");
});
В этом случае первый end запускает второй переход, но не
означает завершение всей цепочки визуальных изменений.
Событие end часто используется для синхронизации логики,
когда необходимо определить момент завершения всей группы переходов.
Поскольку событие вызывается для каждого элемента отдельно, требуется
механизм подсчёта завершённых элементов.
let total = 0;
let completed = 0;
const circles = d3.selectAll("circle");
total = circles.size();
circles
.transition()
.duration(800)
.attr("cx", 300)
.on("end", function() {
completed += 1;
if (completed === total) {
console.log("Все элементы завершили переход");
}
});
Такой подход компенсирует отсутствие встроенного события «end для всей группы».
Если на элемент назначается новый переход до завершения предыдущего,
предыдущий переход прерывается, и его end не вызывается.
Это создаёт важный эффект:
end должна учитывать возможную отменуconst sel = d3.select("rect");
sel.transition()
.duration(1000)
.attr("width", 200);
sel.transition()
.duration(500)
.attr("width", 50);
В данном случае второй переход перезаписывает первый, и событие
end первого не будет вызвано.
Объект события, передаваемый в обработчик, содержит информацию о текущем переходе и позволяет различать источники вызова при сложной анимационной логике:
d3.selectAll("circle")
.transition()
.attr("r", 10)
.on("end", function(event, d) {
console.log(event.type);
console.log(d);
});
Хотя объект события в D3 менее насыщен, чем в стандартных DOM-событиях, он сохраняет минимально необходимый контекст для отладки и управления потоком анимации.
При вложенных переходах каждый уровень имеет собственный
end, и они не пересекаются:
d3.select("circle")
.transition()
.attr("r", 20)
.on("end", function() {
d3.select(this)
.transition()
.attr("r", 40)
.on("end", function() {
console.log("второй переход завершён");
});
});
Здесь фиксируются два независимых события завершения, каждое из которых относится к своему переходу.
Событие end вызывается после завершения интерполяции
всех атрибутов и стилей, участвующих в переходе. Однако фактический
момент вызова зависит от таймера D3 и может не совпадать с системным
временем с абсолютной точностью.
При большом количестве элементов или сложных интерполяциях возможна небольшая задержка между фактическим окончанием анимации и вызовом обработчика.
Если переход имеет нулевую или неопределённую длительность, событие
end вызывается практически мгновенно после применения
начальных изменений:
d3.select("circle")
.transition()
.duration(0)
.attr("r", 100)
.on("end", function() {
console.log("мгновенное завершение");
});
В этом случае переход теряет анимационную фазу, но сохраняет семантику завершения.
Событие завершения перехода часто используется как точка фиксации состояния DOM после анимации. Это позволяет:
При этом логика должна учитывать, что end относится к
отдельному элементу и отдельному переходу, а не к всей сцене в
целом.