เรียกใช้การโทรกลับเมื่อสิ้นสุดการเปลี่ยนแปลง


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):

หากระบุประเภทไว้ให้เพิ่ม Listener สำหรับเหตุการณ์การเปลี่ยนแปลงโดยรองรับทั้งเหตุการณ์ "start" และ "end" ผู้ฟังจะถูกเรียกใช้สำหรับแต่ละองค์ประกอบในการเปลี่ยนแม้ว่าการเปลี่ยนแปลงจะมีความล่าช้าและระยะเวลาคงที่ เหตุการณ์เริ่มต้นสามารถใช้เพื่อทริกเกอร์การเปลี่ยนแปลงทันทีเมื่อแต่ละองค์ประกอบเริ่มเปลี่ยน เหตุการณ์สิ้นสุดสามารถใช้เพื่อเริ่มการเปลี่ยนหลายขั้นตอนโดยการเลือกองค์ประกอบปัจจุบันthisและรับการเปลี่ยนแปลงใหม่ การเปลี่ยนใด ๆ ที่สร้างขึ้นระหว่างเหตุการณ์สิ้นสุดจะสืบทอด ID การเปลี่ยนแปลงปัจจุบันและจะไม่แทนที่การเปลี่ยนแปลงที่ใหม่กว่าที่กำหนดไว้ก่อนหน้านี้

ดูหัวข้อฟอรัมนี้ในหัวข้อสำหรับรายละเอียดเพิ่มเติม

สุดท้ายโปรดทราบว่าหากคุณต้องการลบองค์ประกอบหลังจากที่มันจางหายไป (หลังจากการเปลี่ยนเสร็จสิ้น) คุณสามารถtransition.remove()ใช้ได้


7
ขอบคุณมาก. นี่เป็นห้องสมุดที่ยอดเยี่ยม แต่การค้นหาข้อมูลสำคัญในเอกสารประกอบไม่ใช่เรื่องง่าย
Tony

9
ดังนั้นปัญหาของฉันเกี่ยวกับวิธีการดำเนินการต่อจากจุดสิ้นสุดของการเปลี่ยนแปลงนี้คือมันเรียกใช้ฟังก์ชันของคุณ N ครั้ง (สำหรับ N รายการในชุดองค์ประกอบการเปลี่ยน) สิ่งนี้ยังห่างไกลจากอุดมคติในบางครั้ง
Steven Lu

2
ฉันมีปัญหาเดียวกัน หวังว่ามันจะเรียกใช้ฟังก์ชันหนึ่งครั้งหลังจากการลบครั้งสุดท้าย
canyon289

1
คุณจะโทรกลับได้อย่างไรหลังจากการเปลี่ยนทั้งหมดเสร็จสิ้นสำหรับ a d3.selectAll()(แทนหลังจากแต่ละองค์ประกอบเสร็จสิ้น) กล่าวอีกนัยหนึ่งฉันต้องการเรียกกลับเพียงฟังก์ชันเดียวเมื่อองค์ประกอบทั้งหมดเสร็จสิ้นการเปลี่ยนแปลง
hobbes3

สวัสดีลิงค์แรกไปยังแผนภูมิแท่งแบบสแต็ก / กลุ่มชี้ไปที่สมุดบันทึกที่สังเกตได้ซึ่งไม่ใช้ตัว.eachฟังเหตุการณ์ใด ๆหรือ"end"เหตุการณ์ ดูเหมือนจะไม่ใช่การเปลี่ยน "ลูกโซ่" ลิงค์ที่สองชี้ไปที่ github ที่ไม่โหลดให้ฉัน
The Red Pea

66

โซลูชันของ Mike Bostock สำหรับ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) 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 เละเทะ! ฉันหวังว่าฉันจะเลือกห้องสมุดอื่น
Michael Scheper

1
ฉันควรเพิ่มฉันรู้ว่าคำตอบของคุณช่วยแก้ปัญหาบางอย่างที่ฉันจับได้และฉันสามารถเขียนฟังก์ชันยูทิลิตี้เพื่อใช้งานได้ แต่ฉันไม่พบวิธีที่สวยงามในการนำไปใช้และยังอนุญาตให้ปรับแต่งเพิ่มเติมสำหรับการเปลี่ยนแปลงแต่ละครั้งโดยเฉพาะอย่างยิ่งเมื่อการเปลี่ยนข้อมูลใหม่และข้อมูลเก่าแตกต่างกัน ฉันแน่ใจว่าฉันจะมากับอะไรบางอย่าง แต่ 'วิงวอนนี้ติดต่อกลับเมื่อทุกช่วงการเปลี่ยนภาพเหล่านี้ได้เสร็จสิ้น' ดูเหมือนว่ากรณีการใช้งานที่ควรได้รับการสนับสนุนออกจากกล่องในห้องสมุดเป็นผู้ใหญ่เป็น D3 ดูเหมือนว่าฉันเลือกห้องสมุดผิดไม่ใช่ความผิดของ D3 จริงๆ Anyhoo ขอบคุณสำหรับความช่วยเหลือของคุณ
Michael Scheper

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

9
ดูเหมือนว่าสิ่งนี้เรียกว่าองค์ประกอบ PER ที่การเปลี่ยนแปลงถูกนำไปใช้ซึ่งไม่ใช่คำถามที่เกี่ยวข้องกับความเข้าใจของฉัน
Taylor

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

ต่อไปนี้เป็นโซลูชันของ Mike Bostock อีกเวอร์ชันหนึ่งและได้รับแรงบันดาลใจจากความคิดเห็นของ @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 transitionว่างซึ่งอาจเป็นข้อสันนิษฐานที่ปลอดภัยกว่าในกรณีเช่นนี้


ดีมากฉันชอบมัน
kashesandr

1
ขอบคุณ @kashesandr นี่เป็นแรงบันดาลใจจากคำตอบของคุณที่จะเริ่มต้น!
milos

อย่าคิดว่าเราต้องการฟังก์ชั่นการดร็อปหรือการส่งผ่านอาร์กิวเมนต์เนื่องจากเอฟเฟกต์เดียวกันนี้สามารถทำได้โดยฟังก์ชัน wrapper หรือโดยใช้การโยง ไม่อย่างนั้นฉันคิดว่ามันเป็นทางออกที่ดี +1
Ahmed Masud

ใช้งานได้เหมือนมีเสน่ห์!
BenoîtSauvère

ดูคำตอบนี้, .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

โซลูชันของ Mike Bostock ได้รับการปรับปรุงโดย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)); 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.