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


98

Мне нужно сделать метод FADEOUT ( по аналогии с JQuery) с помощью D3.js . Что мне нужно сделать, так это установить непрозрачность на 0, используя transition().

d3.select("#myid").transition().style("opacity", "0");

Проблема в том, что мне нужен обратный вызов, чтобы понять, когда переход завершен. Как я могу реализовать обратный вызов?

Ответы:


144

Вы хотите прослушать «конечное» событие перехода.

// 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 может использоваться для запуска мгновенного изменения при переходе каждого элемента. Конечное событие может использоваться для инициирования многоступенчатых переходов путем выбора текущего элемента this, и создания нового перехода. Любые переходы, созданные во время конечного события, унаследуют текущий идентификатор перехода и, таким образом, не будут отменять более новый переход, который был ранее запланирован.

Смотрите эту ветку форума по теме для получения более подробной информации.

Наконец, обратите внимание, что если вы просто хотите удалить элементы после того, как они исчезли (после завершения перехода), вы можете использовать transition.remove().


7
Большое спасибо. Это ОТЛИЧНАЯ библиотека, но найти важную информацию в документации не так-то просто.
Тони

9
Итак, моя проблема с этим способом продолжения с конца перехода заключается в том, что он запускает вашу функцию N раз (для N элементов в наборе переходных элементов). Иногда это далеко не идеально.
Стивен Лу

2
У меня такая же проблема. Хотелось бы, чтобы функция запускалась один раз после последнего удаления
canyon289

1
Как выполнить обратный вызов только после завершения всех переходов для a d3.selectAll()(а не после завершения каждого элемента)? Другими словами, я просто хочу вызвать одну функцию после того, как все элементы закончат переход.
hobbes3

Привет, первая ссылка на столбчатую диаграмму стека / группы указывает на записную книжку Observable, которая не использует ни .eachпрослушиватель событий, ни "end"событие. Это не похоже на "цепочку" переходов. Вторая ссылка указывает на github, который у меня не загружается.
The Red Pea

65

Решение Майка Бостока для 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") });

5
Если выделение содержит ноль элементов, обратный вызов никогда не сработает. Один из способов исправить этоif (transition.size() === 0) { callback(); }
Хьюз,

1
если (! обратный вызов) callback = function () {}; почему бы не вернуться мгновенно или не выбросить исключение? Недопустимый обратный вызов сводит на нет всю цель этой рутины, зачем выполнять ее, как слепой часовщик? :)
prizma

1
@kashesandr можно просто ничего не делать, так как пользователь испытает тот же эффект: (без обратного вызова в конце перехода) function endall(transition, callback){ if(!callback) return; // ... } или, поскольку вызов этой функции без обратного вызова, несомненно, является ошибкой, бросает швы исключения в быть подходящим способом справиться с ситуацией, я думаю, в этом случае не нужно слишком сложное исключение function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
prizma

1
Поэтому , когда у нас есть отдельные enter()и exit()переходы, и хотят ждать , пока все три не закончили, мы должны поставить код в функции обратного вызова , чтобы убедиться , что он был вызван в три раза, не так ли? D3 такой беспорядочный! Хотел бы я выбрать другую библиотеку.
Майкл Шепер

1
Я должен добавить, я понимаю, что ваш ответ решает некоторые из проблем, о которых я думал, и я могу написать служебную функцию для его применения. Но я не нашел элегантного способа его применения и по-прежнему позволяю дополнительную настройку для каждого перехода, особенно когда переходы для новых и старых данных различаются. Я уверен, что что-нибудь придумаю, но «вызвать этот обратный вызов после завершения всех этих переходов» кажется вариантом использования, который должен поддерживаться из коробки в такой зрелой библиотеке, как D3. Кажется, я выбрал не ту библиотеку - на самом деле это не вина D3. Anyhoo, спасибо за вашу помощь.
Майкл Шепер

44

Теперь в d3 v4.0 есть возможность явно прикреплять обработчики событий к переходам:

https://github.com/d3/d3-transition#transition_on

Все, что вам нужно, чтобы выполнить код после завершения перехода:

d3.select("#myid").transition().style("opacity", "0").on("end", myCallback);

Прекрасный. Обработчики событий грубые.
KFunk

Также существует transition.remove()( ссылка ), которая обрабатывает общий вариант использования перехода элемента из представления: «» Для каждого выбранного элемента удаляет элемент по окончании перехода, если у элемента нет других активных или ожидающих переходов. Если элемент имеет другие активные или ожидающие переходы, ничего не делает. "
brichins 04

9
Похоже, что это называется элементом PER, к которому применяется переход, что, по моему мнению, не в том, о чем идет речь.
Тейлор К. Уайт

10

Немного другой подход, который работает также, когда есть много переходов с множеством элементов, каждый из которых выполняется одновременно:

var transitions = 0;

d3.select("#myid").transition().style("opacity","0").each( "start", function() {
        transitions++;
    }).each( "end", function() {
        if( --transitions === 0 ) {
            callbackWhenAllIsDone();
        }
    });

Спасибо, у меня это сработало. Я пытался настроить ориентацию метки оси x автоматически после загрузки дискретной гистограммы. Настройка не может вступить в силу до загрузки, и это обеспечило перехват события, с помощью которого я мог бы это сделать.
Whitestryder

6

Ниже еще одна версия Майка Босток в растворе и вдохновленный комментарий @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's 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пусто, что, вероятно, является более безопасным предположением в таком случае.


Приятно, мне это нравится.
kashesandr

1
Спасибо @kashesandr. Это действительно было вдохновлено вашим ответом с самого начала!
milos

На самом деле не думаю, что нам нужна функция отбрасывания или передача аргументов, поскольку того же эффекта можно достичь с помощью функции-оболочки или с помощью связывания. В противном случае я думаю, что это отличное решение +1
Ахмед Масуд

Работает как шарм!
Бенуа Совер 01

См. Этот ответ, .end () теперь официально добавлен - stackoverflow.com/a/57796240/228369
chrismarx

5

Начиная с 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


1
Это очень хороший способ справляться с проблемами. Я просто скажу, что для тех из вас, как я, кто не знает v5 всего и хотел бы реализовать именно это, вы можете импортировать новую библиотеку переходов, используя <script src = " d3js.org/d3-transition.v1 .min.js "> </ script >
DGill

0

Решение Майка Бостока улучшено за счет 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");

-2

На самом деле есть еще один способ сделать это с помощью таймеров.

var timer = null,
    timerFunc = function () {
      doSomethingAfterTransitionEnds();
    };

transition
  .each("end", function() {
    clearTimeout(timer);
    timer = setTimeout(timerFunc, 100);
  });

-2

Я решил аналогичную проблему, установив длительность переходов с помощью переменной. Затем я setTimeout()вызывал следующую функцию. В моем случае я хотел небольшое перекрытие между переходом и следующим вызовом, как вы увидите в моем примере:

var transitionDuration = 400;

selectedItems.transition().duration(transitionDuration).style("opacity", .5);

setTimeout(function () {
  sortControl.forceSort();
}, (transitionDuration * 0.75)); 
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.