Richiama una richiamata alla fine di una transizione


98

Ho bisogno di fare un metodo FadeOut (simile a jQuery) utilizzando d3.js . Quello che devo fare è impostare l'opacità su 0 usando transition().

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

Il problema è che ho bisogno di una richiamata per rendermi conto quando la transizione è terminata. Come posso implementare una richiamata?

Risposte:


144

Vuoi ascoltare l'evento di "fine" della transizione.

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

// old way
d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
  • Questa demo utilizza l'evento "end" per concatenare molte transizioni in ordine.
  • L' esempio di ciambella fornito con D3 lo utilizza anche per concatenare più transizioni.
  • Ecco la mia demo che cambia lo stile degli elementi all'inizio e alla fine della transizione.

Dalla documentazione per transition.each([type],listener):

Se viene specificato il tipo , aggiunge un listener per gli eventi di transizione, supportando sia gli eventi di "inizio" che di "fine". L'ascoltatore verrà richiamato per ogni singolo elemento della transizione, anche se la transizione ha un ritardo e una durata costanti. L'evento di inizio può essere utilizzato per attivare una modifica istantanea quando ogni elemento inizia la transizione. L'evento di fine può essere utilizzato per avviare transizioni a più fasi selezionando l'elemento corrente this, e derivando una nuova transizione. Qualsiasi transizione creata durante l'evento finale erediterà l'ID transizione corrente e quindi non sovrascriverà una transizione più recente precedentemente pianificata.

Vedi questo thread del forum sull'argomento per maggiori dettagli.

Infine, nota che se vuoi rimuovere gli elementi solo dopo che sono svaniti (dopo che la transizione è terminata), puoi usare transition.remove().


7
Grazie mille. Questa è una GRANDE GRANDE libreria, ma non è così facile trovare le informazioni importanti nella documentazione.
Tony

9
Quindi, il mio problema con questo modo di continuare dalla fine della transizione è che esegue la tua funzione N volte (per N elementi nell'insieme di elementi di transizione). Questo a volte è tutt'altro che ideale.
Steven Lu

2
Ho lo stesso problema. Vorrei che
eseguisse

1
Come si esegue una richiamata solo dopo che tutte le transizioni sono terminate per a d3.selectAll()(invece dopo che ogni elemento è finito)? In altre parole, voglio solo richiamare una funzione una volta che tutti gli elementi hanno terminato la transizione.
hobbes3

Salve, il primo collegamento per impilare / raggruppare il grafico a barre punta a un taccuino Observable che non utilizza alcun .eachlistener di eventi, né l' "end"evento. Non sembra "concatenare" le transizioni. Il secondo collegamento punta a un GitHub che non viene caricato per me.
The Red Pea

65

La soluzione di Mike Bostock per v3 con un piccolo aggiornamento:

  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
Se la selezione contiene zero elementi, il callback non verrà mai attivato. Un modo per risolvere questo problema èif (transition.size() === 0) { callback(); }
hughes

1
if (! callback) callback = function () {}; perché non tornare immediatamente o lanciare un'eccezione? Una richiamata non valida vanifica l'intero scopo di questa routine, perché andare fino in fondo come un orologiaio cieco? :)
prizma

1
@kashesandr si può semplicemente non fare nulla, poiché l'utente sperimenterà lo stesso effetto: (nessuna chiamata di callback alla fine della transizione) function endall(transition, callback){ if(!callback) return; // ... } o, poiché è assolutamente un errore chiamare questa funzione senza un callback, il lancio di un'eccezione sembra essere il modo appropriato per gestire la situazione Penso che questo caso non abbia bisogno di troppo complicato Eccezione function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
prizma

1
Quindi, quando abbiamo separate enter()e exit()transizioni e vogliamo aspettare fino al termine di tutti e tre, dobbiamo inserire il codice nel callback per assicurarci che sia stato invocato tre volte, giusto? D3 è così disordinato! Vorrei aver scelto un'altra biblioteca.
Michael Scheper

1
Dovrei aggiungere, mi rendo conto che la tua risposta risolve alcuni dei problemi di cui mi sono lamentato e posso scrivere una funzione di utilità per applicarla. Ma non ho trovato un modo elegante per applicarlo e consentire comunque una personalizzazione aggiuntiva per ogni transizione, soprattutto quando le transizioni per i dati nuovi e vecchi sono diverse. Sono sicuro che tirerò fuori qualcosa, ma "invoca questa richiamata quando tutte queste transizioni sono finite" sembra un caso d'uso che dovrebbe essere supportato immediatamente, in una libreria matura come D3. Quindi sembra che ho scelto la libreria sbagliata, non proprio colpa di D3. Anyhoo, grazie per il tuo aiuto.
Michael Scheper

44

Ora, in d3 v4.0, è disponibile una funzione per allegare esplicitamente gestori di eventi alle transizioni:

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

Per eseguire il codice al termine di una transizione, tutto ciò di cui hai bisogno è:

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

Bellissimo. I gestori di eventi sono grossolani.
KFunk

C'è anche transition.remove()( link ), che gestisce un caso d'uso comune di transizione di un elemento dalla vista: "" Per ogni elemento selezionato, rimuove l'elemento al termine della transizione, purché l'elemento non abbia altre transizioni attive o in sospeso. Se il l'elemento ha altre transizioni attive o in sospeso, non fa nulla. "
brichins

9
Sembra che questo sia chiamato elemento PER a cui viene applicata la transizione, che non è ciò a cui si riferisce la domanda dalla mia comprensione.
Taylor C. White il

10

Un approccio leggermente diverso che funziona anche quando ci sono molte transizioni con molti elementi ciascuno in esecuzione simultaneamente:

var transitions = 0;

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

Grazie, ha funzionato bene per me. Stavo cercando di personalizzare automaticamente l'orientamento dell'etichetta dell'asse x dopo aver caricato un grafico a barre discreto. La personalizzazione non può avere effetto prima del caricamento e questo ha fornito un hook di evento attraverso il quale ho potuto farlo.
whitestryder

6

Quella che segue è un'altra versione della soluzione di Mike Bostock e ispirata dal commento di @hughes alla risposta di @ kashesandr. Fa un singolo callback sutransition fine.

Data una dropfunzione ...

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

... possiamo estendere in questo d3modo:

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

Come JSFiddle .

Usa transition.end(callback[, delayIfEmpty[, arguments...]]):

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

... o con un ritardo opzionale se transitionè vuoto:

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

... o con callbackargomenti opzionali :

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

d3.transition.endapplicherà il passato callbackanche con un vuoto transition se il numero di millisecondi è specificato o se il secondo argomento è vero. Questo inoltrerà anche eventuali argomenti aggiuntivi a callback(e solo a quegli argomenti). È importante sottolineare che questo non applicherà per impostazione predefinitacallback if transitionè vuoto, che è probabilmente un'ipotesi più sicura in questo caso.


Bello, mi piace.
kashesandr

1
Grazie @kashesandr. Questo è stato davvero ispirato dalla tua risposta per cominciare!
milos

non credo davvero che abbiamo bisogno di una funzione drop o del passaggio di argomenti, poiché lo stesso effetto può essere ottenuto da una funzione wrapper o utilizzando bind. Altrimenti penso che sia un'ottima soluzione +1
Ahmed Masud

Funziona come un fascino!
Benoît Sauvère

Vedere questa risposta, .end () è stata ufficialmente aggiunto - stackoverflow.com/a/57796240/228369
chrismarx

5

A partire da D3 v5.8.0 +, ora esiste un modo ufficiale per farlo utilizzando transition.end. I documenti sono qui:

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

Un esempio funzionante di Bostock è qui:

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

E l'idea di base è che semplicemente aggiungendo .end(), la transizione restituirà una promessa che non si risolverà fino a quando tutti gli elementi non avranno completato la transizione:

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

Vedi le note di rilascio della versione per ulteriori informazioni:

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


1
Questo è un modo molto carino di gestire le cose. Dico solo che, per quelli di voi come me che non conoscono completamente la v5 e vorrebbero implementare proprio questo, potete importare la nuova libreria di transizione utilizzando <script src = " d3js.org/d3-transition.v1 .min.js "> </ script >
DGill

0

La soluzione di Mike Bostock è stata migliorata da kashesandr + passando argomenti alla funzione di callback:

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

In realtà c'è un altro modo per farlo usando i timer.

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

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

-2

Ho risolto un problema simile impostando una durata sulle transizioni utilizzando una variabile. Quindi ho usato setTimeout()per chiamare la funzione successiva. Nel mio caso, volevo una leggera sovrapposizione tra la transizione e la chiamata successiva, come vedrai nel mio esempio:

var transitionDuration = 400;

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

setTimeout(function () {
  sortControl.forceSort();
}, (transitionDuration * 0.75)); 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.