Panggil panggilan balik di akhir transisi


98

Saya perlu membuat metode FadeOut (mirip dengan jQuery) menggunakan D3.js . Yang perlu saya lakukan adalah mengatur opacity menjadi 0 menggunakan transition().

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

Masalahnya adalah saya memerlukan panggilan balik untuk menyadari ketika transisi telah selesai. Bagaimana cara menerapkan callback?

Jawaban:


144

Anda ingin mendengarkan acara "akhir" dari transisi.

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

// old way
d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
  • Demo ini menggunakan acara "akhir" untuk merangkai banyak transisi secara berurutan.
  • Contoh donat yang dikirimkan dengan D3 juga menggunakan ini untuk menyatukan beberapa transisi.
  • Inilah demo saya sendiri yang mengubah gaya elemen di awal dan akhir transisi.

Dari dokumentasi untuk transition.each([type],listener):

Jika jenisnya ditentukan, tambahkan listener untuk peristiwa transisi, yang mendukung peristiwa "mulai" dan "akhir". Listener akan dipanggil untuk setiap elemen individu dalam transisi, meskipun transisi memiliki penundaan dan durasi yang konstan. Peristiwa awal dapat digunakan untuk memicu perubahan seketika saat setiap elemen mulai melakukan transisi. Acara akhir dapat digunakan untuk memulai transisi multi-tahap dengan memilih elemen saat ini this, dan mendapatkan transisi baru. Setiap transisi yang dibuat selama acara akhir akan mewarisi ID transisi saat ini, sehingga tidak akan menimpa transisi yang lebih baru yang telah dijadwalkan sebelumnya.

Lihat utas forum ini tentang topik untuk lebih jelasnya.

Terakhir, perhatikan bahwa jika Anda hanya ingin menghapus elemen setelah memudar (setelah transisi selesai), Anda dapat menggunakan transition.remove().


7
Terima kasih banyak. Ini adalah perpustakaan BESAR BESAR, tetapi tidak mudah untuk menemukan informasi penting dalam dokumentasi.
Tony

9
Jadi, masalah saya dengan cara melanjutkan dari akhir transisi ini adalah ia menjalankan fungsi Anda N kali (untuk N item dalam himpunan elemen transisi). Ini terkadang jauh dari ideal.
Steven Lu

2
Saya memiliki masalah yang sama. Berharap itu akan menjalankan fungsi sekali setelah penghapusan terakhir
canyon289

1
Bagaimana Anda melakukan callback hanya setelah semua transisi selesai untuk a d3.selectAll()(bukan setelah setiap elemen selesai)? Dengan kata lain, saya hanya ingin memanggil kembali satu fungsi setelah semua elemen selesai bertransisi.
Hobbes3

Hai, tautan pertama ke bagan batang tumpukan / grup mengarah ke buku catatan yang dapat diamati yang tidak menggunakan .eachpendengar acara, maupun "end"acara. Sepertinya tidak ada transisi "rantai". Tautan kedua menunjuk ke github yang tidak memuat untuk saya.
The Red Pea

65

Solusi Mike Bostock untuk v3 dengan pembaruan kecil:

  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
Jika pilihan berisi nol elemen, callback tidak akan pernah diaktifkan. Salah satu cara untuk memperbaikinya adalahif (transition.size() === 0) { callback(); }
hughes

1
jika (! callback) callback = function () {}; mengapa tidak langsung kembali, atau membuat pengecualian? Callback yang tidak valid memang mengalahkan seluruh tujuan dari rutinitas ini, mengapa harus melakukannya seperti pembuat jam buta? :)
prizma

1
@kashesandr seseorang dapat dengan mudah tidak melakukan apa-apa, karena pengguna akan mengalami efek yang sama: (tidak ada panggilan callback di akhir transisi) function endall(transition, callback){ if(!callback) return; // ... } atau, karena itu adalah kesalahan yang paling pasti untuk memanggil fungsi ini tanpa callback, melempar lapisan pengecualian ke menjadi cara yang tepat untuk menangani situasi Saya rasa kasus ini tidak perlu terlalu rumit Pengecualian function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
prizma

1
Jadi ketika kita memiliki transisi enter()dan terpisah exit(), dan ingin menunggu sampai ketiganya selesai, kita perlu memasukkan kode di callback untuk memastikan itu telah dipanggil tiga kali, bukan? D3 sangat berantakan! Saya berharap saya memilih perpustakaan lain.
Michael Scheper

1
Saya harus menambahkan, saya menyadari jawaban Anda memecahkan beberapa masalah yang saya keluhkan, dan saya dapat menulis fungsi utilitas untuk menerapkannya. Tetapi saya belum menemukan cara yang elegan untuk menerapkannya dan masih mengizinkan kustomisasi tambahan untuk setiap transisi, terutama jika transisi untuk data baru dan lama berbeda. Saya yakin saya akan menemukan sesuatu, tetapi 'panggil callback ini ketika semua transisi ini telah selesai' tampaknya seperti kasus penggunaan yang harus didukung di luar kotak, di pustaka yang sudah matang seperti D3. Jadi, sepertinya saya memilih perpustakaan yang salah — bukan kesalahan D3. Anyhoo, terima kasih atas bantuannya.
Michael Scheper

44

Sekarang, di d3 v4.0, ada fasilitas untuk secara eksplisit melampirkan event handler ke transisi:

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

Untuk mengeksekusi kode ketika transisi telah selesai, yang Anda butuhkan hanyalah:

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

Cantik. Penangan acara kotor.
KFunk

Ada juga transition.remove()( tautan ), yang menangani kasus penggunaan umum untuk mentransisikan elemen dari tampilan: `" Untuk setiap elemen yang dipilih, menghapus elemen saat transisi berakhir, selama elemen tersebut tidak memiliki transisi aktif atau tertunda lainnya. Jika elemen memiliki transisi aktif atau tertunda lainnya, tidak melakukan apa pun. "
brichins

9
Sepertinya ini disebut elemen PER tempat transisi diterapkan, yang bukan pertanyaannya dari pemahaman saya.
Taylor C. White

10

Pendekatan yang sedikit berbeda yang juga berfungsi saat ada banyak transisi dengan banyak elemen yang masing-masing berjalan secara bersamaan:

var transitions = 0;

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

Terima kasih, itu bekerja dengan baik untuk saya. Saya mencoba menyesuaikan orientasi label sumbu x secara otomatis setelah memuat diagram batang diskrit. Kustomisasi tidak dapat diterapkan sebelum dimuat, dan ini memberikan event hook di mana saya dapat melakukan ini.
whitestryder

6

Berikut ini adalah versi lain dari solusi Mike Bostock dan terinspirasi oleh komentar @hughes untuk jawaban @ kashesandr. Itu membuat panggilan balik tunggal transitiondi akhir.

Diberikan dropfungsi ...

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

... kita dapat memperpanjang d3seperti ini:

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

Sebagai JSFiddle .

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

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

... atau dengan penundaan opsional jika transitionkosong:

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

... atau dengan callbackargumen opsional :

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

d3.transition.endakan menerapkan lulus callbackbahkan dengan kosong transition jika jumlah milidetik ditentukan atau jika argumen kedua benar. Ini juga akan meneruskan argumen tambahan apa pun ke callback(dan hanya argumen itu). Yang penting, ini tidak akan secara default menerapkan callbackjika transitionkosong, yang mungkin merupakan asumsi yang lebih aman dalam kasus seperti itu.


Itu bagus, saya menyukainya.
kashesandr

1
Terima kasih @kashesandr. Ini memang terinspirasi oleh jawaban Anda sejak awal!
milos

jangan benar-benar berpikir kita membutuhkan fungsi drop atau argumen yang lewat, karena efek yang sama dapat dicapai dengan fungsi pembungkus atau dengan memanfaatkan bind. Kalau tidak, saya pikir itu solusi yang bagus +1
Ahmed Masud

Bekerja seperti pesona!
Benoît Sauvère

Lihat tanggapan ini, .end () sekarang telah secara resmi ditambahkan - stackoverflow.com/a/57796240/228369
chrismarx

5

Mulai D3 v5.8.0 +, sekarang ada cara resmi untuk melakukan ini menggunakan transition.end . Dokumennya ada di sini:

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

Contoh kerja dari Bostock ada di sini:

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

Dan ide dasarnya adalah hanya dengan menambahkan .end(), transisi akan mengembalikan janji yang tidak akan terselesaikan hingga semua elemen selesai ditransisikan:

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

Lihat catatan rilis versi untuk lebih banyak lagi:

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


1
Ini adalah cara yang sangat bagus untuk menangani sesuatu. Saya hanya akan mengatakan, bagi Anda seperti saya yang tidak tahu semua tentang v5 dan ingin menerapkan ini saja, Anda dapat mengimpor pustaka transisi baru menggunakan <script src = " d3js.org/d3-transition.v1 .min.js "> </ script >
DGill

0

Solusi Mike Bostock ditingkatkan dengan kashesandr + meneruskan argumen ke fungsi 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

Sebenarnya ada satu cara lagi untuk melakukan ini menggunakan pengatur waktu.

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

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

-2

Saya memecahkan masalah serupa dengan mengatur durasi transisi menggunakan variabel. Kemudian saya biasa setTimeout()memanggil fungsi selanjutnya. Dalam kasus saya, saya ingin sedikit tumpang tindih antara transisi dan panggilan berikutnya, seperti yang akan Anda lihat dalam contoh saya:

var transitionDuration = 400;

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

setTimeout(function () {
  sortControl.forceSort();
}, (transitionDuration * 0.75)); 
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.