在过渡结束时调用回调


98

我需要使用D3.js制作FadeOut方法(类似于jQuery)。我需要做的是使用将不透明度设置为0 transition()

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

问题是我需要一个回调来实现转换完成的时间。如何实现回调?

Answers:


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

从文档中transition.each([type],listener)

如果指定了type,则为过渡事件添加一个侦听器,同时支持“开始”和“结束”事件。即使过渡具有恒定的延迟和持续时间,也会为过渡中的每个单独元素调用侦听器。当每个元素开始转换时,开始事件可用于触发瞬时更改。通过选择当前元素this,并派生新的过渡,可以将结束事件用于启动多阶段过渡。在结束事件期间创建的任何过渡都将继承当前的过渡ID,因此不会覆盖以前计划的较新过渡。

有关更多详细信息,请参见主题上的此论坛主题

最后,请注意,如果您只想在元素淡出后(过渡完成后)将其删除,则可以使用transition.remove()


7
非常感谢你。这是一个GREAT GREAT库,但是要在文档中找到重要信息并不容易。
托尼

9
因此,这种从过渡结束开始继续的方式的问题是,它会连续运行您的函数N次(对于过渡元素集中的N个项目)。有时这远非理想。
史蒂文·卢

2
我有同样的问题。希望它在最后一次删除后运行一次该功能
canyon289 2015年

1
仅在所有转换完成d3.selectAll()之后(而不是在每个元素完成之后)如何执行回调?换句话说,我只想在所有元素完成转换后回调一个函数。
hobbes3

您好,堆栈/组条形图的第一个链接指向一个不使用任何.each事件侦听器或"end"事件的Observable笔记本。它似乎并没有“链接”过渡。第二个链接指向一个不为我加载的github。
红豌豆

65

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
if(!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()link),它处理从视图过渡元素的常见用例:`“对于每个选定元素,只要过渡没有其他活动或挂起的过渡,就在过渡结束时删除该元素。元素具有其他活动或待定的转换,则不执行任何操作。”
brichins

9
过渡似乎适用于所谓的PER元素,根据我的理解,这并不是问题所在。
泰勒·C·怀特

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如果指定了毫秒数第二个参数为true,callback即使空值也将应用传递的内容。这还将把所有其他参数转发给(并且仅这些参数)。重要的是,默认情况下不会应用if 为空,在这种情况下,这可能是一个更安全的假设。transition callbackcallbacktransition


很好,我喜欢。
kashesandr

1
谢谢@kashesandr。确实,这是受到您的回答启发的!
米洛斯2015年

真的不认为我们需要放置函数或传递参数,因为可以通过包装函数或通过使用绑定来实现相同的效果。否则,我认为这是一个很好的解决方案+1
Ahmed Masud 2015年

奇迹般有效 !
2016年

看到这个反应,.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)); 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.