Викликати зворотний виклик в кінці переходу


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);
  • Ця демонстрація використовує подію "кінець" для ланцюга багатьох переходів по порядку.
  • Приклад пончика, що постачається з D3, також використовує це для з'єднання кількох переходів.
  • Ось моя власна демонстрація, яка змінює стиль елементів на початку та в кінці переходу.

З документації для transition.each([type],listener):

Якщо тип вказаний, додається слухач для подій переходу, підтримуючи події "старту" та "кінця". Слухач буде викликати кожен окремий елемент переходу, навіть якщо перехід має постійну затримку та тривалість. Подія запуску може використовуватися для запуску миттєвої зміни, коли кожен елемент починає переходити. Кінцева подія може бути використана для ініціювання багатоступеневих переходів шляхом вибору поточного елемента thisта отримання нового переходу. Будь-які переходи, створені під час кінцевої події, успадкують поточний ідентифікатор переходу і, таким чином, не замінять новий перехід, який був запланований раніше.

Детальніше про це див. У цій темі на форумі .

Нарешті, зауважте, що якщо ви просто хочете видалити елементи після їх вицвітання (після завершення переходу), ви можете використовувати transition.remove().


7
Велике спасибі. Це ВЕЛИКА ВЕЛИКА бібліотека, але важливу інформацію знайти в документації не так просто.
Тоні

9
Отже, моя проблема з таким способом продовження з кінця переходу полягає в тому, що він виконує вашу функцію N разів (для N елементів у наборі елементів, що переходять). Іноді це далеко не ідеально.
Стівен Лу

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

1
Як ви виконуєте зворотний виклик лише після того, як усі переходи закінчені для d3.selectAll()(замість того, як кожен елемент закінчується)? Іншими словами, я просто хочу повернути одну функцію, як тільки всі елементи закінчать перехід.
hobbes3

Привіт, перше посилання на стек / групову діаграму вказує на зошит, що спостерігається, який не використовує ані .eachслухача події, ані "end"події. Схоже, це не "ланцюгові" переходи. Друга посилання вказує на github, який не завантажується для мене.
Червоний горох

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
if (! зворотний дзвінок) callback = функція () {}; чому б не повернутись миттєво чи не кинути виняток? Недійсний зворотний виклик перемагає цілі цієї програми, навіщо проходити з нею як сліпий годинник? :)
призма

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

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()( посилання ), яке обробляє загальноприйнятий випадок переходу елемента з виду: `" Для кожного обраного елемента видаляється елемент, коли перехід закінчується, доки у елемента немає інших активних або очікуваних переходів. Якщо елемент має інші активні або очікувані переходи, нічого не робить ".
бричін

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(і лише цих аргументів). Важливо, що це за замовчуванням не застосовуватиметься callbackif if transitionпорожній, що, мабуть, є більш безпечним припущенням у такому випадку.


Це приємно, мені це подобається.
kashesandr

1
Дякую @kashesandr Це справді надихнуло вашу відповідь для початку!
мило

насправді не думайте, що нам потрібна функція краплі або передача аргументів, оскільки такий же ефект може бути досягнутий функцією обгортки або використанням прив’язки. Інакше я думаю, що це чудове рішення +1
Ахмед Масуд

Працює як шарм!
Бенуо Сов'є

Дивіться цей відповідь, .END () тепер офіційно доданий - stackoverflow.com/a/57796240/228369
chrismarx

5

Станом на D3 v5.8.0 +, зараз існує офіційний спосіб зробити це за допомогою transition.end. Документи тут:

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

Робочий приклад з Бостока тут:

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.