【问题标题】:Invoke a callback at the end of a transition在转换结束时调用回调
【发布时间】:2012-05-28 08:35:18
【问题描述】:

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

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

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

【问题讨论】:

    标签: javascript d3.js transition


    【解决方案1】:

    您想监听转换的“结束”事件。

    // d3 v5
    d3.select("#myid").transition().style("opacity","0").on("end", myCallback);
    
    // old way
    d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
    
    • This demo 使用“结束”事件按顺序链接许多转换。
    • D3 附带的donut example 也使用它来链接多个转换。
    • my own demo 在过渡的开始和结束时更改元素的样式。

    来自transition.each([type],listener) 的文档:

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

    详情请见this forum thread on the topic

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

    【讨论】:

    • 非常感谢。这是一个 GREAT GREAT 库,但要在文档中找到重要信息并不容易。
    • 所以,我对这种从过渡结束继续的方式的问题是它运行你的函数 N 次(对于过渡元素集中的 N 个项目)。有时这远非理想。
    • 我也有同样的问题。希望它会在最后一次删除后运行一次该功能
    • 如何仅在d3.selectAll()所有 转换完成后(而不是在每个元素完成后)执行回调?换句话说,我只想在所有元素完成转换后回调一个函数。
    • 嗨,堆栈/组条形图的第一个链接指向一个 Observable 笔记本,它不使用任何 .each 事件侦听器,也不使用 "end" 事件。它似乎没有“链接”转换。第二个链接指向一个不适合我的 github。
    【解决方案2】:

    Mike Bostock 的 solution 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") });
    

    【讨论】:

    • 如果选择包含零个元素,回调将永远不会触发。解决此问题的一种方法是if (transition.size() === 0) { callback(); }
    • if (!callback) callback = function(){};为什么不立即返回,或者抛出异常?一个无效的回调确实违背了这个惯例的全部目的,为什么要像一个盲目的钟表匠一样去经历它呢? :)
    • @kashesandr 可以什么都不做,因为用户将体验到相同的效果:(在转换结束时没有回调调用)function endall(transition, callback){ if(!callback) return; // ... } 或者,因为调用它肯定是一个错误这个函数没有回调,抛出异常接缝是处理这种情况的合适方法我认为这种情况不需要太复杂的异常function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
    • 所以当我们有单独的enter()exit() 转换,并且想等到所有三个都完成时,我们需要在回调中放入代码以确保它被调用了三次,对吧? D3太乱了!我希望我选择了另一个图书馆。
    • 我应该补充一下,我意识到你的回答解决了我抱怨的一些问题,我可以编写一个实用程序函数来应用它。但是我还没有找到一种优雅的方式来应用它并且仍然允许对每个转换进行额外的自定义,尤其是当新旧数据的转换不同时。我确信我会想出一些东西,但是“当所有这些转换都完成时调用这个回调”似乎是一个用例,应该在一个成熟的库中支持开箱即用作为 D3。所以看来我选择了错误的库——这并不是 D3 的错。 Anyhoo,谢谢你的帮助。
    【解决方案3】:

    现在,在 d3 v4.0 中,有一个工具可以将事件处理程序显式附加到转换:

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

    要在转换完成后执行代码,您只需要:

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

    【讨论】:

    • 美丽。事件处理程序很糟糕。
    • 还有transition.remove() (link),它处理从视图中转换元素的常见用例:`"对于每个选定的元素,在转换结束时移除元素,只要因为该元素没有其他活动或待处理的转换。如果该元素有其他活动或待处理的转换,则不执行任何操作。"
    • 看起来这被称为PER元素,转换被应用到,这不是我理解的问题。
    【解决方案4】:

    一种稍微不同的方法,也适用于同时运行多个元素的多个转换:

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

    【讨论】:

    • 谢谢,这对我很有效。我试图在加载离散条形图后自动自定义 x 轴标签方向。自定义无法在加载之前生效,这提供了一个事件挂钩,我可以通过它来执行此操作。
    【解决方案5】:

    以下是 Mike Bostock 的 solution 的另一个版本,灵感来自 @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;
    }
    

    As a 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(并且只有那些参数)。重要的是,如果transition 为空,这将默认情况下不会应用callback,在这种情况下这可能是一个更安全的假设。

    【讨论】:

    • 很好,我喜欢。
    • 谢谢@kashesandr。这确实受到了您的回答的启发!
    • 并不认为我们需要 drop 函数或传递参数,因为同样的效果可以通过包装函数或使用 bind 来实现。否则我认为这是一个很好的解决方案 +1
    • 像魅力一样工作!
    • 看到这个回复,.end() 现已正式添加-stackoverflow.com/a/57796240/228369
    【解决方案6】:

    从 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

    【讨论】:

    • 这是一种非常好的处理方式。我只想说,对于像我一样不了解所有 v5 并希望实现这一点的人,您可以使用 导入新的过渡库
    【解决方案7】:

    Mike Bostock 的 solution 改进了 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");
    

    【讨论】:

      【解决方案8】:

      实际上还有另一种使用计时器的方法。

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

      【讨论】:

        【解决方案9】:

        我通过使用变量设置转换的持续时间解决了类似的问题。然后我用setTimeout() 调用了下一个函数。就我而言,我希望转换和下一次调用之间有轻微的重叠,正如您将在我的示例中看到的那样:

        var transitionDuration = 400;
        
        selectedItems.transition().duration(transitionDuration).style("opacity", .5);
        
        setTimeout(function () {
          sortControl.forceSort();
        }, (transitionDuration * 0.75)); 
        

        【讨论】:

          猜你喜欢
          • 2018-02-28
          • 1970-01-01
          • 2020-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-12-02
          • 2014-01-23
          • 2020-12-24
          相关资源
          最近更新 更多