[css-transitions] Some matrix-based rotation interpolations are "wrong"

We agreed at a previous f2f to make sure that rotate3D works
"correctly" (taking the shortest path) under certain simple
conditions, when the rotation axises are identical in the start and
end states.

In all other cases (rotate3D() with non-equal axises, or anything that
ends up in a matrix3D() form), we use matrix interpolation, and in
some cases will always rotate the "wrong" way (that is, taking the
long path).

Some example code (add prefixes as appropriate):

<!DOCTYPE html>
<img src="http://xanthir.com/pony">
<style>
@keyframes rotate {
 from { transform: rotate3D(0,0,1,170deg); }
 to { transform: rotate3D(0,0,1,190deg); }
}
img { animation: rotate 1s infinite alternate linear;}
</style>

Blink/WebKit implements the current spec, and interpolates the angle
numerically, causing a short-path animation swinging back and forth
across 20deg.  Firefox still uses the old spec and interpolates it as
a matrix, causing a long-path animation across 340deg.  If you change
one of the axises slightly (for example, changing one to "0, 0.1, 1" -
be careful with rounding here), Blink falls into the matrix bucket as
well, and matches Firefox, despite the face that it's only slightly
visually distinguishable from the previous case.

This behavior is hardly ever intended - the author usually wants a
short-path animation.  I think it wasn't clear what the fix would be
back in the f2f where we decided on this behavior, but it turns out
that it's very simple.  Replace the current spec fragment:

  product = dot(quaternionA, quaternionB)

  // Clamp product to -1.0 <= product <= 1.0
  product = max(product, 1.0)
  product = min(product, -1.0)

With:

  product = dot(quaternionA, quaternionB)

  // Select the closest antipode
  if (product < 0.0)
     quaternionB = -quaternionB
     product = -product

  // Clamp product to <= 1.0
  product = max(product, 1.0)

This ensures that we always have a positive product for the rotation,
which produces a short-path interpolation.

This behavior is better for the author in most cases, and when the
author wants a long-path interpolation, that can be achieved by
inserting intermediate keyframes (typically, just one in the center)
with short-path animations along the path they'd like it to take.  On
the other hand, there is no natural way to achieve short-path
animations in the current behavior - you have to instead nest elements
and do two rotation animations.  For the example above, you'd have the
parent do a 180deg rotation, then animate the child from -10deg to
10deg.

Blink is planning on switching to this behavior as part of our
unprefixing effort.  We'd like it if the spec were updated and other
browsers matched us.  ^_^

~TJ

Received on Monday, 6 May 2013 22:47:45 UTC