Gọi lại khi kết thúc quá trình chuyển đổi


98

Tôi cần phải thực hiện một phương pháp fadeOut (tương tự như jQuery) sử dụng D3.js . Những gì tôi cần làm là đặt độ mờ về 0 bằng cách sử dụng transition().

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

Vấn đề là tôi cần gọi lại để nhận biết khi nào quá trình chuyển đổi đã kết thúc. Làm cách nào để triển khai một lệnh gọi lại?

Câu trả lời:


144

Bạn muốn lắng nghe sự kiện "kết thúc" của quá trình chuyển đổi.

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

// old way
d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
  • Bản demo này sử dụng sự kiện "kết thúc" để xâu chuỗi nhiều quá trình chuyển đổi theo thứ tự.
  • Các ví dụ donut mà tàu với D3 cũng sử dụng này để chuỗi lại với nhau nhiều hiệu ứng chuyển tiếp.
  • Đây là bản trình diễn của riêng tôi thay đổi kiểu của các phần tử khi bắt đầu và kết thúc quá trình chuyển đổi.

Từ tài liệu cho transition.each([type],listener):

Nếu loại được chỉ định, hãy thêm trình nghe cho các sự kiện chuyển tiếp, hỗ trợ cả sự kiện "bắt đầu" và "kết thúc". Người nghe sẽ được gọi cho từng phần tử riêng lẻ trong quá trình chuyển đổi, ngay cả khi quá trình chuyển đổi có độ trễ và thời lượng liên tục. Sự kiện bắt đầu có thể được sử dụng để kích hoạt thay đổi tức thời khi mỗi phần tử bắt đầu chuyển đổi. Sự kiện kết thúc có thể được sử dụng để bắt đầu chuyển đổi nhiều giai đoạn bằng cách chọn phần tử hiện tại thisvà tạo ra một chuyển đổi mới. Mọi chuyển đổi được tạo trong sự kiện kết thúc sẽ kế thừa ID chuyển đổi hiện tại và do đó sẽ không ghi đè quá trình chuyển đổi mới hơn đã được lên lịch trước đó.

Xem chuỗi diễn đàn này về chủ đề để biết thêm chi tiết.

Cuối cùng, lưu ý rằng nếu bạn chỉ muốn xóa các phần tử sau khi chúng đã mờ đi (sau khi quá trình chuyển đổi kết thúc), bạn có thể sử dụng transition.remove().


7
Cảm ơn rât nhiều. Đây là một thư viện TUYỆT VỜI, nhưng để tìm được thông tin quan trọng trong tài liệu thì không dễ dàng như vậy.
Tony

9
Vì vậy, vấn đề của tôi với cách tiếp tục này từ cuối quá trình chuyển đổi là nó chạy hàm của bạn N lần (đối với N mục trong tập hợp các phần tử chuyển tiếp). Điều này đôi khi xa lý tưởng.
Steven Lu

2
Tôi có cùng một vấn đề. Rất mong nó sẽ chạy các chức năng một lần sau khi loại bỏ cuối cùng
canyon289

1
Làm cách nào để bạn thực hiện lệnh gọi lại chỉ sau khi tất cả các quá trình chuyển đổi kết thúc cho a d3.selectAll()(thay vì sau khi mỗi phần tử kết thúc)? Nói cách khác, tôi chỉ muốn gọi lại một hàm sau khi tất cả các phần tử kết thúc quá trình chuyển đổi.
hobbes

Xin chào, liên kết đầu tiên đến biểu đồ thanh ngăn xếp / nhóm trỏ đến một sổ ghi chép có thể quan sát được mà không sử dụng bất kỳ trình .eachnghe sự kiện nào cũng như "end"sự kiện. Nó dường như không chuyển tiếp "chuỗi". Liên kết thứ hai trỏ đến một github không tải đối với tôi.
The Red Pea,

65

Giải pháp của Mike Bostock cho v3 với một bản cập nhật nhỏ:

  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
Nếu vùng chọn không chứa các phần tử, thì lệnh gọi lại sẽ không bao giờ kích hoạt. Một cách để khắc phục điều này làif (transition.size() === 0) { callback(); }
ôm vào

1
if (! callback) callback = function () {}; tại sao không trở lại ngay lập tức, hoặc ném một ngoại lệ? Một lệnh gọi lại không hợp lệ không đánh bại toàn bộ mục đích của quy trình này, tại sao lại tiếp tục với nó như một người thợ đồng hồ mù? :)
prizma

1
@kashandr đơn giản là người dùng không thể làm gì, vì người dùng sẽ gặp phải hiệu ứng tương tự: (không có lệnh gọi lại khi kết thúc quá trình chuyển đổi) function endall(transition, callback){ if(!callback) return; // ... } hoặc vì đây là lỗi nghiêm trọng nhất khi gọi hàm này mà không có lệnh gọi lại, nên ném một đường nối ngoại lệ đến là cách thích hợp để xử lý các tình huống tôi nghĩ rằng trường hợp này không cần phải quá phức tạp Exception function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
Prizma

1
Vì vậy, khi chúng ta có quá trình chuyển đổi enter()exit()chuyển tiếp riêng biệt , và muốn đợi cho đến khi cả ba kết thúc, chúng ta cần đặt mã vào lệnh gọi lại để đảm bảo rằng nó đã được gọi ba lần, phải không? D3 lộn xộn quá! Tôi ước tôi đã chọn một thư viện khác.
Michael Scheper

1
Tôi nên nói thêm, tôi nhận thấy câu trả lời của bạn giải quyết được một số vấn đề mà tôi quan tâm và tôi có thể viết một hàm tiện ích để áp dụng nó. Nhưng tôi đã không tìm thấy một cách hay để áp dụng nó và vẫn cho phép tùy chỉnh bổ sung cho mỗi lần chuyển đổi, đặc biệt là khi quá trình chuyển đổi cho dữ liệu mới và cũ khác nhau. Tôi chắc chắn rằng tôi sẽ nghĩ ra một cái gì đó, nhưng 'gọi lệnh gọi lại này khi tất cả các quá trình chuyển đổi này đã kết thúc' có vẻ như là một trường hợp sử dụng cần được hỗ trợ ngay lập tức, trong một thư viện trưởng thành như D3. Vì vậy, có vẻ như tôi đã chọn sai thư viện — không thực sự là lỗi của D3. Anyhoo, cảm ơn sự giúp đỡ của bạn.
Michael Scheper

44

Bây giờ, trong d3 v4.0, có một cơ sở để gắn các trình xử lý sự kiện một cách rõ ràng vào các chuyển tiếp:

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

Để thực thi mã khi quá trình chuyển đổi hoàn tất, tất cả những gì bạn cần là:

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

Xinh đẹp. Trình xử lý sự kiện là thô.
KFunk

Ngoài ra còn có transition.remove()( liên kết ), xử lý một trường hợp sử dụng phổ biến là chuyển đổi một phần tử khỏi chế độ xem: "" Đối với mỗi phần tử đã chọn, hãy xóa phần tử khi quá trình chuyển đổi kết thúc, miễn là phần tử không có chuyển đổi đang hoạt động hoặc đang chờ xử lý nào khác. Nếu phần tử có các chuyển đổi đang hoạt động hoặc đang chờ xử lý khác, không làm gì cả. "
brichins

9
Có vẻ như đây được gọi là phần tử PER mà quá trình chuyển đổi được áp dụng, đó không phải là câu hỏi liên quan đến sự hiểu biết của tôi.
Taylor C. White

10

Một cách tiếp cận hơi khác cũng hoạt động khi có nhiều chuyển tiếp với nhiều phần tử chạy đồng thời:

var transitions = 0;

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

Cảm ơn, điều đó đã làm việc tốt cho tôi. Tôi đang cố gắng tự động tùy chỉnh hướng nhãn trục x sau khi tải biểu đồ thanh rời rạc. Tùy chỉnh không thể có hiệu lực trước khi tải và điều này đã cung cấp một móc sự kiện để tôi có thể thực hiện việc này.
whitestryder

6

Sau đây là một phiên bản khác của giải pháp của Mike Bostock và được truyền cảm hứng từ nhận xét của @hughes cho câu trả lời của @ kashandr. Nó thực hiện một cuộc gọi lại duy nhất khi transitionkết thúc.

Cho một drophàm ...

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

... chúng tôi có thể mở rộng d3như vậy:

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

Là một JSFiddle .

Sử dụng transition.end(callback[, delayIfEmpty[, arguments...]]):

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

... hoặc với độ trễ tùy chọn nếu transitiontrống:

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

... hoặc với các callbackđối số tùy chọn :

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

d3.transition.endsẽ áp dụng giá trị được truyền callbackngay cả với giá trị trống transition nếu số mili giây được chỉ định hoặc nếu đối số thứ hai là đúng. Điều này cũng sẽ chuyển tiếp bất kỳ đối số bổ sung nào đến callback(và chỉ những đối số đó). Quan trọng hơn, điều này sẽ không theo mặc định áp dụng callbacknếu transitionlà trống rỗng, mà có lẽ là một giả định an toàn trong trường hợp này.


Thật tuyệt, tôi thích nó.
kashandr

1
Cảm ơn @kashandr. Điều này thực sự được truyền cảm hứng từ câu trả lời của bạn để bắt đầu!
milos

không thực sự nghĩ rằng chúng ta cần một hàm drop hoặc truyền các đối số, vì hiệu ứng tương tự có thể đạt được bằng một hàm wrapper hoặc bằng cách sử dụng bind. Nếu không tôi nghĩ đó là một giải pháp tuyệt vời 1
Ahmed Masud

Hoạt động như một sự quyến rũ!
Benoît Sauvère

Hãy xem phản hồi này, .end () hiện đã chính thức được thêm vào - stackoverflow.com/a/57796240/228369
chrismarx

5

Kể từ D3 v5.8.0 +, hiện đã có một cách chính thức để thực hiện việc này bằng cách sử dụng transition.end. Các tài liệu ở đây:

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

Một ví dụ làm việc từ Bostock là ở đây:

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

Và ý tưởng cơ bản là chỉ cần thêm bớt .end(), quá trình chuyển đổi sẽ trả về một lời hứa sẽ không giải quyết cho đến khi tất cả các yếu tố được thực hiện chuyển đổi:

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

Xem ghi chú phát hành phiên bản để biết thêm:

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


1
Đây là một cách xử lý rất tốt. Tôi chỉ nói rằng, đối với những người trong số các bạn như tôi, những người không biết tất cả v5 và chỉ muốn thực hiện điều này, bạn có thể nhập thư viện chuyển đổi mới bằng cách sử dụng <script src = " d3js.org/d3-transition.v1 .min.js "> </ script >
DGill

0

Giải pháp của Mike Bostock được cải thiện bằng cách kashandr + chuyển đối số vào hàm gọi lại:

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

Trên thực tế, có một cách nữa để làm điều này bằng cách sử dụng bộ hẹn giờ.

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

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

-2

Tôi đã giải quyết một vấn đề tương tự bằng cách đặt thời lượng chuyển tiếp bằng một biến. Sau đó, tôi sử dụng setTimeout()để gọi chức năng tiếp theo. Trong trường hợp của tôi, tôi muốn có một chút trùng lặp giữa quá trình chuyển đổi và cuộc gọi tiếp theo, như bạn sẽ thấy trong ví dụ của tôi:

var transitionDuration = 400;

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

setTimeout(function () {
  sortControl.forceSort();
}, (transitionDuration * 0.75)); 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.