Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-cascade] Additive CSS #1594

Open
birtles opened this issue Jul 12, 2017 · 31 comments
Open

[css-cascade] Additive CSS #1594

birtles opened this issue Jul 12, 2017 · 31 comments

Comments

@birtles
Copy link
Contributor

birtles commented Jul 12, 2017

Additive CSS

The following material has been prepared for discussion at the August 2017 CSS F2F in Paris, France.

Outline

The goal of this discussion is to:

  1. Explain additive animation and why it was added to Web Animations
  2. Propose a general-purpose approach to combining CSS property values that is useful in static contexts too
  3. Decide if it is suitable for to ship additive animation in Web Animations and CSS Animations 2 knowing that an overlapping general-purpose feature may arrive in the near future

Part 1: Additive animation

This section describes what additive animation is, how it works, and why it is part of Web Animations.

What is additive animation?

Typically when two independent animations target the same property on the same element, one animation will clobber the other.

This can be either because of the nature of the CSS cascade, e.g.

.spin {
  animation: spin 2s infinite;
}
.swell {
  animation: swell 1s alternate 2;
}
@keyframes spin  { to { transform: rotate(1turn) } }
@keyframes swell { to { transform: scale(2) } }

(Demo)

Or simply because only one animation is ever applied to a given property, e.g.

div {
  animation: spin 2s infinite, swell 1s alternate 2;
}
@keyframes spin  {
  from { transform: rotate(0turn) }
  to { transform: rotate(1turn) }
}
@keyframes swell {
  from { transform: scale(1) }
  to { transform: scale(2) }
}

(Demo)

Additive animation, however, allows the two independent animations to be combined together.

For example, using the Web Animations API we can express the above as:

elem.animate({ transform: 'rotate(1turn)' },
             { duration: 2000, iterations: Infinity });
elem.animate({ transform: 'scale(2)', composite: 'add' },
             { duration: 1000, iterations: 2, direction: 'alternate' });

(Demo - requires Firefox Nightly or Chrome Canary)

CSS Animations 2 proposes to expose this using the animation-composite property.

e.g.

div {
  animation: spin 2s infinite, swell 1s alternate 2;
}
@keyframes spin  { to { transform: rotate(1turn) } }
@keyframes swell { to { transform: scale(2); animation-composite: add } }

Note that this only solves the second of the two problem cases above. To solve the case where the CSS cascade results in only one animation appearing in the computed value of animation-name we need the more general additive approach described in part 2 of this proposal.

How does it work?

Firstly, animations that target the same property are arranged in order. This order is important because:

  • for some types of properties, the addition operation might not be commutative (e.g. addition of transform lists involves concatenating lists which is equivalent to matrix multiplication which is not commutative), and
  • if animation A is additive, but animation B is not, what should be the result? The answer is it depends on which is higher in the stack.

Web animations defines this as an effect stack. For animations created using the API the order is roughly equal to the order in which the animations are generated but for CSS animations, it is based on the order of the animations listed in the animation-name property.

At the bottom of the stack is the unanimated value, called the underlying value. It is possible for a single animation to be combined with the unanimated value using this same mechanism, e.g.

elem.style.transform = 'translate(100px)';
// The following animation represents an offset from the initial translation
elem.animate({ transform: 'translate(100px, 100px)', composite: 'add' },
             { duration: 1000, iterations: 2, direction: 'alternate' });

(Demo - requires Firefox Nightly or Chrome Canary)

The specific procedures for adding each property needs to be defined just as we define the specific procedures for interpolating each property. The intention is that both the interpolation procedures and addition procedures for common types will be defined in CSS Values and Units and then specifications
that define new property types can defined further procedures.

One technical detail is that in some cases there are multiple notions of addition. For example, for a filter list we can define blur(2px) + blur(5px) as either blur(2px) blur(5px) or blur(7px).

As it turns out SVG uses both these modes. When two independent animations are added together it does list concatenation, i.e. blur(2px) blur(5px) from above. When an animation is defined to build on itself on each iteration it adds the function components, i.e. blur(7px) from above.

(Technically SVG only makes this distinction for transform animations and it doesn't actually do list concatenation, but matrix post-multiplication which is functionally equivalent.)

Web Animations calls these two modes add and accumulate respectively, and collectively calls these composite operations.

Why does Web Animations have additive animation?

The original brief for Web Animations was that it should represent the common base for CSS Animations, CSS Transitions, and SMIL/SVG animation. SMIL/SVG animation has additive animation so therefore Web Animations has additive animation.

Is it needed?

The most common usage of additive animation is for combining transform animations. A number of these use cases can be addressed using the individual transform properties from CSS Transforms 2. However, this does not cover all use cases such as:

  • animating translateX and translateY independently (as required by libraries such as GreenSock Animation)
  • animating the same transform component independently (e.g. the translate example above)
  • animating other properties such as filter (e.g. animating the blur on an element that already has a sepia effect applied), or even opacity (e.g. animating the opacity to +50% of its current value).

The other alternative to additive animation is to simply add more and more <div> elements to represent each possible independently timed animation component. This approach does not scale well to dynamic animations where a potentially unbounded number of animations might be generated dynamically and having to adding elements for this is, in this author's opinion, one of the more frustrating parts of authoring CSS at the moment.

Part 2: Static CSS and addition

As it turns out, the notion of combining CSS property values is useful beyond just animations.

Use cases

e.g. truly generic classes

.nostalgic {
  filter: sepia(60%) !add;
}
.blurry {
  filter: blur(5px) !add;
}
<div class="nostalgic"></div>
<div class="blurry"></div>
<div class="nostalgic blurry"></div> <!-- No problem -->

e.g. modifying underlying values

.translucent {
  opacity: -50% !add;
}

e.g. combining animations defined by different rules that target the same property

.spin {
  animation: spin 2s infinite !add;
  animation-composite: add;
}
.swell {
  animation: swell 1s alternate 2 !add;
  animation-composite: add;
}

e.g. overriding certain list items by using the cascade order

button {
  filter: blur(2px);
}
button:focus {
  /* This will override the blur() item but not the grayscale() due to cascade order */
  filter: blur(0px);
}
button:disabled {
  filter: grayscale(50%) !add;
}

Proposal: !add annotation

I believe both David Baron and Tab Atkins have at some point considered the idea of an !add annotation for this.

For static CSS the CSS cascade (that is selector specificity etc.) provides the ordering for composition. The absence of an !add annotation on a declaration indicates that any lower priority declarations are ignored as they are today (even if those lower priority declarations have !add annotations).

Issues that need consideration

Some issues that would need to be considered as part of such a proposal:

  • Is !add enough? Do we need both the add and accumulate modes described above?
  • Are there other modes we need?
    • e.g. multiplication to produce 75% of the current opacity value. Or should that be achieved by extending calc() to be able to work with the underlying value?
  • Can we relax the range checking used when an !add annotation is in place so that is is possible to write opacity: -50% !add?
    (We already do this internally for SMIL and I suppose we want to do this for animation-composite anyway.)

Part 3: Shipping additive animations

The purpose of raising this proposal is to consider the implications of shipping additive animations now and later introducing a more generalized mechanism.

How would the additive features specified in part 1 change if we also eventually do part 2?

Example 1: Setting the composite mode in a single keyframe

// Current API (part 1)
elem.animate([ { transform: 'translate(100px)' },
               { transform: 'translate(200px)', composite: 'add' ], 2000);

// Possible future API (part 2)
elem.animate([ { transform: 'translate(100px)' },
               { transform: 'translate(200px) !add' ], 2000);

// What if both are combined? (part 1 & part 2)
elem.animate([ { transform: 'translate(100px)' },
               { transform: 'translate(200px) !add',
                 opacity: 0.5,
                 composite: 'accumulate' ],
             2000);
// Presumably we would define a rule that says that if we have an !add
// annotation then that overrides any composite member such that in the above
// 'transform' uses the 'add' composite mode, and 'opacity' uses the
// 'accumulate' composite mode.
// As such, I think we can harmonize these two approaches.

Example 2: Setting the composite mode across all keyframes

// Current API (part 1)
elem.animate({ transform: [ 'translate(100px)', 'translate(200px)' ] },
             { composite: 'add', duration: 2000 });

// Possible future API (part 2)
elem.animate({ transform: [ 'translate(100px) !add',
                            'translate(200px) !add' ] },
             2000);

// What if both are combined? (part 1 & part 2)
elem.animate({ transform: [ 'translate(100px)', 'translate(200px) !add' ] },
             { composite: 'accumulate', duration: 2000 });
// Again, presumably we say that !add overrides any composite member such
// that the first value uses 'accumulate' and the second value uses 'add'.
// As such, there does not appear to be any conflict here.

I think these same principles could equally apply to the CSS syntax if we choose to ship that too. For example, taking Example 1 and translating it into the proposed CSS syntax from CSS Animations 2:

/* Current API (part 1) */
div {
  animation: move 2s;
}
@keyframes move {
  from { transform: translate(100px); }
  to   { transform: translate(200px); animation-composite: add; }
}

/* Possible future API (part 2) */
div {
  animation: move 2s;
}
@keyframes move {
  from { transform: translate(100px) }
  to   { transform: translate(200px) !add }
}

/* What if both are combined? (part 1 & part 2) */
div {
  animation: move 2s;
}
@keyframes move {
  from { transform: translate(100px) }
  to   { transform: translate(200px) !add;
         opacity: 0.5;
         animation-composite: accumulate }
}
/* As above, the 'transform' value would use 'add' while the 'opacity' value
   would use accumulate. */

It seems to me that there is a relatively straightforward path for harmonizing the approach in part 1 if we also do part 2 and that, in fact, the features introduced in part 1 might be useful in combination with part 2 as a way of bulk-setting the composite operation.

@FremyCompany
Copy link
Contributor

Still relevant on the topic:
https://lists.w3.org/Archives/Public/www-style/2013Apr/0711.html

I have some more thoughts that are not reflected in this 4 years old thread, most notably the role custom properties could have here to improve DRY, but I would happy to discuss this further at the F2F.

@jackdoyle
Copy link

I wish I had more time to dig into this, but I wanted to chime in briefly...

I fear that this feature won't really address the pain points that animators face in their day-to-day work. Here are 2 quick demos of where I think it might miss the mark:

  1. https://codepen.io/GreenSock/pen/37ab2315a5599df13a172edeeffc53ee?editors=0010
  2. https://codepen.io/GreenSock/pen/20ad5e2cc4c7f0e02e471d70927e73a6?editors=0010

(Read the comments in the JS code)

I'd LOVE to be able to tap into these technologies under the hood in GSAP, but from what I understand, it doesn't appear as though this feature will deliver what we really need in order to do that. Layering things on top of each other might help in some scenarios, but there are quite a few (as demonstrated above) that still seem to be impossible/cumbersome. Or perhaps I'm just missing something(?) Please forgive me if I botched something in the implementation.

@FremyCompany
Copy link
Contributor

FremyCompany commented Jul 25, 2017

@jackdoyle Thanks for the use cases, they seem relevant.

Here's a similar thing written using the solution I proposed:

:root {
  --GSAP: 1000;
  --GSAP_ROTATE: var(--GSAP) 0;
  --GSAP_SCALE: var(--GSAP) 1;
  --GSAP_TRANSLATE: var(--GSAP) 2;
}
#WebAnimation1 {
  transition: transform[] 1s linear;
}
#WebAnimation1.State1{
  transform[ GSAP_ROTATE 0 ]: rotate(180deg)
}
#WebAnimation1.State2{
  transform[ GSAP_TRANSLATE 0 ]: translateX(200px);
}
#WebAnimation1.State3{
  transform[ GSAP_ROTATE 1 ]: rotate(-180deg);
}

Now you can add classes State1, State2 and State3 in whichever order you want and get a deterministic transition. The way this works is that the subtransforms will always be sorted by number sequence, in descending order. That means "1000 2 0" (GSAP_TRANSLATE 0) will always be first, "1000 0 1" (GSAP_ROTATE 1) will be second and "1000 0 0" (GSAP_ROTATE 0) will always be third.


You can override these values later in your stylesheet if needed by referencing their list-id:

#WebAnimation1.State1b {
  transform[ GSAP_ROTATE 0 ]: rotate(270deg);
}

I also have a proposal to allow you to leave out the last number as a automatically-unique value (you cannot override it later like I did with State1 and State1b, but its relative ordering will be solved automatically using css cascade order)

#WebAnimation2.State1{
  transform[ GSAP_TRANSLATE + ]: translateX(200px);
}
#WebAnimation2.State2{
  transform[ GSAP_TRANSLATE + ]: translateY(100px);
}

Last but not least, this gives a lot of flexibility when you need mixing frameworks: If there is another framework, you can (as an author) decide in which order the operations of these frameworks should take place. For instance, if this framework ("ANIMF") should take place after GSAP you can ensure it uses

--ANIMF: 500

and if you want to run its effects before GSAP

--ANIMF: 1500

but you can also have its effect run during GSAP Scale phase

--ANIMF: var(--GSAP_SCALE) 1000

Since variables cascade you can even decide that element per element by redefining GSAP and ANIMF per element.

If you need some operations of ANIMF to happen before GSAP but others to happen after, you can use

--ANIMF: 500;
--ANIMF_TRANSLATE_EFFECTS: var(--GSAP_TRANSLATE) 1000;

@birtles
Copy link
Contributor Author

birtles commented Jul 25, 2017

@jackdoyle I'm pretty sure you can address that use case when we add the proposed composite order control.

@jackdoyle
Copy link

jackdoyle commented Jul 31, 2017

Sorry about the late response. For some odd reason, I wasn't notified of your replies, nor am I allowed to subscribe to the thread (even now).

@FremyCompany I can't mock up that solution in a codepen because those features aren't implemented in any browser yet, right? I read through your suggestion several times and I couldn't grasp how this would solve the problem I was pointing out. Likely because of my own cognitive deficiencies :)

A demo would be super helpful (though I realize it's probably not possible today).

@birtles I'm in a similar situation. Does the proposed composite order control allow folks to, for example, have 100 different elements flying around and randomly choosing various translateX/translateY/rotate/scale values with completely different timings without a bunch of overhead that tracks the order of previous transforms?

For example, with this additive stuff, after animating to rotate(180deg) how can I get back to 0deg without tracking previous stuff? It seems I'd have to do rotate(-180deg) in order to cancel out the previous rotate(180deg), right?

If there's a translateX(100px) and halfway through that, I need to animate translateY(200px) (without interfering with the translateX()) but previously there had already been a translateY(500px) applied, how can I ensure that it arrives at translateY(200px) instead of translateY(700px)? Would I have to getComputedStyle() and parse through the current values? What if it was part-way through a translateY(500px) animation (thus getComputedStyle() won't return the destination)?

How much work will it be for the average developer to animate to absolute translateX/translateY/scaleX/scaleY/rotate values with completely offset timings from each other (overlaps)? Got a demo of how this will solve that problem? It'd be super cool if you could fork the demos I provided and show me what you mean. Please forgive my slowness.

@birtles
Copy link
Contributor Author

birtles commented Aug 1, 2017

Hopefully I've restored Jack's comment which I accidentally edited. Sorry about that. (And I find it quite odd I can edit someone else's comment!)

@birtles
Copy link
Contributor Author

birtles commented Aug 1, 2017

@birtles I'm in a similar situation. Does the proposed composite order control allow folks to, for example, have 100 different elements flying around and randomly choosing various translateX/translateY/rotate/scale values with completely different timings without a bunch of overhead that tracks the order of previous transforms?

Yes. Obviously if they are different elements, then they have separate composite stacks so there should be no conflict. Within a single element, you can call getAnimations() and iterate over the returned Animations to see what animations it has running (and hence where in the stack you want to add new animations to get the desired result).

For example, with this additive stuff, after animating to rotate(180deg) how can I get back to 0deg without tracking previous stuff? It seems I'd have to do rotate(-180deg) in order to cancel out the previous rotate(180deg), right?

You can animate the rotate property to rotate(0deg) without specifying a from value to get the desired effect.

If there's a translateX(100px) and halfway through that, I need to animate translateY(200px) (without interfering with the translateX()) but previously there had already been a translateY(500px) applied, how can I ensure that it arrives at translateY(200px) instead of translateY(700px)? Would I have to getComputedStyle() and parse through the current values? What if it was part-way through a translateY(500px) animation (thus getComputedStyle() won't return the destination)?

Yes, if you're looking to animate the components independently in a pseudo-additive fashion like that despite the fact that they are expressed by the same CSS property, then, for now, you'll need to go through the result of getAnimations() to find your current target Y. You can annotate the Animation objects with extra metadata (e.g. the target X/Y values) when you generate them to avoid parsing the result of getKeyframes() later.

There's some complexity but I think it's manageable and only present when all of the following are true:

  • You have a pseudo-additive animation, i.e. start point is not fixed and end point is and you have underlying animations on that component that you want to blend with the underlying animations
    • If start and end point are fixed you can cancel the existing animations on the necessary component
    • If start and end are additive, no extra work is necessary
    • If there are no underlying animations on the components, then obviously no extra work is necessary
  • And you are trying to animate components that are represented by the same property
    • For animating rotate etc. you can use the separate property

If we find that this is common enough, then we can split off separate properties for the independent components that need to be animated in this way.

@AmeliaBR
Copy link
Contributor

AmeliaBR commented Aug 2, 2017

(I'm reading through the proposals & comments, so I'll be making a series of comments on separate points. Overall: I really want this feature in CSS!)

As it turns out SVG uses both these modes. When two independent animations are added together it does list concatenation, i.e. blur(2px) blur(5px) from above. When an animation is defined to build on itself on each iteration it adds the function components, i.e. blur(7px) from above.

(Technically SVG only makes this distinction for transform animations and it doesn't actually do list concatenation, but matrix post-multiplication which is functionally equivalent.)

This is a very misleading summary.

SVG/SMIL has two types of additive animations, yes: multiple independent animations adding together ("additive" animations) versus a single animation that increases its effect on each repeat ("accumulate" animations).

Only certain data types are additive (number, length, angle, color, x-y pairs, and transformations). However, they normally behave identically as far as how the addition is calculated, regardless whether it is addition from separate animations or from repeats.

The only time there is a difference is for <animateTransform>. And that has less to do with the non-commutative nature of transforms, and more to do with the structure of the <animateTransform>: the transform type (e.g., translate versus rotate) is defined separately from the values (parameters, i.e., the distance or the angle). Accumulation applies to the values, because it's a property of a single animation element, but addition applies to the complete effect.

Example of accumulate and additive animations, for <animate> and <animateTransform>

Presumably, if you used <animate> to animate a transformation by directly setting the transformation string as the value (as proposed for Level 2 SVG animations), then you would always be adding the complete animation, even for accumulations.


Edited to add:

SVG/SMIL also has an additive animation option that is so super-simple I forgot about it: the by attribute allows you to define a single-keyframe animation as a change relative to the base value on the element.

@AmeliaBR
Copy link
Contributor

AmeliaBR commented Aug 2, 2017

The most common usage of additive animation is for combining transform animations.

Definitely the most common, but here are some others (remembering that we're not only talking about one animation adding to another, but also about animations adding to the base value):

Unlike with transforms, creating an additive effect with nested <div> isn't even possible. (Well, except for maybe using percentages of container width or inherited font-size.) Even for transforms, having to add extra markup for every dimension of the transform is really hacky and ugly and annoying.

@AmeliaBR
Copy link
Contributor

AmeliaBR commented Aug 2, 2017

General thoughts on the !add proposal:

This is one of the most straight-forward proposals I've seen for allowing partial or additive CSS declarations, which is a definite bonus. It fits nicely into CSS syntax (finally something else that uses !qualifier). And it directly builds on CSS cascading.

The main thing that would need to be worked out is the addition rules for different data types. This would be similar to how we have interpolation rules for data types -- and with the similar understanding that some things can't be added and would be replaced instead.

The simplicity could be a limitation when talking about more complex properties. For property values that consist of more than a single value, there usually wouldn't be a single universal way to define how to add two declarations.

For some cases, this issue could probably be avoided if you could build up that complex value from custom properties, and then animate the custom property values in an additive way. This could be a solution to Brian's filter example. Normally, filters could add by adding an extra filter function at the end of the chain. But you could declare a filter: blur(var(--blur-radius)); and then make the --blur-radius declarations additive. (This of course assumes an easy way to declare the data types of custom properties for interpolation and addition. But that's going to happen anyway, right?)

But there are other cases where you want to modify a complex value, which can't easily be deconstructed into custom properties. For example, to insert an image layer into a background-image stack. You need to add a comma token into your custom property value, and the syntax gets very fragile.

For the specific issues Brian highlighted:

Is !add enough? Do we need both the add and accumulate modes described above?

I think accumulate is useful. But it doesn't fit into the !add syntax. Accumulation is a property of the animation repeat cycle, not of an individual declaration. The entire animation would either need to accumulate or not. And whether it accumulates or not would be separate from whether they underlying declarations were additive or not.

(I'm assuming that, for !add in a @keyframes rule set, we are always adding on to the previous value from the cascade, and not on the previous keyframe. So it would be possible to have e.g. a 50% keyframe that was additive (so the first half of the animation is defined as a change from the base value), and then a 100% keyframe that wasn't additive (final value in the animation is the same regardless of the base value).)

If you wanted to support accumulative repeats in addition to an !add syntax, I think the logical way to do so would be via a new animation-* property. Accumulation would be something like alternating repeats or a forwards-fill mode, that is part of the overall timing and repeat structure of the animation, not part of the keyframes.

However, there would still need to be details to figure out:

  • How do you define the amount of change which becomes the amount to add? If we're being consistent with SVG/SMIL (and I think Web Animations), then the additive amount for each keyframe would be measured as the value for that keyframe relative to the from value. However, SVG/SMIL only does it this way if a from is set explicitly, or if the relative change is set explicitly with by. An animation defined with implicit from and explicit to does not accumulate in SVG/SMIL. Which doesn't translate nicely to a CSS model. Also, values and accumulate aren't well defined together in the spec; Firefox and Chrome treat them so that the values automatically become additive, then accumulative in their difference relative to the base value, instead of relative to the starting frame. Basically, it's a huge mess of logical inconsistencies and I'm not sure you'd want to automatically follow along to match.

  • How do accumulative animations interact with reverse-direction or alternating animations? SVG/SMIL does not have these. The definitions in Web Animations do not appear to discuss it.

Are there other modes we need? e.g. multiplication to produce 75% of the current opacity value. Or should that be achieved by extending calc() to be able to work with the underlying value?

Now we're getting into the complicated questions that the simple syntax can't cover. Which other combination options you want would depend on the specifics of each property type. The more complex the property, the more different possible ways you might have for combining values. Think of two text-shadow declarations: do you add them as a list, to create two shadows? Or do you add all the individual terms. So maybe there's a !concatenate list option versus !add? But how does this compare with !multiply for other properties? It quickly becomes a tangle.

A simple syntax like !add needs a simple rule. Just like with the SVG/SMIL additive rules, that would probably mean a very restricted set of additive data types. As mentioned above, more complicated combinations would require CSS variables.

Can we relax the range checking used when an !add annotation is in place so that is is possible to write opacity: -50% !add?
(We already do this internally for SMIL and I suppose we want to do this for animation-composite anyway.)

This would be very desirable. It should be similar to calc() and var() rules for range checking, where you only clamp when you can convert it to a meaningful final value.

(This is probably it for me tonight. Sorry I didn't get a chance to review @FremyCompany's proposal. I'll be reading over the IRC minutes when I get online tomorrow.)

@birtles
Copy link
Contributor Author

birtles commented Aug 2, 2017

(I'm reading through the proposals & comments, so I'll be making a series of comments on separate points. Overall: I really want this feature in CSS!)

As it turns out SVG uses both these modes. When two independent animations are added together it does list concatenation, i.e. blur(2px) blur(5px) from above. When an animation is defined to build on itself on each iteration it adds the function components, i.e. blur(7px) from above.

(Technically SVG only makes this distinction for transform animations and it doesn't actually do list concatenation, but matrix post-multiplication which is functionally equivalent.)

This is a very misleading summary.

How is that misleading?

Only certain data types are additive (number, length, angle, color, x-y pairs, and transformations). However, they normally behave identically as far as how the addition is calculated, regardless whether it is addition from separate animations or from repeats.

The only time there is a difference is for <animateTransform>.

Isn't that precisely what the summary says?

Presumably, if you used <animate> to animate a transformation by directly setting the transformation string as the value (as proposed for Level 2 SVG animations), then you would always be adding the complete animation, even for accumulations.

I don't know why that is the assumed behavior.

(For what it's worth, as the implementer of SMIL in Gecko, the distinction between these two types of addition was very surprising and was only revealed by a single test case in the SVG 1.1 test suite that failed when I used the same definition of addition across the board. The spec does not make this distinction obvious at all and arguably it only existed in the test writer's interpretation. I don't think anything can be assumed about how it ought to work from the SVG spec. One motivating reason for choosing the component-based addition for accumulation in Web Animations is to avoid creating a very long list for repeating animation.)

@birtles
Copy link
Contributor Author

birtles commented Aug 2, 2017

The main thing that would need to be worked out is the addition rules for different data types. This would be similar to how we have interpolation rules for data types -- and with the similar understanding that some things can't be added and would be replaced instead.

Yes, I am working on adding that to CSS Values & Units since we already use this in Web Animations.

For property values that consist of more than a single value, there usually wouldn't be a single universal way to define how to add two declarations.

I'm optimistic we can solve this (we have most of these complex types adding together in Gecko / Servo / Blink) by doing addition on the components. There are some cases where we fall back to non-additive just like for interpolation there are some cases where we fall back to discrete animation. As with interpolation, I think we can probably fill some of these gaps in the future without breaking content.

(This of course assumes an easy way to declare the data types of custom properties for interpolation and addition. But that's going to happen anyway, right?)

Right.

I think accumulate is useful. But it doesn't fit into the !add syntax. Accumulation is a property of the animation repeat cycle, not of an individual declaration. The entire animation would either need to accumulate or not. And whether it accumulates or not would be separate from whether they underlying declarations were additive or not.

Web Animations lets you use the accumulate behavior (i.e. adding the function parameter values rather than appending to the list) when adding independent animations.

(I'm assuming that, for !add in a @Keyframes rule set, we are always adding on to the previous value from the cascade, and not on the previous keyframe. So it would be possible to have e.g. a 50% keyframe that was additive (so the first half of the animation is defined as a change from the base value), and then a 100% keyframe that wasn't additive (final value in the animation is the same regardless of the base value).)

Right. This per-keyframe additive behavior is already specified in Web Animations and implemented in Firefox and Chrome (but not shipping yet).

How do you define the amount of change which becomes the amount to add?

I assume this is referring to how to apply accumulate behavior? If so, then this proposal is simply putting forward accumulate behavior as another type of addition independent of repetition.

Think of two text-shadow declarations: do you add them as a list, to create two shadows? Or do you add all the individual terms. So maybe there's a !concatenate list option versus !add?

This is precisely the difference between !add and !accumulate in this proposal.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed Additive CSS, and agreed to the following resolutions:

  • RESOLVED: No concerns wrt shipping Web Animations
  • RESOLVED: CSSWG is interested in working on additive cascade proposals.
The full IRC log of that discussion <dino> Topic: Additive CSS
<fantasai> ScribeNick: fantasai
<birtles> https://github.com//issues/1594
<fantasai> birtles: This issue covers everything I want to say
<fantasai> birtles: Want to introduce idea of addititve CSS
<fantasai> birtles: Figure out if we want to do this soon or not
<fantasai> birtles: We have something like this in Animations, wanted to see if we should pursue in static CSS also
<fantasai> birtles: Start off with additive animation
<fantasai> birtles: then static additive CSS
<fantasai> birtles: Normally when you have animations in CSS
<fantasai> birtles: basically you can only ever have one animation affecting the same property at the same time
<fantasai> birtles: This demo here has animations spin and swell
<fantasai> birtles: when I apply the both
<fantasai> birtles: you can see only the swell animation is taking effect
<birtles> http://slides.com/birtles/browser-animation-2017-2#/3/11
<fantasai> birtles: This can be due to the nature of the cascade, only one animation declaration will win
<fantasai> birtles: But even when you specify two animations in a isngle declarations
<fantasai> birtles: then the declarations in the keyframes could override each other
<fantasai> birtles: Problem in animations
<fantasai> birtles: There's a means for solving that which comes from SMIL
<fantasai> birtles: and is in Web Animations
<fantasai> birtles: where you define the properties as being addititve
<fantasai> birtles shows demo with Web Animations that shows spinning and growing at the same time
<fantasai> glazou: Do you put composite=add on both of them?
<fantasai> birtles: only the top one
<fantasai> birtles: If you declare an animation as additive, then it adds to underlying unanimated style, or adds to underlying animations
<fantasai> birtles: wherever in the chain doesn't say add, clobbers earlier declarations
<fantasai> glazou: animate(composite=add) that's ok
<fantasai> glazou: but for properties...
<fantasai> glazou: parsing is not done yet?
<fantasai> birtles: That feature is defined in Web Animations, implemented (but not shipping) in Chrome & Firefox
<fantasai> birtles: Property for CSS Animations 2 to give same feature
<fantasai> birtles: How does it actually work?
<fantasai> birtles: different animations types, define what it means to add animations
<fantasai> birtles: then need to define order, since not operations are commutative
<fantasai> birtles: and also need to know which to exclude
<fantasai> birtles: Web Animations there's a defined order
<fantasai> birtles: but for CSS Animations the order of the animations comes from the 'animation-name' property
<fantasai> Florian: If you're trying to add things that are not additive, what happens?
<fantasai> birtles: Just as with interpolation, there are some types are not interpolable and we fall back to discrete
<fantasai> birtles: for types we can't add, we fall back to replace
<fantasai> Florian: Is this stable? Are there things that we could add in the future but can't right now?
<fantasai> Florian: e.g. auto + <length>
<fantasai> birtles: Yes, and there is this problem also with interpolation
<fantasai> birtles: Our hope with interpolation is that filling in gaps won't break content, we don't know but we hope
<fantasai> birtles: so in this case also hoping
<fantasai> fantasai: I think switching from replace to add is more likely to break that switching from discrete to gradual interpolation
<fantasai> Florian: in JS, could throw an exception, but not in CSS
<fantasai> birtles: Amelia had a concern about defining addition
<fantasai> birtles: THere are different ways to add things
<fantasai> birtles: e.g. blur(2px) + blur(5px)
<fantasai> birtles: is it blur(2px) blur(5px) (sequence) or blur(7px)
<fantasai> birtles: Web Animations has two different modes of addition
<fantasai> birtles: add and accumulate (corresponding to above, respectively)
<fantasai> birtles: For most types they're the same operation, but for list types are different
<fantasai> birtles: I think Amelia was concerned because accumulation in SMIL is used in repetition
<fantasai> birtles: but here it's just another type of add
<fantasai> melanierichards: You mentioned that if you get to something that doesn't add, does it replace all the values added up below it
<fantasai> melanierichards: [gives an example]
<fantasai> birtles: overrides sum up to that point
<fantasai> birtles: Add means "add myself to what's beneath me"
<fantasai> birtles: You have a stack with A at the top and D at the bottom
<fantasai> birtles: A and B are additive, C and D are not
<fantasai> [discussion of what ordering means]
<fantasai> dino: Don't say it's a stack. Everything is a list. THe last one wins
<dbaron> so this would be like animation-name: D, C, B, A.
<fantasai> birtles: The result of that would be A + B+ C
<fantasai> dino: And if A wasn't additive, then result would be A
<fantasai> birtles: And if all additive, then you get A+ B+ C+ D+ underlying style
<fantasai> Florian: When you're switching into another style, add to the top of the stack, which can override those beneath it
<fantasai> birtles: This proposal suggesting that if we expose in regular standard CSS, then the ordering comes from the Cascade
<fantasai> birtles: if you have something of higher specificity, takes precedence
<fantasai> dbaron: There are two different mechanisms proposed here
<fantasai> birtles: Why have this to begin with?
<dbaron> github: https://github.com//issues/1594
<fantasai> birtles: The most common use of additive animation is for combining transform animations
<glazou> q+ what about !important in such an addition
<fantasai> birtles: to some degree can already do that with separate transform property in L2, where we have translate+rotate+scale properties
<fantasai> birtles: Sovles a number of use cases, but doesn't solve all of them
<glazou> q+ to ask about !important in such an addition
<fantasai> birtles: If you wanted independent animations of the same operation, doesn't allow you to add those
<fantasai> birtles: Doesn't help with other things like filters
<fantasai> birtles: Any cases where separate transformatoins are not split out enough in our syntax
<fantasai> birtles: The other way to solve these problems currently is to add another <div> wrapper element. Not very appealing when you have a lot of transforms
<fantasai> birtles: So that's a summary of additive animation
<fantasai> birtles: It's in Web Animations spec atm because the scope of Web Amnimations was to cover all the features of CSS Animations, ? Animations, and SMIL
<fantasai> birtles: And SMIL already has this feature
<fantasai> birtles: My next proposal is a straw man for providing the same functionality in static contexts for regular CSS, not just animations
<fantasai> birtles: Here's an example of combining filters
<fantasai> birtles shows off examples in the issue
<fantasai> birtles: Some potential use cases
<fantasai> birtles: proposal is to add a !add to the end of the declaration
<glazou> q+ to mention variables for the first Part 2 use case 'filter'
<fantasai> birtles: ordering is given by the cascade
<fremy> q+
<dbaron> q+ to talk about !add as only being for list-valued properties
<fantasai> birtles: Not a fully fleshed-out proposal, just a rough idea
<Rossen> a?
<Rossen> q?
<fantasai> birtles: Last question is whether to ship additive animations as specced or wait to line them up with these other features
<Zakim> glazou, you wanted to ask about !important in such an addition and to mention variables for the first Part 2 use case 'filter'
<Myles_> q+
<fantasai> glazou: First, about your use case, about the filter property
<fantasai> glazou: This is easily doable with variables
<fantasai> glazou: Not for an unlimited number of classes or inputs, but if you know the scope of the styles you can get fairly doable with variables
<fantasai> glazou: Using !add would allow some cases
<fantasai> glazou: Next, !important, how does that interact with this?
<fantasai> birtles: !important continues to affect where it appears in the cascade
<fantasai> dbaron: I've wanted to have additive cascade for a long time, just didn't have a good syntax for it
<fantasai> dbaron: Normal cascading order is that you have a set of declarations and the last declaration wins
<fantasai> s/set/sorted set/
<fantasai> dbaron: [describes something I missed]
<Florian> q+
<fantasai> glazou: What would happen to APIs that climbe the cascade and find *the* rule that wins?
<fantasai> birtles: which APIs?
<fantasai> glazou: Inspectors in borwsers
<fantasai> birtles: Any Web-facing APIs?
<dbaron> s/[describes something I missed]/The cascade just produces a sorted list of declarations. With !add, instead of taking just the highest one for a property, you'd take all of the highest ones down to the highest that doesn't have !add./
<fantasai> s/APIs/APIs affected/
<fantasai> fremy: there's a depreciated API in Chrome
<fantasai> glazou: So browsers internally will have to update something, so OK but be careful
<fantasai> birtles: inspector also shows which ruels were clobbered
<fantasai> fantasai: So you'd have an ordered set of declarations that win, not just one
<glazou> ack glazou
<Rossen> q?
<fantasai> Florian: If you getComputedStyle or some similar thing that give syou the computed value
<fantasai> Florian: when something has been added
<fantasai> Florian: do you return calc()? what do you do?
<fantasai> birtles: Depends on the case
<fantasai> birtles: similar to interpolation
<fantasai> birtles: e.g. adding % and length, get a calc
<fantasai> dbaron: A lot of these are lists, which would append items
<fantasai> astearns: Do you get !add in the computed value?
<fantasai> No
<fantasai> dbaron: There's always a lowest value
<fantasai> astearns: might have !add
<Rossen> q?
<fantasai> dbaron: then there's nothing to add to
<Rossen> ack fremy
<fantasai> fremy: There are two different proposal in this proposal
<fantasai> fremy: There's list-accumulating behavior, which I and Tab have been discussing
<fantasai> fremy: This is reasonable to add to browsers, and solves most of the use cases
<fantasai> fremy: There's another proposal for things like auto + 50px
<fantasai> fremy: That's a different propsoal
<fantasai> fremy: I think thses are two very different proposals
<Florian> q+
<fantasai> dbaron: This is one of the things I wanted to raise as well
<fantasai> fremy: At least in this WG there are ppl working on list things, I thin kit's easier to get there
<fantasai> fremy: trying to transform all properties into this kind of list thing isn't something that we are ready to do yet
<fantasai> astearns: Showing that we might wnat to go there is useful though
<fantasai> birtles: This is implemented in Gecko, Blink, Servo, for more than just these types
<fantasai> birtles: The ability to add different CSS types
<fantasai> TabAtkins: in Web Animations
<fantasai> fremy: I would be curious to see test cases
<fantasai> fremy: Still, should be mentioned they are two different things
<fantasai> fremy: Another thing I dislike !add is that it's mpossible to redefine a value
<fantasai> fremy: e.g. you have filter: blur(val1) ??(val2)
<fantasai> fremy: You can add to the list, but you can't alter preivious values in the list
<astearns> I thought "filter: blur(val) !important;" would override
<fantasai> Florian: You can always wipe out the entire list
<fantasai> astearns, yes it would
<fantasai> TabAtkins: At that point, you should use variables
<fantasai> TabAtkins: variables allow coordinated substitution
<fantasai> TabAtkins: uncoordinated addition is what we have to solve here
<fantasai> fremy: Other concern is that ordering matters, e.g. transform + rotate and roate + transform are not the same
<fantasai> fremy: it's a problem with web animations atm
<fantasai> fremy: We have this problem with ppl making websites where everybody is screaming the highst z-index
<fantasai> birtles: For Web Animations there's no number
<fantasai> fremy: People wnat to control the order, and !add will follow the cascade order
<fantasai> fremy: I think for lists, there are better ways to approach this problem with more control
<fantasai> fremy: plain !add for list is not good enough
<fantasai> fremy: When you are programming, used to being able to modify values and that's something not there, and I think it's really important
<fantasai> astearns: Seems to me that you could fiddle with selector specificity
<fantasai> fremy: specificity isn't always correlated with priority
<fantasai> dbaron: Animations are one of th emore complicated cases
<fantasai> dbaron: There are a bunch of other list-valued properties where you really want to combine
<fantasai> dbaron: E.g. for counter properties, generally would prefer to have declarations combine, ordering doesn't matter
<fantasai> fantasai: Stuff in text decoration also
<fantasai> TabAtkins: a lot of properties are sets, not lists, order doesn't matter
<fantasai> fremy: My proposal would be ot use an array syntax
<fantasai> fremy: And you could put a number as the arg, or a name
<fantasai> fremy writes some declarations
<fantasai> transform: translate(0px, 0px)
<fantasai> transform[zoom]: scale(1.1);
<fantasai> transfomr[rotate]: rotate(180deg)
<fantasai> transform[hover:100]: scale(1.0) translate(0px, 0px);
<fantasai> transition[]: transfomr[hover:100] 0.5s eas-in
<fantasai> birtles: We already have seaparate properties for rotate and zoom in Animations L2
<fantasai> Rossen: So your feedback is there's no way to have selection and authoring of the list after initial decl
<fantasai> Rossen: anything else?
<fantasai> fremy: Also that cascading order is not always the orde ryou want to ocncatenate
<fantasai> fremy: There's also the issue of coordination between frameworks
<fantasai> fremy: If there's no way to coordinate other thandefining / redefining variables, then they can't coordinate
<fantasai> fremy: You can't solve that problem with variables
<Rossen> q?
<fantasai> dbaron: Few comments
<Rossen> ack dbaron
<Zakim> dbaron, you wanted to talk about !add as only being for list-valued properties
<fantasai> dbaron: One is that there are two separate pieces to birtles' proposal
<fantasai> dbaron: I see these as independent
<fantasai> dbaron: I se reasonable to do one and not the other in either way
<fantasai> dbaron: Either one would get us some good benefits
<astearns> I expect that if you're trying to allow more than one framework to compose animations you're going to have a lot of problems
<fantasai> dbaron: But I think they are two independent things
<fantasai> dbaron: When you start having multiple declaration of animations / ?
<fantasai> dbaron: One is about combining declarations, the other about combining animations
<fantasai> dbaron: I think as far as the cascading pieces, bits about combining declarations
<fantasai> dbaron: I think one of the reasons I prefer something simple here is that I think something like !add or something like that
<fantasai> dbaron: It's simple neough, if we can agree on a syntax, it's feasible to implement within existing engines in a reasonable amount of time
<glazou> q+
<fantasai> dbaron: I think fremy's other proposal has a lot more complexity, and I'd be more hesitant to go down that path
<fantasai> fremy: I'm not arguing for names, I'm arguing for numbers
<fantasai> dbaron: There's a bit more complexity there, although a little... I don't know
<fantasai> dbaron:Other comment
<fantasai> dbaron: Like fremy, I saw this as being only for list-valued propeties
<fantasai> dbaron: I see that SMIL has more general mechanisms, and I have mixed feelings for having those in the same system
<fantasai> dbaron: It's pulling in a big part of SMIL to pull into CSS, to do addition of other than comma-separated lists
<fantasai> dbaron: Other small comment is that in that middle example, I would expect animation-composit itself ot be a list-valued property
<fantasai> dbaron: And therefore I would expect that second lne to be animation-composite: add !add;
<fantasai> dbaron: Because I think you want to add youre animation-composite value to the list, not replace the list
<Rossen> ack Myles_
<fantasai> Myles_: I have a couple points
<fantasai> Myles_: One already made iwhich is 2nd piece of proposal can be used in many places
<fantasai> Myles_: We've gotten lots of request for ppl to turn on various font features
<fantasai> Myles_: and this makes that easier
<dbaron> fantasai: font-variant rather than font-feature-settings
<fantasai> Myles_: in WebKit we have an issue with text-decoration
<dbaron> (but it's worse for font-variation-settings!)
<fantasai> Myles_: If parent says text-decoration: underline and child ses text-decoration: strike-through
<fantasai> Myles_: In both of those examples, the addition occurs down the DOM, not across cascade
<fantasai> dbaron: One of the cases we discussed is what happens if you have adds all the way down
<fantasai> dbaron: If you do adds all the way down on an inherited value
<fantasai> dbaron: you add to the base value, which ofr inherited properties is the inherited value
<Rossen> q?
<dbaron> s/inherited value/inherited property/
<dbaron> (the second last one should be substituted)
<fantasai> Myles_: Other comment was on pulling apart a list and inserting stuff in the middle
<fantasai> Myles_: Issue of different teams trying to coordinate, this is difficult for a company
<fantasai> Myles_: we already have two different ways for differnet areas of a document interact
<fantasai> Myles_: The cascade through specificity, and then inheritance through document tree
<fantasai> Myles_: adding yet another way seems complicated, seems better to re-use existing ones
<fantasai> Myles_: Another comment
<fantasai> Myles_: You had na issue of adding values
<fantasai> Myles_: I fyou have line-height: 50px and line-height: normal, how do you add?
<fantasai> birtles: For some we use calc(), but some things can't add and thos efall back to replace
<fantasai> birtles: In our impl, the function that does interpolation is the same as the one that does addition
<fremy> q+ to reply to Myles with new idea
<fantasai> fantasai: On that topic, if we have only the list-valued addition for now
<fantasai> fantasai: That's avoids the problem of forwards-compat for types that we can't add yet
<fantasai> s/add/add (or interpolate)/
<dbaron> Florian: so if you restricted to list-valued properties, then !add would be a syntax error for other properties?
<dbaron> fantasai, dbaron: yes
<fantasai> fantasai^: I think switching from replace to add is a more likely compat problem than switching interpolation from discrete to gradual in the future
<Rossen> q?
<fantasai> birtles: I feel a bit uncomfortable drawing a line between list types and non list types
<fantasai> birtles: in implementation terms there's no distinction
<fantasai> birtles: For an author that you can add translate(200px) to translate(200px) but can't add margins of 200px, seems awkward
<Rossen> q?
<fantasai> birtles: Also ...
<fantasai> Florian: Less likely that people wil use something that gets thrown out and is a syntax error (though still possible they leave it in the stylesheet, less likely)
<fantasai> Florian: than if it has a particular behvaior (replace) and we wnat ot hcange it later to add
<Rossen> ack Florian
<fantasai> Florian: Not an objection, but a concern about the compat path
<fantasai> Florian: This allows things that were impractical
<fantasai> Florian: but also is same as using calc() in some cases
<fantasai> Florian: I'm a bit worried about cases of e.g. Chrome shipping way earlier than other impls and authors relying on it and it being broken in other impls
<fantasai> fantasai: This is an issue with every major feature we add to CSS, can't decide t ojust not add things
<fantasai> glazou: I'm a bit surprised we're using !add
<Rossen> o+:
<fantasai> glazou: Woudl prefer property+:value
<glazou> no
<fantasai> dbaron: would love property += value, but we're not using = :/
<Rossen> q?
<glazou> would prefer +property
<Florian> +prop: value / prop+: value
<fantasai> fantasai: +property:value ?
<Rossen> ack glazou
<Rossen> ack fremy
<Zakim> fremy, you wanted to reply to Myles with new idea
<fantasai> fremy: I'm OK to not have ability resort stacking order
<fantasai> fremy: But I want ability to give added values different weights
<fantasai> s/weights/timing rates/
<astearns> property: value +add;
<fantasai> fremy: I want to say that "for this part I just added, I want to transition the addition at a different rate"
<fantasai> Myles_: For font-features that has no relevance, so this feature should be scoped appropriately
<fantasai> birtles: I would liked to get a go/no-go for shipping additive animations
<fantasai> birtles: and also to gague interest with regards to refining the static CSS proposal
<fremy> btw glazou dbaron: my syntax proposal is { transform[]: added-value; }
<dbaron> s/gague/gauge/
<fantasai> fantasai: I have no idea about Web Animations, but the additive cascade is something people have wanted to have for a long tme, and it seems like your proposal has a lot of interest from the people here
<fantasai> fantasai: No idea how long it'll take but there's interest :)
<TabAtkins> fremy: For poking at individual entries of a list, we have the proposal for list-indexed sub-properties...
<fremy> astearns: yes; could be a different feature really, just want to make sure we think about a path forward ;)
<fantasai> birtles: We have same compat questions for the animated version of the proposal, shoudl they block us from shipping additive animations or are they less sever in the case of Web Animations?
<TabAtkins> fremy: Just need to add commas to the transform syntax, to let it become a comma-separated list.
<dbaron> fantasai: Was a suggestion to throw exception for things you can't add.
<dbaron> fantasai: We can't do that in CSS syntax.
<fremy> TabAtkins: yes, could work
<dbaron> birtles: probably worse for compat, since first browser to ship will see unhandled exceptions
<dbaron> fantasai: if we can throw exceptions for use cases and remove later, less concerned about compat
<dbaron> q+
<dbaron> fantasai: if chrome ships auto+100px working, it's because they've figured out how to make that work, and everyone else will want to make that work
<fantasai> dbaron: I guess I mostly disagree with fantasai's comment on exceptions
<fantasai> dbaron: I think the two things are mostly indpenednet, and I don't see a problem with animations stuff moving forward if additive cascade doesn't
<fantasai> birtles: What we have in the spec is a definition of addition for each type, that needs to be written before shipping
<fantasai> birtles: pretty simple though
<dbaron> s/fremy:/fremy,/
<dbaron> s/TabAtkins:/TabAtkins,/
<fantasai> birtles: additive animations implementation
<fantasai> birtles: go forward, assumign test suite / compat etc.
<fantasai> RESOLVED: No concerns wrt shipping Web Animations
<fantasai> fantasai: Do we want to conclude something on additive cascade?
<fantasai> RESOLVED: CSSWG is interested in working on additive cascade proposals.
<dbaron> fantasai: can someone draft an ED?
<dbaron> alan: I think it's premature to put in a spec.
<fremy> ScribeNick: fremy

@jonjohnjohnson
Copy link

jonjohnjohnson commented Mar 14, 2018

After reading @AmeliaBR thoughts on !add I'm just gonna throw this out there...

Would a special keyword for the accessing the current value in the cascade, similar to currentColor, be simpler, as well as more flexible, allowing for authors to run their own operations within a calc() function? Possibly better than the unique notations of !add or !multiply?

So instead of this !add notation...

.selector {
  filter: grayscale(50%) !add;
}

How about currentValue allowing for these...

.selector {
  filter: grayscale(calc(currentValue) + 50%);
  filter: grayscale(calc(currentValue) - 25%);
  filter: grayscale(calc(currentValue) / 2);
}

Then people can just run whatever operations the value/type that was already in the cascade? Would currentValue need some sort of fallback if it was previously set to a different type? Like currentValue(0%) having a fallback of 0% ?

@FremyCompany
Copy link
Contributor

FremyCompany commented Mar 15, 2018

@jonjohnjohnson

This looks similar to the cascade proposal [1] [2] which I still support. The exact proposal you made ith currentValue does not seem feasible to implement as you use it because most functions can take more than one parameter, or different parameters depending on what parameters are specified, and you need to support the case where filter has more than one filter specified.

What you could do though is rely on a custom property to do the math, like this:

.selector {
  --grayscale-value: calc(cascadedValue * 50%);
  --grayscale-value: calc(cascadedValue - 25%);
  filter: grayscale(var(--grayscale-value));
  transition: --grayscale-value 0.3s;
}
.selector:hover {
  --grayscale-value: calc(cascadedValue / 2);
}

[1] https://lists.w3.org/Archives/Public/www-style/2013Apr/0711.html (coined cascade)
[2] https://lists.w3.org/Archives/Public/www-style/2014Apr/0197.html (additional use case)

@inoas
Copy link

inoas commented Mar 15, 2018

I like having a way to read the inherited value of the same property for its flexibility as shown above!
Would inherited be a better signifier for the same thing than cascadedValue?

@jonjohnjohnson
Copy link

@FremyCompany I was thinking the currentValue could be retrieved within the context it was used, such as the value of a grayscale function, as well as the whole value of a property. But I definitely understand what you are saying and that my idea is a bit more complex to implement even if it is more open/simple to use for developers not having to know/write extra custom properties.

@jonjohnjohnson
Copy link

jonjohnjohnson commented Apr 12, 2018

Since custom properties cannot be set with references to their own inherited value, except in quite round about ways...

<style>
  body {
    --mag: 0em; /* initial value */
    --plus: 1em;
    --current: var(--mag);
  }
  span {
    --current: calc(var(--mag) + var(--plus));
  }
  div {
    --mag: var(--current);
    property: var(--current);
  }
</style>
<body>
  <div>
    <span>
      <div>
        <span>
          <div>
            <span>
              <div>
              </div>
            </span>
          </div>
        </span>
      </div>
    </span>
  </div>
</body>

I hope whatever solution is found for "additive" does away with this mess, since we are missing things like shorthand assignment operators (+=/-=/*=/etc) offered in javascript.

@jonjohnjohnson
Copy link

I had to write something like this today, overspecifying and repeating myself, explicitly accounting for all possible permutations of combined counter incrementing utilities.

.ut-count-1                       { counter-increment: utCount1; }
.ut-count-2                       { counter-increment: utCount2; }
.ut-count-3                       { counter-increment: utCount3; }
.ut-count-1.ut-count-2            { counter-increment: utCount1 utCount2; }
.ut-count-1.ut-count-3            { counter-increment: utCount1 utCount3; }
.ut-count-2.ut-count-3            { counter-increment: utCount2 utCount3; }
.ut-count-1.ut-count-2.ut-count-3 { counter-increment: utCount1 utCount2 utCount3; }
<li class="ut-count-1"></li>
<li class="ut-count-1 ut-count-2"></li>
<li class="ut-count-1 ut-count-2 ut-count-3"></li>
...

@tabatkins
Copy link
Member

Re: variables, letting var() refer to the inherited value of the variable, rather than the current value, is planned for L2. This is separate from additive stuff, tho.

@nicksherman
Copy link

Related to this conversation is the ability to cascade font-feature-settings, as discussed in #552

@faceless2
Copy link

faceless2 commented Dec 11, 2019

If additive behaviour was defined on a per-property basis it could apply even where lists of items aren't natural - for example:

<style>
.disabled {
    text-decoration: line-through !add;
}
a {
   text-decoration: underline !add;
}
</style>
<a class="disabled">underlined and struck-out</a>

or, slightly more hypothetically:

<style>
p {
    text-spacing: trim-adjacent-to-punctuation; /* a made-up value */
}
:lang(fr) {
   text-spacing: punctuation !add;
}
</style>
/* Remove spacing around the colon AND insert a 1/4 nbsp before */
<p lang="fr">Punctuation : le colon</p>

Various forms of text "fix up" keep being proposed[1][2][3][4][5][6], and additive behaviour seems like a good option for what will inevitably become an increasingly long list.

For font-feature-settings too. Here, I was wondering how you might offer more control over the cascade than just "add to, rather than replace, the previous matched value"

p {
    font-feature-settings: "ss01" !name swash;
}
.fancy {
    font-feature-settings: "ss02" !add; /* gets "ss01", "ss02" */
}
.noswash {
    font-feature-settings: inherit !remove swash; /* gets "ss02" */
}

This would require each declaration has a name field, but beyond that should be no harder than the vanilla "!add" proposal.

[1] https://www.w3.org/TR/css-text-4/#text-spacing-property
[2] https://www.w3.org/Style/2013/paged-media-tasks#text-fix
[3] https://www.princexml.com/doc/css-props/#prop-prince-text-replace
[4] https://www.antennahouse.com/product/ahf66/ahf-ext.html#axf.text-replace
[5] https://specs.rivoal.net/css-custom-tt/
[6] https://books.idea.whatwg.org/#character-substitution
[7] #4246

@LukeTOBrien
Copy link

LukeTOBrien commented Jan 13, 2020

Hello there,

I am just a humble web dev, nothing important, but I stumbled upon your discussion and I woul like to add my views.
It seems to me that what you want is something like (Compound Assignment in C#) Additional Assignment and subtraction assignment.

So instead of:
!add and !remove - In your eg: text-decoration: underline !add
You would have:
+: and -: - So for eg: text-decoration+: underline

I think symbols would also benefit non-English speakers

For animations I have also bee thinking about compounding a CSS var:
So if you want to slide in and animate the colour:

:root {
  --animation: ;
}
.animated {
  animation: var(--animation);
}
.slide-in {
  --animation: slidein 5s;
}
.colour {
  --animation: var(--animation), colour 5s;
}

See my pen - I have commented out the compound var expression so that the pen will work.

Whatcha think? :-)

@warengonzaga
Copy link

I'm gonna read all of this @LukeTOBrien thanks for informing the AnimateCSS community.

@argyleink
Copy link
Contributor

building an animation grudgingly in javascript, just for additive compositing. wish I could use CSS only. here to say I would have reached for it right now if I could 👍🏻

@Kilian
Copy link

Kilian commented May 4, 2021

This would be useful for any CSS property that accepts layers, like background, text-shadow, box-shadow or combines multiple options like filter and font-feature-settings like already mentioned.

A way to add to these, or even compose them, rather than fully overwriting them, would be great.

@tabatkins
Copy link
Member

I think you added this comment to the wrong issue?

@GrossDesignCo
Copy link

+1, this would be really useful for design systems that need to define specific properties without overriding user styles.

@dbaron
Copy link
Member

dbaron commented Aug 11, 2022

It's not clear to me (particularly given #1594 (comment)) whether this issue should cover all additive cascading concepts in CSS, or only those intended for animation.

Also, for what it's worth, the earliest additive cascade proposal that I could find is https://lists.w3.org/Archives/Public/www-style/1999Oct/0025.html .

@korenevskiy
Copy link

korenevskiy commented Aug 12, 2022

You can consider the syntax with a plus sign "+" at the very end .

button{
	transition: width 0.5s;
	transition: box-shadow 0.5s +;
	transition: background-color 1s +;
}

Optionally, you can consider specifying the order of the member after the plus sign "+"

button{
	transition: width 0.5s;
	transition: background-color 1s +2;
	transition: box-shadow 0.5s +1;
}

This is the equivalent of

button{
	transition: width 0.5s, box-shadow 0.5s, background-color 1s;
}

The description should apply to all multilayer properties. Such as :

filter, background, transition, box-shadow, text-shadow and ...

This will allow you to structure styles by meaning.

button{
/* Transition Width */
	width: 50px;
	transition: width 0.5s;
/* Transition box-shadow */
	box-shadow: 0 0 10px black;
	transition: box-shadow 0.5s +;
/* Transition background-color */
	background-color: red; 
	transition: background-color 1s +;
}

@SebastianZ
Copy link
Contributor

It's not clear to me (particularly given #1594 (comment)) whether this issue should cover all additive cascading concepts in CSS, or only those intended for animation.

I believe those for animation are already covered by the prior resolution to ship Web Animations with additive animation and by the animation-composition property @birtles mentioned in his initial post.

And given the long discussion in this issue about a general solution, I think it makes sense to keep using this issue to find a general solution.

@tabatkins Could you explain why you think @Kilian's comment belongs to a different issue?

Regarding "all cascading concepts", we should first clarify what those different concepts are. As far as I can see, we have two general main concepts (which @birtles already mentioned in his initial post).

Manipulating a numeric inherited value

This means a numeric value is calculated relative to the value given by the cascade. In Brian's example this was a blur(5px) on a given blur(2px) resulting in a blur(7px). Another example would be adding 10px for the top padding to a given padding: 20px resulting in padding: 30px 20px 20px 20px.

I guess those use cases may already be covered by #2864, calc() and/or custom properties.

Adding a value to a list of values

This means any kind of value is appended to (or removed from) the list of a property's value, independent of whether it is inherited or not.

This is what Brian wrote as blur(2px) + blur(5px) resulting in blur(2px) blur(5px) and generally what people mean as adding values to a property. This is obviously also what people had in mind when suggesting additive cascade as solution for #7066.
There it doesn't matter if the value is space-separated like in transform or filter or comma-separated like in background or box-shadow as mentioned by @Kilian.
Other examples for them would be transform: scale(1.2) + transform: translateX(100px) = transform: scale(1.2) translateX(100px), box-shadow: 2px 0 2px red + box-shadow: -2px 0 2px blue = box-shadow: 2px 0 2px red, -2px 0 2px blue or container-type: style + container-type: size = container-type: style size.

As mentioned above, there might also be cases in which people want to remove a value from the list of values. E.g. as we now have style containment by default (which can't be removed at the moment), people may explicitly remove it for cases they only want size containment to improve performance.

Sebastian

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests