Вызов обратного вызова в конце перехода
Мне нужно сделать метод FadeOut (похожий на jQuery) с помощью D3.js. Мне нужно сделать, чтобы непрозрачность была 0 с помощью transition()
.
d3.select("#myid").transition().style("opacity", "0");
Проблема в том, что мне нужен обратный вызов для реализации, когда переход завершен. Как реализовать обратный вызов?
Ответы
Ответ 1
Вы хотите прослушать "конечное" событие перехода.
// d3 v5
d3.select("#myid").transition().style("opacity","0").on("end", myCallback);
// old way
d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
Из документации для transition.each([type],listener)
:
Если указан тип, добавляется прослушиватель для событий перехода, поддерживающий события "start" и "end". Слушатель будет вызываться для каждого отдельного элемента в переходе, даже если переход имеет постоянную задержку и длительность. Событие start можно использовать для мгновенного изменения, когда каждый элемент начинает переход. Конечное событие можно использовать для инициирования многоступенчатых переходов, выбрав текущий элемент this
и получив новый переход. Любые переходы, созданные во время конечного события, будут наследовать текущий идентификатор перехода и, следовательно, не будут переопределять более новый переход, который был ранее запланирован.
Подробнее читайте в этой ветке форума по теме.
Наконец, обратите внимание, что если вы просто хотите удалить элементы после их исчезновения (после завершения перехода), вы можете использовать transition.remove()
.
Ответ 2
Mike Bostock решение для v3 с небольшим обновлением:
function endall(transition, callback) {
if (typeof callback !== "function") throw new Error("Wrong callback in endall");
if (transition.size() === 0) { callback() }
var n = 0;
transition
.each(function() { ++n; })
.each("end", function() { if (!--n) callback.apply(this, arguments); });
}
d3.selectAll("g").transition().call(endall, function() { console.log("all done") });
Ответ 3
Теперь, в d3 v4.0, есть средство для явного прикрепления обработчиков событий к переходам:
https://github.com/d3/d3-transition#transition_on
Для выполнения кода при завершении перехода все, что вам нужно, это:
d3.select("#myid").transition().style("opacity", "0").on("end", myCallback);
Ответ 4
Немного другой подход, который работает также, когда есть много переходов со многими элементами, каждый из которых выполняется одновременно:
var transitions = 0;
d3.select("#myid").transition().style("opacity","0").each( "start", function() {
transitions++;
}).each( "end", function() {
if( --transitions === 0 ) {
callbackWhenAllIsDone();
}
});
Ответ 5
Ниже приведена другая версия решения Mike Bostock и вдохновлена комментарием @hughes на ответ @kashesandr. Он делает один обратный вызов на конце transition
.
Для функции drop
...
function drop(n, args, callback) {
for (var i = 0; i < args.length - n; ++i) args[i] = args[i + n];
args.length = args.length - n;
callback.apply(this, args);
}
... мы можем расширить d3
так:
d3.transition.prototype.end = function(callback, delayIfEmpty) {
var f = callback,
delay = delayIfEmpty,
transition = this;
drop(2, arguments, function() {
var args = arguments;
if (!transition.size() && (delay || delay === 0)) { // if empty
d3.timer(function() {
f.apply(transition, args);
return true;
}, typeof(delay) === "number" ? delay : 0);
} else { // else Mike Bostock routine
var n = 0;
transition.each(function() { ++n; })
.each("end", function() {
if (!--n) f.apply(transition, args);
});
}
});
return transition;
}
Как JSFiddle.
Используйте transition.end(callback[, delayIfEmpty[, arguments...]])
:
transition.end(function() {
console.log("all done");
});
... или с дополнительной задержкой, если transition
пуст:
transition.end(function() {
console.log("all done");
}, 1000);
... или с необязательными аргументами callback
:
transition.end(function(x) {
console.log("all done " + x);
}, 1000, "with callback arguments");
d3.transition.end
будет применять переданный callback
даже с пустым transition
, если указано число миллисекунд , или если, второй аргумент является правдивым. Это также перенаправляет любые дополнительные аргументы в callback
(и только те аргументы). Важно отметить, что не по умолчанию применяет callback
, если transition
пуст, что, вероятно, является более безопасным предположением в этом случае.
Ответ 6
Начиная с D3 v5.8. 0+, теперь есть официальный способ сделать это с помощью transition.end
. Документы здесь:
https://github.com/d3/d3-transition#transition_end
Рабочий пример от Bostock находится здесь:
https://observablehq.com/@d3/transition-end
И основная идея заключается в том, что, просто добавив .end()
, переход вернет обещание, которое не будет выполнено, пока все элементы не завершат переход:
await d3.selectAll("circle").transition()
.duration(1000)
.ease(d3.easeBounce)
.attr("fill", "yellow")
.attr("cx", r)
.end();
Смотрите примечания к выпуску версии:
https://github.com/d3/d3/releases/tag/v5.8.0
Ответ 7
Майк Босток решение улучшено kashesandr + передача аргументов функции обратного вызова:
function d3_transition_endall(transition, callback, arguments) {
if (!callback) callback = function(){};
if (transition.size() === 0) {
callback(arguments);
}
var n = 0;
transition
.each(function() {
++n;
})
.each("end", function() {
if (!--n) callback.apply(this, arguments);
});
}
function callback_function(arguments) {
console.log("all done");
console.log(arguments);
}
d3.selectAll("g").transition()
.call(d3_transition_endall, callback_function, "some arguments");
Ответ 8
На самом деле есть еще один способ сделать это с помощью таймеров.
var timer = null,
timerFunc = function () {
doSomethingAfterTransitionEnds();
};
transition
.each("end", function() {
clearTimeout(timer);
timer = setTimeout(timerFunc, 100);
});
Ответ 9
Я решил аналогичную проблему, установив длительность переходов с использованием переменной. Затем я использовал setTimeout()
для вызова следующей функции. В моем случае я хотел немного перекрыться между переходом и следующим вызовом, как вы увидите в моем примере:
var transitionDuration = 400;
selectedItems.transition().duration(transitionDuration).style("opacity", .5);
setTimeout(function () {
sortControl.forceSort();
}, (transitionDuration * 0.75));