Wywołaj wywołanie zwrotne na końcu przejścia

98

Muszę zrobić Fadeout metodę podobną do jQuery () używając D3.js . To, co muszę zrobić, to ustawić krycie na 0 za pomocą transition().

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

Problem polega na tym, że potrzebuję oddzwonienia, aby dowiedzieć się, kiedy przejście się zakończy. Jak mogę zaimplementować wywołanie zwrotne?

Tony
źródło

Odpowiedzi:

144

Chcesz nasłuchiwać zdarzenia „końca” przejścia.

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

// old way
d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
  • To demo wykorzystuje zdarzenie „end” do połączenia wielu przejść w określonej kolejności.
  • Przykład pączek , że statki z D3 również używa tego do łańcucha razem wiele przejść.
  • Oto moje własne demo , który zmienia styl elementów na początku i na końcu przejścia.

Z dokumentacji transition.each([type],listener):

Jeśli określono typ , dodaje detektor zdarzeń przejścia, obsługujący zarówno zdarzenia „start”, jak i „end”. Odbiornik zostanie wywołany dla każdego elementu przejścia, nawet jeśli przejście ma stałe opóźnienie i czas trwania. Zdarzenie początkowe może służyć do wywołania natychmiastowej zmiany, gdy każdy element zaczyna się zmieniać. Zdarzenie końcowe można wykorzystać do zainicjowania przejść wieloetapowych przez wybranie bieżącego elementu thisi wyprowadzenie nowego przejścia. Wszelkie przejścia utworzone podczas zdarzenia końcowego odziedziczą bieżący identyfikator przejścia, a zatem nie zastąpią nowszego przejścia, które było wcześniej zaplanowane.

Więcej informacji znajdziesz w tym wątku na forum na ten temat .

Na koniec zwróć uwagę, że jeśli chcesz po prostu usunąć elementy po ich wyblaknięciu (po zakończeniu przejścia), możesz użyć transition.remove().

Phrogz
źródło
7
Dziękuję Ci bardzo. To jest WIELKA WIELKA biblioteka, ale nie jest łatwo znaleźć ważne informacje w dokumentacji.
Tony
9
Tak więc mój problem z tym sposobem kontynuowania od końca przejścia polega na tym, że uruchamia twoją funkcję N razy (dla N elementów w zbiorze elementów przejściowych). Czasami jest to dalekie od ideału.
Steven Lu
2
Mam ten sam problem. Chciałbym, żeby raz uruchomił funkcję po ostatnim usunięciu
canyon289
1
Jak wykonać wywołanie zwrotne dopiero po zakończeniu wszystkich przejść na a d3.selectAll()(zamiast po zakończeniu każdego elementu)? Innymi słowy, chcę po prostu wywołać jedną funkcję, gdy wszystkie elementy zakończą przejście.
hobbes3
Cześć, pierwszy link do wykresu słupkowego stosu / grupy wskazuje na Notatnik Observable, który nie używa żadnego .eachdetektora zdarzeń ani "end"zdarzenia. Wydaje się, że nie łączy to przejść w łańcuch. Drugi link wskazuje na github, który nie ładuje się dla mnie.
The Red Pea
65

Mike Bostock za rozwiązaniem dla v3 z małą aktualizację:

  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") });
kashesandr
źródło
5
Jeśli zaznaczenie zawiera zero elementów, wywołanie zwrotne nigdy nie zostanie uruchomione. Jednym ze sposobów rozwiązania tego if (transition.size() === 0) { callback(); }
problemu
1
if (! callback) callback = function () {}; dlaczego nie wrócić natychmiast lub nie zgłosić wyjątku? Nieprawidłowe wywołanie zwrotne niweczy cały cel tej rutyny, po co robić to jak ślepy zegarmistrz? :)
prizma
1
@kashesandr można po prostu nic nie zrobić, ponieważ użytkownik doświadczy tego samego efektu: (brak wywołania zwrotnego na końcu przejścia) function endall(transition, callback){ if(!callback) return; // ... } lub, ponieważ wywołanie tej funkcji bez wywołania zwrotnego jest z całą pewnością błędem, rzucenie wyjątku być odpowiednim sposobem radzenia sobie z sytuacją Myślę, że ta sprawa nie wymaga zbyt skomplikowanej Wyjątek function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
prizma
1
Więc kiedy mamy separację enter()i exit()przejścia i chcemy poczekać, aż wszystkie trzy zakończą się, musimy umieścić kod w wywołaniu zwrotnym, aby upewnić się, że został wywołany trzy razy, prawda? D3 jest taki niechlujny! Żałuję, że nie wybrałem innej biblioteki.
Michael Scheper
1
Powinienem dodać, zdaję sobie sprawę, że twoja odpowiedź rozwiązuje niektóre z problemów, o których się skarżyłem, i mogę napisać funkcję narzędzia, aby ją zastosować. Ale nie znalazłem eleganckiego sposobu na jego zastosowanie i nadal pozwalam na dodatkowe dostosowanie dla każdego przejścia, zwłaszcza gdy przejścia dla nowych i starych danych są różne. Jestem pewien, że coś wymyślę, ale „wywołaj to wywołanie zwrotne po zakończeniu wszystkich tych przejść” wydaje się być przypadkiem użycia, który powinien być obsługiwany po wyjęciu z pudełka, w bibliotece tak dojrzałej jak D3. Wygląda więc na to, że wybrałem złą bibliotekę - nie jest to wina D3. W każdym razie, dzięki za pomoc.
Michael Scheper
44

Teraz w d3 v4.0 istnieje możliwość jawnego dołączania programów obsługi zdarzeń do przejść:

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

Aby wykonać kod po zakończeniu przejścia, potrzebujesz tylko:

d3.select("#myid").transition().style("opacity", "0").on("end", myCallback);
ericsoco
źródło
Piękny. Programy obsługi zdarzeń są obrzydliwe.
KFunk,
Istnieje również transition.remove()( link ), który obsługuje typowy przypadek użycia przejścia elementu z widoku: `` Dla każdego wybranego elementu, usuwa element po zakończeniu przejścia, o ile element nie ma innych aktywnych lub oczekujących przejść. element ma inne aktywne lub oczekujące przejścia, nic nie robi. "
brichins
9
Wygląda na to, że nazywa się to elementem PER, do którego zastosowano przejście, co nie jest tym, o co chodzi w moim rozumieniu.
Taylor C. White,
10

Nieco inne podejście, które działa również w przypadku wielu przejść, z których każdy działa jednocześnie:

var transitions = 0;

d3.select("#myid").transition().style("opacity","0").each( "start", function() {
        transitions++;
    }).each( "end", function() {
        if( --transitions === 0 ) {
            callbackWhenAllIsDone();
        }
    });
Jesper We
źródło
Dzięki, dobrze mi się udało. Próbowałem automatycznie dostosować orientację etykiety osi X po załadowaniu dyskretnego wykresu słupkowego. Dostosowanie nie może zacząć obowiązywać przed załadowaniem, a to zapewniło punkt zaczepienia zdarzenia, za pomocą którego mogłem to zrobić.
whitestryder
6

Poniżej znajduje się kolejna wersja Mike'a Bostocka roztworze i zainspirowany komentarzem @hughes' do użytkownika @ kashesandr odpowiedź. Wykonuje pojedyncze wywołanie zwrotne po transitionzakończeniu.

Dawać drop funkcję ...

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);
}

... możemy przedłużyć d3 sposób:

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;
}

Jako JSFiddle .

Posługiwać się transition.end(callback[, delayIfEmpty[, arguments...]]) :

transition.end(function() {
    console.log("all done");
});

... lub z opcjonalnym opóźnieniem, jeśli transition jest pusty:

transition.end(function() {
    console.log("all done");
}, 1000);

... lub z opcjonalnymi callbackargumentami:

transition.end(function(x) {
    console.log("all done " + x);
}, 1000, "with callback arguments");

d3.transition.endzastosuje przekazaną wartość callbacknawet z pustą wartością, transition jeśli podano liczbę milisekund lub jeśli drugi argument jest prawdziwy. Spowoduje to również przekazanie wszelkich dodatkowych argumentów do callback(i tylko tych argumentów). Co ważne, domyślnie nie będzie to miało zastosowania, callbackjeśli if transitionjest puste, co jest prawdopodobnie bezpieczniejszym założeniem w takim przypadku.

milos
źródło
To miło, podoba mi się.
kashesandr
1
Dzięki @kashesandr. Rzeczywiście było to zainspirowane Twoją odpowiedzią!
milos
nie sądzę, że potrzebujemy funkcji drop lub przekazywania argumentów, ponieważ ten sam efekt można osiągnąć za pomocą funkcji opakowującej lub przez użycie bind. W przeciwnym razie myślę, że to świetne rozwiązanie +1
Ahmed Masud
Działa jak marzenie !
Benoît Sauvère
Zobacz tę odpowiedź, .end () został oficjalnie dodany - stackoverflow.com/a/57796240/228369
chrismarx
5

Od wersji D3 w wersji 5.8.0 + jest teraz oficjalny sposób na zrobienie tego za pomocą transition.end . Dokumenty są tutaj:

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

Przykład roboczy firmy Bostock jest tutaj:

https://observablehq.com/@d3/transition-end

Podstawową ideą jest to, że samo dołączenie .end()przejścia zwróci obietnicę, która nie zostanie rozwiązana, dopóki wszystkie elementy nie zostaną zakończone:

 await d3.selectAll("circle").transition()
      .duration(1000)
      .ease(d3.easeBounce)
      .attr("fill", "yellow")
      .attr("cx", r)
    .end();

Zobacz informacje o wydaniu wersji, aby dowiedzieć się więcej:

https://github.com/d3/d3/releases/tag/v5.8.0

chrismarx
źródło
1
To bardzo fajny sposób na załatwienie spraw. Powiem tylko, że dla tych z Was, takich jak ja, którzy nie znają całej wersji 5 i chcieliby tylko to zaimplementować, mogą zaimportować nową bibliotekę przejść za pomocą <script src = " d3js.org/d3-transition.v1 .min.js "> </ script >
DGill
0

Rozwiązanie Mike'a Bostocka ulepszone przez kashesandr + przekazanie argumentów do funkcji zwrotnej:

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");
int_ua
źródło
-2

Właściwie jest jeszcze jeden sposób na zrobienie tego za pomocą timerów.

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

transition
  .each("end", function() {
    clearTimeout(timer);
    timer = setTimeout(timerFunc, 100);
  });
ifadey
źródło
-2

Rozwiązałem podobny problem, ustawiając czas trwania przejść za pomocą zmiennej. Następnie setTimeout()wywoływałem następną funkcję. W moim przypadku chciałem, aby przejście między przejściem a następnym połączeniem nieznacznie się pokrywało, jak zobaczysz w moim przykładzie:

var transitionDuration = 400;

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

setTimeout(function () {
  sortControl.forceSort();
}, (transitionDuration * 0.75)); 
Brett
źródło