Bug 228896

Summary: CSS animation timing is broken when animating from scaleX(0) to scale(1)
Product: WebKit Reporter: evan.exe
Component: AnimationsAssignee: Nobody <webkit-unassigned>
Status: RESOLVED CONFIGURATION CHANGED    
Severity: Normal CC: dino, graouts, graouts, kevin_neal, simon.fraser, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: Safari 14   
Hardware: Mac (Intel)   
OS: macOS 11   
See Also: https://bugs.webkit.org/show_bug.cgi?id=223876
Attachments:
Description Flags
A screen capture showing Chrome working correctly and Safari working incorrectly
none
HTML snapshot of the current state of https://esbuild.github.io/
none
Testcase none

Description evan.exe 2021-08-07 06:33:08 PDT
Created attachment 435129 [details]
A screen capture showing Chrome working correctly and Safari working incorrectly

Repro:

1. Visit https://esbuild.github.io/ on Chrome or Firefox
2. Notice how the progress bars move together

3. Visit https://esbuild.github.io/ on Safari
4. Notice how the progress bars move separately at different speeds

The animation is a simple linear CSS animation from left to right, and is completely broken in Safari.
Comment 1 evan.exe 2021-08-07 06:35:29 PDT
Looks like this is also broken on the latest version of iOS (version 14.7.1).
Comment 2 evan.exe 2021-08-07 06:45:05 PDT
Created attachment 435130 [details]
HTML snapshot of the current state of https://esbuild.github.io/
Comment 3 Simon Fraser (smfr) 2021-08-09 20:43:23 PDT
Created attachment 435240 [details]
Testcase
Comment 4 Simon Fraser (smfr) 2021-08-09 20:44:08 PDT
The broken animation has mismatched transform functions:

        @keyframes scale-bar {
            0% { transform: scaleX(0) }
            to { transform: scale(1) }
        }

so it interpolates via matrix interpolation. This appears to use the wrong timing function.
Comment 5 Simon Fraser (smfr) 2021-08-09 20:51:54 PDT
scaleX(0) is not invertible so we fall back to software animation.
Comment 6 Simon Fraser (smfr) 2021-08-09 21:12:22 PDT
This is about interpolation via matrix decomposition.

We decompose the scaleX(0) matrix to:
(WebCore::TransformationMatrix::Decomposed2Type) $1 = (scaleX = 0, scaleY = 1, translateX = 0, translateY = 0, angle = 0, m11 = 0, m12 = 0, m21 = 0, m22 = 1)

and the scale(1) matrix to:
(WebCore::TransformationMatrix::Decomposed2Type) $2 = (scaleX = 1, scaleY = 1, translateX = 0, translateY = 0, angle = 0, m11 = 1, m12 = 0, m21 = 0, m22 = 1)

then interpolate scaleX and m11. Recomposing the matrix then causes m11 to affect scaleX in matrix[0][0]
Comment 7 Radar WebKit Bug Importer 2021-08-12 10:08:57 PDT
<rdar://problem/81854922>
Comment 8 evan.exe 2021-08-12 16:03:45 PDT
Thanks for determining the root cause. In that case I'm going to change https://esbuild.github.io/ to use a small but non-zero scale value to avoid a non-invertible matrix. This means the original repro steps will no longer work, but you will still be able to reproduce this with the attached test case.
Comment 9 Simon Fraser (smfr) 2021-08-12 17:05:55 PDT
A small scale would work. You could also use scale(x, y) or scaleX(x) in both keyframes.
Comment 10 evan.exe 2021-08-12 17:20:06 PDT
> A small scale would work. You could also use scale(x, y) or scaleX(x) in both keyframes.

Yes, but the problem is that CSS minifiers convert "scaleX(1)" into "scale(1)" and "scale(0, 1)" into "scaleX(0)", which prevents using both "scale" or both "scaleX" in this case if you're using a CSS minifier. See this code for example: https://github.com/cssnano/cssnano/blob/04bd16e9de5e2d4409fef74034edb7534cd9e457/packages/postcss-reduce-transforms/src/index.js#L93-L123.

I believe that this CSS minification transformation is valid since these I'd expect for these forms to generate the equivalent matrix (i.e. "scaleX(0) == scale(0, 1)" and "scaleX(1) == scale(1, 1)"). Please let me know if that's not a valid CSS minification transformation and I can file an issue with the relevant CSS minifiers.
Comment 11 Antoine Quint 2023-05-11 08:01:50 PDT
This is working fine in Safari 16.