Вызов обратного вызова в конце перехода

Мне нужно сделать метод 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));