d3 / d3-transition

Animated transitions for D3 selections.
https://d3js.org/d3-transition
ISC License
224 stars 64 forks source link

Special-case transition duration of 0, do not start any timer #93

Closed Herst closed 5 years ago

Herst commented 5 years ago

I am not sure I can properly explain all of this so please bear with me.

Problem:
I have a complex application with a lot of changes happening regularly. These changes are applied using transitions. If a page is in the background then those transitions get triggered all the time but because the timer resolution in background tabs is throttled by the browser they are piling up and causing issues if the tab is in the background for quite some while. The problem is basically what is being described at: https://stackoverflow.com/q/53042400/2261442

Workaround:
A possible workaround in my case where I want the changes to be there when the user switches back to the tab would be to apply the changes without transitions either

or

(What I wouldn't want is that after the switching back all the changes get applied with transitions, especially with transitions which could be misleading)

I can do this with a lot of ifs, e.g. in many parts of my code it does not matter whether one is working with a selection or a transition object:

selection = …

 if (document.visibilityState === "visible") {
    selection = selection.transition().duration(my_duration);
 }

selection
    .attr(…

Changes to d3-transition which would make my code cleaner:
Now I was thinking, in my case it would be nicer if I could just set the transition duration to 0 in order to achieve basically the same, e.g. that I could do:

selection
    .transition()
    .duration(document.visibilityState === "visible" ? my_duration : 0)
    .attr(…
mbostock commented 5 years ago

I think it would be a mistake to change the behavior of d3-transition this way, in the same way that it would be highly surprising if setTimeout(fun, 0) synchronously invoked the specified function.

If you want to make your code cleaner, you could decorate d3-selection with a helper method.

d3.selection.prototype.maybeTransition = function(duration) {
  return duration > 0 ? this.transition().duration(duration) : this;
};

Then:

selection
  .maybeTransition(document.visibilityState === "visible" ? my_duration : 0)
    .attr(…
koxu1996 commented 5 months ago

Based on @mbostock comment I was able to skip transitions for inactive tab:

import "d3-selection";

declare module "d3-selection" {
  export interface Selection {
    maybeTransition(milliseconds: number, name?: string): this;
  }
}
import "d3-transition";

declare module "d3-transition" {
  export interface Transition {
    maybeTransition(milliseconds: number, name?: string): this;
  }
}
import * as d3 from "d3";

d3.selection.prototype.maybeTransition = function(milliseconds: number, name?: string ) {
  if (document.visibilityState === "visible") {
    return this.transition(name).duration(milliseconds);
  } else {
    return this;
  }
};
d3.transition.prototype.maybeTransition = function(milliseconds: number, name?: string ) {
  if (document.visibilityState === "visible") {
    return this.transition(name).duration(milliseconds);
  } else {
    return this;
  }
};

// Component code

      const circle = d3.select(`#circle-${id}`);
      circle
        //.transition().duration(circlePulseOutTime) // <- commented out
        .maybeTransition(circlePulseOutTime) // <- THIS IS GOLD

// ...