d3/d3-transition

transition.style("transform", …) ignores inherited styles.

edcoumont opened this issue · 1 comments

I'm trying to perform a transition to a transform:translate style with values specified as em.

This jsfiddle is reproducing the problem : https://jsfiddle.net/59ushyut/

Note the body font-size:2px so that 1em should be considered as 2px in the document.

When I start at transform: translate(100em) :

  • doing element.style("transform", "translate(200em)") is ok.
  • but doing element.transition().duration(1000).style("transform", "translate(200em)"); is sending my element way too far. The final style becomes transform: translate(3200px, 0px);, as if d3 was considering that 1em = 16px.

D3 version is 4.10.0
Issue reproduced on last versions of Chrome and Firefox.

The problem is that transition.style on the transform style uses d3.interpolateTransformCss, which computes the normalized form of the transform style property using an off-screen DIV element; see parse.js. Thus the interpolator ignores the inherited style from the body, and transitions from matrix(1, 0, 0, 1, 1600, 0) to matrix(1, 0, 0, 1, 3200, 0).

(Prior to d3-transition@1.1.0/d3@4.9.0, the computed style property value on the transitioning element was used as the starting value for the transition, so the starting value was correct: matrix(1, 0, 0, 1, 200, 0). However, the ending value would still be interpreted incorrectly as matrix(1, 0, 0, 1, 3200, 0).)

You can workaround this issue by not depending on inherited styles, by using px units, or by using transition.attr (which only allows pixel units). You could also compute the normalized values yourself on each transitioning element, and pass them to d3.interpolateTransformCss using transition.styleTween.

Fixing this is possible, but it doesn’t look easy, since it requires computing the normalized transform on the transitioning element for both the starting and ending value. And there’s no easy way to pass along the transitioning element to d3.interpolateTransformCss (without either further special-casing of transitions on the transform style, or always passing the transitioning element as the third argument to the interpolator factory, which seems like overkill and could also cause surprising behavior if interpolator factories are not expecting that additional argument).

This might require a completely different code path in transition.style for the transform attribute, so that the computed value is always used as the starting and ending value, rather than the normal behavior where the inline value takes priority.

Alternatively, we could use the computed value for both the starting and ending values, as discussed in #47 (comment). However, this behavior was recently changed in 1.1.0 as mentioned above, and I think in general using the inline values (as specified by the caller) leads to more predictable behavior; transforms are special because they are normalized rather than interpolated as-specified using d3.interpolateString. So I think it’d be better to change the special case in transition.style as described in the previous paragraph.