When applying a `perspective-origin` to an element, where at least one of its components is a CSS custom property, and animating the composed value with the custom property in the keyframes the animations does not apply. This happens where all keyframes are custom properties, and with mixed. Found one exception where the start is custom and the end is not or vice versa, but it might have some logical explanation. This fails: ``` @keyframes perspective-var { from { perspective-origin: 50% var(--perspective-y-start); } to { perspective-origin: 50% var(--perspective-y-end); } } ``` This also fails: ``` @keyframes perspective-mixed { 0% { perspective-origin: 50% var(--perspective-y-start); } 50% { perspective-origin: 50% 50%; } 100% { perspective-origin: 50% var(--perspective-y-end); } } ``` But This works: ``` @keyframes perspective-mixed { from { perspective-origin: 50% 0; } to { perspective-origin: 50% var(--perspective-y-end); } } ``` Fails on Safari Desktop and iOS Works on Chrome and Firefox On Firefox animating perspective-origin does not work as expected without will-change: transform, with it it works like in Chrome.
Created attachment 471788 [details] Demo showing the bug
It seems that the custom property doesn't get resolved correctly inside the @keyframes rule and the values keeps the initial 50% value. If we change the x values inline the animation still plays on the x axis, but the y axis is still failing in the same way - every custom property stays as initial.
The `perspective-origin` is implemented as a shorthand for the `perspective-origin-x` and `perspective-origin-y` properties. While this is not standard, this is how things stand now. Interestingly, this does not happen when the longhand `perspective-origin-y` is animated. This could be a problem with shorthands in general, or specifically with this one.
This is purely a CSS issue, not related to animations per se. This is due to this code in `consumePositionCoordinates()`: auto value2 = consumePositionComponent(range, parserMode, unitless, negativePercentagePolicy); if (!value2) return positionFromOneValue(*value1); So if we fail to parse a second value for `perspective-origin`, we treat the value as a single value property instead of determining we failed to parse that second value because it's a variable. If we returned `nullptr` here instead, the caller `CSSPropertyParser::consumePerspectiveOrigin()` would return `false` which would propagate that return value up to `CSSPropertyParser::parseValueStart()` which would do the right thing per this comment: // Variable references will fail to parse here and will fall out to the variable ref parser below. I'll note that `consumePositionCoordinates()` has this FIXME, although I'm not sure whether it is relevant: // FIXME: This may consume from the range upon failure. The background // shorthand works around it, but we should just fix it here.
Putting the `var()` as the first value processes things correctly since `consumePositionCoordinates()` return `nullptr` in that case, evidence that if we returned `nullptr` when the second value failed to parse we'd have the expected behavior.
Created attachment 471814 [details] Reduction
<rdar://problem/131288246>