D3.js: Stop transitions interrupting on mouseover?

2020-08-25 05:07发布

问题:

I'm working with D3's enter/exit selections, and I want to add a transition on mouseover events too.

The problem is that if I mouseover the letters as they are moving, they freeze, because the position transition is interrupted.

Here is a JSFiddle demonstrating the problem: http://jsfiddle.net/uEuE4/1/ and this is the code I'm using to add mouseover events to the update and enter selections:

text
.on('mouseover', function(d) { 
  d3.select(this).transition().duration(100).style('fill', 'yellow');
});

How can I only add the mouseover event handlers once all other transitions are completed, in order to stop the letters freezing?

Any tips for making the code more DRY would be very welcome too.

回答1:

You can allocate a name to transition, then this transition will only be interrupted by new transition with the same name.

text
.on('mouseover', function(d) { 
  d3.select(this).transition("fillColor").duration(100).style('fill', 'yellow');
});


回答2:

I upvoted and agree with @Jason answer, this will try to complete the previous with some clarifications and a simple demo that can be used as playground for multiple transition behaviour.

Inspecting your code you have various animations going on but only two of them need to be named to get rid of all your transitions "colisions",
the two event listeners:

text.on('mouseover', function(d) {
        d3.select(this).transition("texTr").duration(100).style('fill', 'yellow');
});

enter_text.on('mouseover', function(d) {
        d3.select(this).transition("enterTexTr").duration(100).style('fill', 'yellow');
});

The long story is that without names D3 thinks that all the transitions in your code are the same thus it stops the ongoing transition (an example can be a letter transitioning) and replaces it with a new one (for example a fill transition called by the event listener), because the transition name are the same.

But sometimes the desired behaviour is to explicitly stop transition on some elements; this can be done using .interrupt("transitionName"):

.on("mouseover", function() {
        d3.select(this).interrupt("fadeOut")
        .attr("fill", "orange")
})

.on("mouseout", function(d) {
        d3.select(this).transition("fadeOut")
            .duration(5000)
            .attr("fill", "rgb(0, 0, " + (d * 10) + ")");
})

In this case without the interrupt command we can't trigger the fill orange until the fadeOut ends (5 seconds!).

Here the FIDDLE that you can play with :)



回答3:

I also had a problem with mouseovers interrupting transitions, and came up with the following (admittedly hacky) solution: before the transition, add the css style pointer-events: none to the svg element; then remove it after the transition. Actually, I've found it works more reliably to apply the style to an element which encloses the svg.

E.g.:

<div class="chart-container">
    <svg></svg>
</div>

Then:

$('.chart-container').addClass('no-mouse');
d3.select("svg").on("mouseover", function(d) {...})
    .on("mouseout", function(d) {...})
    .transition()
        .duration(animDuration)
        .attr("...", ...);
setTimeout(function() {
    $('.chart-container').removeClass('no-mouse');
}, animDuration+10);

with the css:

.no-mouse {
    pointer-events: none;
}

Works in Firefox, Safari, Chrome and even IE 8. But keen to hear a cleaner solution.



标签: d3.js