- From: Javier Contreras via GitHub <noreply@w3.org>
- Date: Mon, 21 Jul 2025 16:55:04 +0000
- To: public-css-archive@w3.org
We're trying to come up with generalized definitions for the interpolation behavior, whilst keeping repeaters in mind. This is a summary of what we have been discussing so far and what we'd like other people's feedback on. Repeat syntax is defined here: https://www.w3.org/TR/css-gaps-1/#lists-repeat ## Current implementation (Chromium). For now we are interpolating by [computed value](https://www.w3.org/TR/web-animations/#animating-properties), meaning that list lengths must be of same size. Moreover, we are only interpolating repeaters with other repeaters that have same `repeat_count_` and `len(repeated_values_)`. This is the same thing that grid is doing for template-rows. So for example, we will interpolate these: `5px` → `15px` `5px 10px` → `20px 30px` `repeat(2, 10px 20px)` → `repeat(2, 15px 30px)` `10px repeat(2, 3px)` → `30px repeat(2, 10px)` But on the other hand we will not interpolate these: `5px` → `10px 15px` `repeat(2, 10px 20px)` → `repeat(2, 15px)` `10px repeat(4, 3px)` → `30px repeat(2, 10px)` However, for Gap Decorations, we believe that it makes sense to expand this behavior and allow other combinations of values to interpolate. ## LCM to allow for different length lists Firstly, one easy improvement is that we could interpolate lists of different lengths by doing [repeatable list](https://www.w3.org/TR/web-animations/#repeatable-list) interpolation in which > if the two lists have differing numbers of items, they are first repeated to the least common multiple number of items (LCM). Each item is then combined by computed value. If a pair of values cannot be combined or if any component value uses [discrete](https://www.w3.org/TR/web-animations/#discrete) animation, then the property values combine as discrete. `background-size` interpolates this way. An example of this is included for clarity: 1) Interpolating `50px 100px → 20px 30px 40px`. 2) `LCM(2,3) = 6` 3) becomes `[50px,100px,50px,100px,50px,100px] → [20px, 30px, 40px, 20px, 30px, 40px]` 4) `(50→20), (100→30), (50→40), (100→20), (50→30), (100→40)` ## Handling Repeaters However, the tricky part comes when we include repeaters, so we're looking to define how repeaters would behave when interpolating with other repeaters as well as other non-repeaters. @flackr mentioned above a concrete example of something we might want to do: > repeat(3, red) to repeat(2, red) blue should interpolate at 50% to repeat(2, red) #770077 I will change this example to use Widths since that's easier to reason about: `repeat(3, 10px) → repeat(2, 10px) 20px` would become at 50% `repeat(2, 10px) 15px`. This implies that we're doing some logic where we find the intersection from these repeaters, see how they line up, and pull out any values, which leads to the rather clean result of `repeat(2, 10px) 15px`. On the flip side, this seems to be somewhat at odds of the LCM Repeatable List interpolation behavior. There will be some cases where this will not be possible, or at least I can't quickly see how we could generalize that into also interpolating something like: `repeat(3, 10px 15px) → repeat(2, 10px) 20px`, or something even more complicated like `r(3, 5px 10px 15px 20px) → r(4, 15px 20px 25px)` ### Potential Option 1: LCM on the raw lists One potential solution would be to treat repeaters as exactly what they are, lists of values, and interpolate using LCM as such, "creating" (I put quotes on "creating" because we wouldn't have to actually create them, using modulo would be sufficient I believe) lists of equal length and then interpolating matching pair indices. So for example, `r(2, 10px)` → r(2, 15px 20px)` 1) we interpolate as `10px 10px → 15px 20px 15px 20px` 2) We expand each list to the LCM length: `10px 10px 10px 10px → 15px 20px 15px 20px` 3) We interpolate matching indices. This would also solve the problem of interpolating between repeaters and non-repeaters. For example, `r(2, 10px 15px)` → 20px`: 1) Apply LCM. 2) We interpolate as `10px 15px 10px 15px → 20px 20px 20px 20px` To go back to the previous example that @flackr illustrated (but with widths) `repeat(3, 10px) → repeat(2, 10px) 20px`. The first thing to consider would be how we deal with the fact that the lengths of the `to` and `from` don't match (2 vs 1), at least if we take `r(...)` as one element. ### Element-wise LCM Another option would be to apply LCM element-wise: 1) So for `repeat(3, 10px) → repeat(2, 10px) 20px` 2) We would first have `r(3, 10px) r(3, 10px) → r(2, 10px) 20px`. 3) Then we interpolate pairs `r(3, 10px) → r(2, 10px)` and `r(3, 10px) → 20px` Even though this is intuitive and easy to generalize, we end up interpolating potentially fairly large number of elements (9 pairs in this small example). If this is not a path we want to go down to, I could see a potential work around as treating the length of `repeat(3, 10px)` as `3` and `repeat(2, 10px)` as `2`), which leads to this example then interpolating between matching list lengths. However, I suspect this would lead us to have to expand the repeaters in the general case, to be able to do pairwise interpolation, but there might also be a way around this. ### The intermediate values Another question that arises, is what do we expose as the computed value at x% through the animation? All of the options described previously would entail "losing" the `repeat` syntax for the in-between steps of the interpolation. So for example something like `r(2, 10px) → r(2, 15px 20px)`: 1) we interpolate as `10px 10px → 15px 20px 15px 20px` 2) We expand each list to the LCM length: `10px 10px 10px 10px → 15px 20px 15px 20px` a) at 50% this would be something like: `12.5px 15px 12.5px 15px` b) although in this particular case it is easy for us to derive a repeater from this, it is not a trivial task in the general case. @oSamDavis was able to come up with a potential formula that could allow us to derive a repeater from a list in the general case, but we haven't rigorously tested it so might not work for all scenarios. c) As a result, at intermediate keyframes we will return only the expanded list of values (no repeat()), e.g. [12.5px, 15px, …], which may or may not be desired. 3) We interpolate matching indices. ### My personal take: I would lean towards the LCM-based, “repeatable list” interpolation model. It’s simple,, and it ensures that every index in both lists has a clear pair to interpolate against. That predictability is a huge win, especially when authors rely on computed values at arbitrary keyframes. Flattening out to an expanded array every frame makes the math easy, but it sacrifices author-friendly semantics if someone inspects the computed style mid-animation. Personally, I’d be comfortable with the trade-off, but maybe we could also consider a post-processing pass at the end of interpolation (or at discrete checkpoints) to re-collapse patterns that trivially map back into a repeat() form. Regarding performance, LCM could balloon the list lengths dramatically. In practice, gaps tend to be small lists, so it’d probably never become too big, but we already handle potential cases such as this in Chromium https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/animation/list_interpolation_functions.cc;drc=3dc8b5b36eaa1d68f5caa5e36444fcb3e204d460;l=108 Would appreciate more people's thoughts and takes on how we could solve these issues and come up with behavior that is generalizable among lists of different types (repeaters and non-repeaters) and different lengths. -- GitHub Notification of comment by jav099 Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/12431#issuecomment-3097590073 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Monday, 21 July 2025 16:55:04 UTC