Open Bug 1984925 Opened 7 months ago Updated 5 months ago

opacity clamping and additive animation

Categories

(Core :: CSS Transitions and Animations, defect, P3)

Firefox 144
defect

Tracking

()

ASSIGNED

People

(Reporter: kvndy, Assigned: kvndy)

Details

Attachments

(4 files, 4 obsolete files)

Steps to reproduce:

Firefox Nightly 144.0a1 (2025-08-24)

Toggle manually-constructed additive Web-Animation of opacity from 1 to 0 on top of a newly-changed specified value of 0, run to completion. Item fades out.

Toggle manually-constructed additive Web-Animation of opacity from -1 to 0 on top of a newly-changed specified value of 1, run to completion. Item should fade in but doesn't. It instantly changes to 1, and an opacity animation runs from 0 to 0

Actual results:

Opacity values are clamped between 0 and 1

Expected results:

Opacity values should not be clamped between 0 and 1. There is no useful form of additive animation that disallows negative values.

As this related bug
https://bugzilla.mozilla.org/show_bug.cgi?id=662157#c4
points out, the SVG spec specifically defers clamping, here:
https://www.w3.org/TR/SVG11/implnote.html#RangeClamping
There is no such accommodation made for CSS. Firefox behavior has changed since this comment:
https://github.com/w3c/csswg-drafts/issues/3340#issuecomment-441488781

Chromium behavior is as expected, an additive opacity animation runs from -1 to 0.

This needs to be fixed at the spec level, but it would be wonderful if Firefox would exhibit the more useful behavior of deferred clamping until that time.

It also needs to be pointed out that negative width and height values are disallowed on Keyframe construction in both Chromium and Firefox. (This also needs to be changed at the spec level.) Firefox console.warns of this in KeyframeUtils.cpp MakePropertyValuePair, but the same does not happen for opacity which fails silently.

Attached file opacity-2025-bug.html

Toggle on mousedown, twice.

I’ve filed an issue with the W3C. Please support specifying deferred clamping for Keyframe values.
https://github.com/w3c/csswg-drafts/issues/12648

The Bugbug bot thinks this bug should belong to the 'Core::Layout' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → Layout
Product: Firefox → Core

This is not specific to opacity in any way tho, right? All values clamp the same (e.g. all non-negative values clamp similarly to opacity)

Severity: -- → S3
Component: Layout → CSS Transitions and Animations
Priority: -- → P3

Don’t know the answer to your question but can investigate. I’ve narrowed it down some but still can’t figure it out. Maybe there’s an outer to_computed_value getting called which is clamping?

Logs:

glue.rs Servo_GetComputedKeyframeValues
animated_properties.mako.rs AnimationValue from_declaration
animated_properties.mako.rs AnimationValue from_declaration not boxed value pre:Opacity(Number { value: -1.0, calc_clamping_mode: None });
specified/mod.rs Number get result:-1.0;
specified/mod.rs to_computed_value for Number result:-1.0;
animated_properties.mako.rs AnimationValue from_declaration not boxed value post:0.0;

Code (animated_properties.mako.rs):

% if boxed:
let value = (**value).to_computed_value(context);
% else:
println!("animated_properties.mako.rs AnimationValue from_declaration not boxed value pre:{:?};",value);
let value = value.to_computed_value(context);
println!("animated_properties.mako.rs AnimationValue from_declaration not boxed value post:{:?};",value);
% endif

I will continue to investigate but will have to pause for a bit. If the solution is obvious to you or anyone else by all means please land a patch.

I don’t have breakpoints enabled but the first thing I will try is to get a stack trace.

On first mousedown:
opacity from 1 to 0, converted to relative 1-0 is from 1 to 0, animates
lineHeight from 2.4 to 1.2, converted to relative 2.4-1.2 is from 1.2 to 0, animates
width & height from 200px to 100px, converted to relative 200-100 is from 100px to 0, animates

On second mousedown:
opacity from 0 to 1, converted to relative 0-1 is from -1 to 0, fails
lineHeight from 1.2 to 2.4, converted to relative 1.2-2.4 is from -1.2 to 0, fails
width & height from 100px to 200px, converted to relative 100-200 is from -100px to 0, fails

Console warns:
Keyframe property value “-100px” is invalid according to the syntax for “width”.
Keyframe property value “-100px” is invalid according to the syntax for “height”.
Keyframe property value “-1.2” is invalid according to the syntax for “line-height”.

No console warning for opacity. In Chromium opacity animates on both mousedown events, and it gives the same three console warnings.

Finally got a stack trace. My worst fears have been realized. The only useful form of additive animation has been arbitrarily restricted by under-specification. I’ve tried to convince the W3C to allow these features for more than a dozen years. Someone else will have to acknowledge the usefulness and request negative values for keyframes. Code from specified/mod.rs:

impl ToComputedValue for Opacity {
    type ComputedValue = CSSFloat;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> CSSFloat {
        let value = self.0.to_computed_value(context);
        if context.for_smil_animation {
            // SMIL expects to be able to interpolate between out-of-range
            // opacity values.
            value
        } else {
            value.min(1.0).max(0.0)
        }
    }

    #[inline]
    fn from_computed_value(computed: &CSSFloat) -> Self {
        Opacity(Number::from_computed_value(computed))
    }
}

https://github.com/w3c/csswg-drafts/issues/12645

Tab Atkins responded to https://github.com/w3c/csswg-drafts/issues/12648
I couldn’t help but bloviate, but his response may impact any decision on accepting this patch

This is the corrected link to his second response:
https://github.com/w3c/csswg-drafts/issues/12648#issuecomment-3234345013

Attachment #9510215 - Attachment description: WIP: Bug 1984925 - [animation] Remove opacity clamping → Bug 1984925 - [animation] Remove opacity clamping

No changes planned, despite erroneous tag

Attachment #9510215 - Attachment is obsolete: true

Chromium behavior differs from Firefox 142 and Safari. I don’t understand when animation clamping is supposed to occur. I also don’t understand why opacity behavior differs in allowing out-of-bounds values for keyframes and element.style when other properties such as height and lineHeight do not. Closed as invalid until I do understand.

Status: UNCONFIRMED → RESOLVED
Closed: 6 months ago
Resolution: --- → INVALID

Reopened to submit a second patch. I'm still unsure if opacity clamping is a choice or not, if this is a feature or a bug. I believe type <number> and <percentage> should be able to interpolate out-of-range values for opacity, regardless if it’s SMIL or not.

Status: RESOLVED → UNCONFIRMED
Resolution: INVALID → ---
Assignee: nobody → kvndy
Attachment #9516759 - Attachment description: Bug 1984925 - [animation] Update comment to remove SMIL specific language → Bug 1984925 - [animation] Remove clamping for opacity interpolation with updated comment
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

Sorry, I don't know how to add to an existing patch. I attempted git merge --squash which is obviously wrong

Just git commit --amend, then moz-phab again?

(In reply to Emilio Cobos Álvarez [:emilio] from comment #20)

Just git commit --amend, then moz-phab again?

I tried that but I guess I was in the wrong branch, one that I made to test against the mochikit tests, so it failed. I can try again.

The reason why I keep submitting new patches is I used the command git commit --amend -m "Bug 1984925 - [animation] out-of-bounds opacity keyframes and wpt"

I didn't realize adding a commit message is what creates a new patch. Next time I will use the command git commit --amend only.

WIP because the test needs a spec reference.

Attachment #9516759 - Attachment is obsolete: true
Attachment #9516756 - Attachment is obsolete: true

Attempting to download D266702 which is the tentatively accepted patch and continue from there

git commit --amend without the additional -m "commit message" forces me to give a commit message anyway. I'm afraid moz-phab after that will create another new patch. Apologies in advance for my incompetence

Attachment #9517307 - Attachment is obsolete: true

From CSS-Values-4 Section 16. Serializing “Opacity values outside the range [0,1] are preserved, without clamping, in the serialized specified value.”

From Web-Animations-1 5.3.2. Computing property values “resolve value according to the Computed Value line of the property’s definition table”.

From Web-Animations-1 5.3.3. Calculating computed keyframes “Before calculating the effect value of a keyframe effect, the property values on its keyframes are computed” and “The result of resolving these values is a set of computed keyframes.”

The later subsection states “Computed keyframes are produced using the following procedure.” and “2. For each property specified in keyframe: Compute a property value using the value specified on keyframe as the value”.

From Web-Animations-1 5.3.4. The effect value of a keyframe effect “5. Define the neutral value for composition as a value which, when combined with an underlying value using the add composite operation, produces the underlying value.” and “6. Let property-specific keyframes be the result of getting the set of computed keyframes for this keyframe effect.”

Later it states “If keyframe has a composite operation that is not replace” and “2. Let value to combine be the property value of target property specified on keyframe.”

I’m having difficulty finding a generous interpretation that would allow deferred opacity clamping. If the computed value is clamped, the set of computed keyframes is clamped.

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: