Skip to content

Interpolate values between breakpoints #6245

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

Open
scottkellum opened this issue Apr 27, 2021 · 82 comments
Open

Interpolate values between breakpoints #6245

scottkellum opened this issue Apr 27, 2021 · 82 comments

Comments

@scottkellum
Copy link

scottkellum commented Apr 27, 2021

Edit by @LeaVerou: CSS WG resolution to work on this, pending a name (options here)


Allow interpolation between both viewport and element breakpoints.

The problems with clamp(), min(), and max() is that you can only interpolate length values on a single property between two points. You may want to interpolate rulesets across multiple breakpoints. You may also want to interpolate things like variable font settings, color, etc. Additionally it would be nice to be able to ease how breakpoints are interpolated as rates at which things scale across different screen or element sizes can often be variable.

Update Dec. 7, 2022

I’ve created an explainer on this issue with a more detailed proposal: https://css.typetura.com/ruleset-interpolation/explainer/

This includes a more specific and detailed proposal as well as a companion proposal to expand scroll timeline. Expanding scroll timeline is likely the easiest solution at the moment but it would make it more user friendly to allow length values as keyframes. This can always be added at a later date though.

@mirisuzanne
Copy link
Contributor

Yeah, this would be real useful for something like "intrinsic typography" - but I can imagine other use-cases as well. It's interesting to think about media/container "breakpoints" as keyframes in an animation, which we can then interpolate (with easing).

My immediate association is scroll-timeline. I wonder if there would be a way to adapt something similar to get, basically, a container-timeline.

@fantasai
Copy link
Collaborator

fantasai commented Sep 24, 2021

@mirisuzanne and I put together this proposal for defining and using query-linked timelines as part of rethinking various features for animation timelines and interpolation and how to fit them all together.

Query-linked Timelines

Query-linked interpolation uses a set of keyframes (minimally, two) to interpolate values along an easing curve based on the value of a query (such as a media query or container query). The timeline is therefore defined by the value of the query, and can be referenced by an interpolation function in individual property declarations.

Defining the Query Timeline

The @timeline rule defines a named timeline. It can be expanded later to define other types of timelines, but here we're defining only two types: media query timelines and container query timelines.

@timeline NAME {
  type: media | container;
  feature:  | ;
  from: ; /* 0% of the timeline */
  to: ; /* 100% of the timeline */
  container: <'container'>; /* only applies to container query timelines,
                               same seeking function as container queries */
}

A typical example might look like:

@timeline font-size-timeline {
  type: media;
  feature: width;
  from: 20em;
  to: 60em;
}

While query-linked timelines can be referenced in animation-timeline, it's not recommended to use this method in most cases because it would cause cascading problem: anyone using query-based interpolation via animation properties would override all affected properties at levels of the cascade.

They can, however, be referenced by an interpolation function within the affected property declarations, which allows the interpolated value to cascade the same as any other declared value.

Value Interpolation

Value interpolation uses a percentage value to indicate how close or far from the start/end points to calculate the interpolated value. Interpolation is interpreted through an easing curve, and the input percentage can be selected based on the current position on a timeline such as a query timeline.

Timeline-based Value Interpolation

This extends the generic interpolation function adopted (but as yet unnamed ;) in #581

  mix( [  && [ by  ]? ] ;  ; )

By naming a timeline instead of giving a percentage directly (see percentage mixes in #581 (comment)), the author can use progress along a timeline as the progress percentage. Any value valid for animation-timeline or any timeline name defined via @timeline is valid, which allows the mix() to respond to query-linked timelines and scroll-linked timelines.

Value Interpolation with Keyframes

For more complex interpolation curves, the and can be replaced by a reference to a named set of keyframes.

  mix( [  && [ by  ]? && of  ] )

Note: Using keyword markers (as in gradients) allows the arguments to be reordered, so that authors don't have to memorize positions of arguments.

@bramus
Copy link
Contributor

bramus commented Sep 24, 2021

A typical example might look like:

@timeline font-size-timeline {
  type: media;
  feature: width;
  from: 20em;
  to: 60em;
}

As an addendum: for CSS @scroll-timeline — as specced in the Scroll-Linked Animations spec — the authors explicitly moved away from using start (here named from) and end (there named to) as descriptors.

They replaced it with one descriptor named scroll-offsets (which here could be named offsets), that accepts an array of values.

As per spec:

Scroll timeline offsets determine the effective scroll offsets in the direction specified by orientation that constitute the equally-distanced in progress intervals in which the timeline is active.

That way they allow more than two offset to be used.

Relevant Issue: #4912, specifically this comment.

@mirisuzanne
Copy link
Contributor

@bramus If I understand right, we're thinking about timelines a bit differently here. They seem to be establishing the number and placement of keyframes in the timeline description, and that seems to me like the wrong location for that information.

In our proposal, the timeline doesn't establish the available keyframes, just the distance that we travel from 0% complete to 100% complete. Then authors can attach that to an animation or interpolation function with as many keyframes as they need. The individual offset concerns of each animation ("reveal, "unreveal", etc) are handled in keyframes, rather than in the timeline itself. So you have a single timeline (x to y), and then the ability to offset keyframes within that timeline. "Reveal" might happen between 20%-40% of the timeline, and "unreveal" happens from 60%-80%, each one using as many keyframes as it wants.

The number and placement of keyframes is controlled by the animation/interpolation rather than by the timeline.

But maybe I'm misunderstanding something there?

@bramus
Copy link
Contributor

bramus commented Sep 26, 2021

@mirisuzanne You understand correctly there. Simply wanted to point out that “this move in the other direction” was made before, and that it perhaps could be relevant to take into account ;)

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed interpolating values between breakpoints, and agreed to the following:

  • RESOLVED: Accept mix() function into Values 5
The full IRC log of that discussion Topic: interpolating values between breakpoints
github: https://github.com//issues/6245
https://github.com//issues/6245#issuecomment-926351855
miriam: This is building on that same idea, but creating timelines out of MQ/CQs
miriam: In this caes you're more often doing interpolated values based on the timeline, not animations specifically
q+
miriam: We want to be able to create timelines off the size of the container
miriam: So for defining the query timeline, we have an @timeline syntax.
q+
miriam: Give it a name, say what we're querying, what feature we're querying
miriam: And give it a from/to value to offset that range
miriam: So interp between a container being 100px and 40em to define the timeline
miriam: If it's a CQ we give the name of the container
miriam: If there are multiple CQs with that name this'll apply to all of them
miriam: Kids will look at their appropriate ancestor container
miriam: And we can use the timeline name in an animation-timeline
miriam: But more often we'll want a value that interps in the cascade instead, so we can override it if we need to
miriam: A generic interpolate() function has been discussed for a long time
miriam: We called in mix() here, named TBD
miriam: Idea is it could be generic, taking a %, or take a timeline which resolves to a %. Could invoke scroll timelines too, etc.
miriam: And then it takes an easing function and two values to interp between
miriam: In some cases this'll get more complex with multiple values, maybe get quite long
miriam: Wondered if mix() could ref keyframes
miriam: So you could pull out the value details into keyframes for more detailed control
fantasai: I wanted to point out the cascade effects
q+ to wonder about once more doing piecewise functions with no continuity
fantasai: We considered putting query-based timelines as a value of aniamtion-timeline
fantasai: That ends up applyin all the props at once, and at an overriding level of the cascade
fantasai: Usually you don't want that, you just want to specify a value at a normal cascade spot, but *based on* a timeline
fantasai: So my font-size timeline can just spec an interpolated normal font size, and then have an overriding rule setting the font-size to a specific value as normal.
ack fantasai
fantasai, you wanted to react to flackr to point out cascade effects
ack flackr
s/as normal/as usual in the cascade/
flackr: I think what fantasai just said might change my q...
flackr: So this isn't an animation timeline, it only exists for the mix() function?
fantasai: We were debating that.
fantasai: We definitely want it for mix(). Whether it's available for animation-timeline is an open question
fantasai: We've asked brian for feedback and he pointed out there were a lot of complexities, so we might not want to do it
fantasai: Not the most important; mix() is the primary case
flackr: Yeah was gonna raise the same complexities; if it's animation, we have to have the animation progress update in the middle of the cascade.
flackr: Anders said it would be a huge technical burden to have anims update as part of the cascade due to the cascade
smfr: This feels like calc() to me
ack smfr
smfr: We could have one that interps with easing funcs
smfr: Missing piece is input from media features, could come in as env()
smfr: And so with a calc easing function thing
TabAtkins: not quite, implies only doing calc()-able things
TabAtkins: not all things that can be interpolated
TabAtkins: which includes colors, etc.
smfr: Can we make calc() accept these things?
TabAtkins: I don't want to but we can talk about it?
ack chris
chris, you wanted to wonder about once more doing piecewise functions with no continuity
chris: So this is unpopular
chris: We start by lerping two values
chris: Then we add more values and lerp them
chris: And if you draw that it's jaggy on a graph because slopes are different
chris: And then we add easings, and you can maybe fake it to look continuous
chris: But we never get to a thing that smoothly interpolates thru N values
chris: Is that something we want to do or just continue keeping it pairwise?
flackr: Is this not having easing on the mix function?
chris: That requires the author to figure out C1 continuity on their own
fantasai: This seems compatible with what keyframes do right now, we could default to smooth interp
TabAtkins: So chris's request is for the abilty to spec an animation with N values and have it automatically smoothly interp, rather than only having pairwise interp control that needs manual adjustment
chris: yes
TabAtkins: c1 continuity, to be specific
fantasai: We specced multi-stop animations using @Keyframes, see last section of proposal
q?
TabAtkins: I suspect that's something we can handle at a higher level
TabAtkins: we have a default for pairwise interpolation, default to ?
TabAtkins: could do smarter things in animations
TabAtkins: fits within existing syntax structure of animations
flackr: It will be challenging, though
flackr: easing function per keyframe is just between those endpoints
flackr: easin function on animation is just input time to output time
TabAtkins: animation-easing-function is the default between frames
flackr: that's correct for CSS. Web Animations also adds an easing curve to the timeline
TabAtkins: you're easing time into massaged version, that's separate from this
ack fantasai
fantasai: i think we could easily have a "tweak the time"-based version, we could add that into the rule as well
s/rule/@timeline rule/
fantasai: Intention of mix() argument was the default easing between frames
fantasai: If we want to default to doing continuous magic, or adding a keyword to opt into it, that's fine
flackr: Yeah it would be like combining adjacent pairs that have the same value into one continuous timing function
fantasai: I want to point out we dont' ahve a resolution on the form of the generic interp function
fantasai: So our proposal is to have it accept %s and two values
https://github.com//issues/581#issuecomment-926353789
fantasai: So this would be a function that replaces the % with a timeline that computes to a %
fantasai: We have a resolution to *add* a mix() function but didn't settle on the syntax
Rossen_: So what can we resolve on?
s/this would be/this proposal is/
fantasai: resolution the first: generic interpolate function is called mix(). Takes %, then start value, then end value. Values are separated with semicolons to avoid ambiguity with comma-containing values
(you can interp a comma-separated list, for example)
TabAtkins: Simon had some thoughts about this in calc(), do you want to continue talkinga bout this?
smfr: I'm not quite sold on @timeline yet, but I don't want to stall this
https://github.com//issues/581
fantasai: Right now it's just mix()
smfr: Would this be like a calc()?
fantasai: Like, but wider.
fantasai: It has to be able to interpolate every possible computed value in the entire space of CSS
smfr: It requires UAs to have a parallel version of calc trees, for every possible value
fantasai: You kinda already have that since everything can interp
fantasai: Like, how do you interp between currentcolor and blue? No way to represent that right now. (color-mix() is coming, but this is a wider issue)
fantasai: So we have lots of places where we want to interp things that don't have intermediate values
smfr: That makes sense, we also invented cross-fade() to hit the image case
smfr: I'd like to hear from other impls about their thoughts on impl complexity, and whether it makes sense to think of it in terms of calc()
TabAtkins: I don't have problem of thinking about it in terms of calc(), can re-use machinery there
TabAtkins: but I think that's an internal detail
fantasai: Note that we *resolved* to add the function years ago but didn't resolve on the syntax
see also https://github.com//issues/2854
RESOLVED: Accept mix() function into Values 5
s/Accept mix() function into Values 5/Accept mix() function into Values 4/
fantasai: So next is do we want mix() to accept a timeline+easing function instead of a %
fantasai: If no, I don't need to go into details. If yes, we'd use the @timeline rule discussed previously.
TabAtkins: This just got proposed last week, it's a little big. I'd like more time to review on it.
fantasai: And this would def go into level 5
ScribeNick: fantasai

@scottkellum
Copy link
Author

I have updated this issue with a more detailed explainer containing a proposal. Here is the explainer.

@danielsakhapov
Copy link
Contributor

Hello, everyone!

Since we now have scroll- and view-timelines, maybe it makes sense to do something similar for this problem?
Like, container-timelines? They will allow to drive a regular CSS/Web animation using the size of a query container’s content-box.

A container timeline is created similarly to how a scroll/view-timeline is created:

#container {
  container: mycontainer inline-size;
  container-timeline: mytimeline inline-size;
}

And then the animation is set up as you would set up a scroll/view-timeline:

#target {
  animation: anim auto;
  animation-timeline: mytimeline;
}

And keyframe offsets will accept values. The following defines an animation that takes place between 40em and 800px:

@keyframes anim {
  40em {
    color: green;
  }
  800px {
    color: red;
  }
}

So, overall:

container-timeline-name: #
container-timeline-axis: #[ block | inline | x | y ]
container-timeline-range: #[   ]
container-timeline: #[  [ ? ||  ] ]

With container-timeline-range being min and max lengths for the progress of the animation.

@bramus
Copy link
Contributor

bramus commented Sep 5, 2023

I’m very much sold on the idea.

Two remarks though:

  1. For ScrollTimeline/ViewTimeline the range is not part of the Timeline; there are no view-timeline-range or scroll-timeline-range properties. Instead, the range part of the animation, using the animation-range property. That way one can re-use one timeline instance for multiple animations with different ranges.

    I would suggest to do the same for container timelines.

  2. ScrollTimeline and ViewTimeline also have anonymous timelines, via the scroll() and view() functional notations. Ideally there should also be an anonymous container timeline.

    Strawperson proposal is to name it container(), with this syntax:

     = container( ? )
     = block | inline | x | y
    

    An anonymous container timeline would always look up the nearest container in the ancestor tree. The default value for is block.

@andruud
Copy link
Member

andruud commented Sep 6, 2023

For ScrollTimeline/ViewTimeline the range is not part of the Timeline;

It very much is, it's just that it's set automatically from min/max scroll (etc) for those. The animation attachment range is not the same thing as the timeline range itself. Notice how animation-range-start/end:normal refers to the start/end of the timeline. Without container-timeline-range, we'd need another way of understanding the start/end.

@bramus
Copy link
Contributor

bramus commented Sep 6, 2023

Ah yes, I see now why one would need to define the container-timeline-range, because unlike scroll-timeline and view-timeline it cannot be automatically determined for containers. Thanks, @andruud.

@danielsakhapov
Copy link
Contributor

Sorry, I forgot about the functional notation. Container timeline can be created directly with a functional notation.
The following creates a timeline from the nearest inline-size container:

#target {
  animation: anim auto;
  animation-timeline: container(inline-size);
}

@andruud
Copy link
Member

andruud commented Sep 7, 2023

Agenda+ to see if we can add container-timeline-* to css-contain. (cc @mirisuzanne @fantasai)

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed Interpolate values between breakpoints, and agreed to the following:

  • RESOLVED: Accept the general approach, change map naming to scale for now, iterate on syntax later
The full IRC log of that discussion TabAtkins: elika and i were looking over this and there is a generic request to interpolate values based on some othe rprogeress value
… can be done by hand with calc, but does not work for all values
… or there are complex ways to interpolate
… seem slike a reasonable thing, that if you can do atransition, you can get the value for it too
… upon review of the use-case, what we were tyring in the spec was not good enough
… currently not implemented, so can start over
… new proposal
q+
… two notions of interpolation
Writeup is at https://github.com//issues/6245#issuecomment-2661545284
… 1. mixing of two values
… 2. interpolate values in series, more like linear() for timing or gradients
are the existing spec'd functions the progress() functions?
… both are nearly identical, but when you reach 3 values they differ a lot
… can model either with the other, but frustrating and weird
… prop to introduce mix and map
weinig: https://drafts.csswg.org/css-values-5/
… mix = …
… syntax proposal in the issue
q+
… thoughts, ideas, …?
https://github.com//issues/6245#issuecomment-2685663483
ack lea
lea: posted my thoughts on this link here
… when i started writing that i was not too crazy about introducing mor efunctions
… wanted to overload the exsting mixing functions
… but while writing my feedback it would make sens to have a new function (if it has a good name)
… maps typically help you transform 1 value into another
… (example)
… several requests had to add mapping to css
… even though sth like calc-map() or whater-map() it overloads the concept and now mapping is 2 diff things
… also confusing to be using an established concept in a diffefernt way
… interpolate is long and technical
… brainstormed a bit (see table in comment)
… leaning towards the name scale which gives a nice color-scale
… and calc-scale
… not ideal for images
… also huge +1 for solving this use case
… is major in design systems and tokens
… so yeah
TabAtkins: not attached to the name, we chose map bc interpolate was bad for your reasons and its also short like mix
+1
… scale seems appropriate
… having a hwole family of related fns the worry
We weren't particular about the name, just needed one.
a little worried about our existing scale property
ack weinig
weinig: which of the css-values=5 fns is this replacing?
TabAtkins: the mix functions, not the pgoress ones which are inputs to these
weinig: got it
… other suggestion: blend
… bc that is often the result
… proble with scale is that we already have scale()
TabAtkins: only obj to blend is that it is a close synonym to mix
+1 to TabAtkins . Also `blend` sounds related to blend modes
… the better we can help authors remember which is which is good
lea: +1
… and also bend sound related to blending modes
TabAtkins: prolly cant avoid some semantic overlap
Rossen3: sounds like we are converging?
fantasai: yes, want to go over what it is
q+
… the fn takes a bunch of top level params and what th eprogrsss is within the scale and a list of stops similar to linear-gradient(0 with an interpolation option between the stops (easing fn for example)
… and then the top level options are a source for the progress, also giving you an option of transofmring that source thorugh an easing function, and an option saying each step gets its own easing
… that is the top level
… and then the stops have a syntax where it is `stop: value` and you interpolate between th stops
color: color-map(media-progress(width, 0px, 2000px),
0%: red,
100%: blue);
color: color-map(media(width),
0px: red,
2000px: blue);
fantasai: also introducing new fns
… percentages, pull the value out directly
… pull out the start and end values, package them up together and put m in a variable
… so the progress functions are worth keeping for that reason
… Qs?
ack lea
lea: dont quite understand by vs each
… its a good design goal to be compatible with gradients
… it could even b ea design goal that everyting in side color-scale is a valid gradient
… good for rease and debugging
… the syntax with colons of positions adn values. not sure the reduction with the rest of CSS is warranted by the usability adv
… seems small
… breaking compat is a good thing if it gives you a significant advantage, not sure that is the case here
… suggestion to stick to existing syntax
… (missed: gradients)
… where you can percentages
animating-timing-function vs animation-easing
fantasai: diff between by and each is wehther you are applying easing between two stops or to the entiry of the progress value. Animations have this distinction. Can ease the entire timilne or in between keyframes.
… have the ability to put easing between any two stops, or the whole thing upfront
s/(missed: gradients)/we would need to make it mandatory that the stop position comes after the value in the calc version, as otherwise there would be disambiguation problems/
… that is the difference
… about the colon in the stop syntax: that is bc of the parsing ambiguity
… cant know the reeturn value upfront
… other optino is to align with if() and use semicolons
… not sure though
q+ to ask about multiple positions, also use cases for each
TabAtkins: there are certain value types make it ambigious to read or actually ambiguious
… e.g. calcs() sometimes hard to read
… not obvious to a human sometimes
… more problematic is the generic map() that can do entire property values
… no way to know where the value ends
… so have to either put the percentages in a distinguished place (like now) or only require 1
… would not allow us to do 1 or 2 stops
… bc those fns have parsing difficulties if you try to mix things in, we decided to carry that through the whole family of functions
… making most of map fns look similar to ?? but hten some not , looks worse
ack lea
lea, you wanted to ask about multiple positions, also use cases for each
s/??/mix functions/
lea: agree that constiency with eachother is more imporatnt than consistency with the rest of CSS
1 + 2px +3px
… not sure about the color … hard to read or disambiguation issue?
… for the generic no amount of syntax would work other than putting it first
fantasai: (missed) progress values. can have a stripe
lea: is the generic fn actually ahppening? remember we had one for mix but then had to drop it
TabAtkins: multiway interpolation is trickier but if its just for map you are only interpolating two values at a time – we already know how to do that
lea: this wont solve issue with generic, but at least fo rothers we coul dintroduce an at-keyword
… like red at 50%
… very readable
TabAtkins: but unfrotunately does not extend
… not the best, happy to discuss precise syntax more
… looking for objections about the general approach
Rossen3: so now are gonna stick with the map?
fantasai: no, I meant `at 50%`
q?
@ 50% might be kinda nice though :)
It avoids any parsing issues since @ is not otherwise valid
can we have a proposed resolution?
TabAtkins: lets switch over to scale and continue discussing
lea: do we have a proposed resolution? take on work?
TabAtkins: yes: accept the approach we have outlined in the issue changing the map naming for scale and continue iterating on the design in the spec
PROPOSED RESOLUTION: Accept the general approach, change map naming to scale for now, iterate on syntax later
Rossen3: sounds reasonable
weinig: will the keyframes part remain as well?
TabAtkins: that is the plan, but also up for discussion
weinig: I think you should keep them
fantasai: Qs for syntax were about the separator keywords by vs each and the name of the function
Huge +1000 to solving this problem, this is huge. Some doubts about each (do we have use cases for it?) but we can sort out later
lea: +1, like I said in IRC
… its low level, but lack of being able to do this keeps cropping up all the time
fantasai: yes, need to thank scott for filing this and following up on it
Rossen3: Objections?
… none, so we are resolved.
RESOLVED: Accept the general approach, change map naming to scale for now, iterate on syntax later

@fantasai
Copy link
Collaborator

Syntax follow-up questions are:

  • What should be the name of the function? (We're going with -scale for now, but open to other ideas.)
  • What should be the separators a) between stops b) between stop input value and stop output value?
  • Is by vs each reasonable or do we have other ideas for disambiguating the easing function applied to the progress as a whole vs between stops?

@scottkellum
Copy link
Author

For me, scale is too similar to the transform: scale() function, and I immediately think of scaling in geometry. For this reason, it feels unclear to me. I agree with Lea on map.

Other options that I’ll throw in:

  • *-change
  • *-morph

I like morph the best, but will leave it to all the smart folks here to debate some more.

@benface
Copy link

benface commented Feb 26, 2025

*-interpolate makes the most sense to me.

@bramus
Copy link
Contributor

bramus commented Feb 26, 2025

(#) For me, scale is too similar to the transform: scale() function, and I immediately think of scaling in geometry.

+1

(#) *-interpolate makes the most sense to me.

Also +1. The word might be long, but it most accurately reflects what is happening.

I would also argue against interpolate not being a common word. A lot of specs use the term and it’s also used in developer facing documentation such as linear() on MDN

@tabatkins
Copy link
Member

We use a lot of technical terms in the specs that don't translate to author-facing names. interpolate is, imo, on the edge of too complex to show up in an API name.

Is by vs each reasonable or do we have other ideas for disambiguating the easing function applied to the progress as a whole vs between stops?

Could be more explicit, like ease-input vs ease-each

@LeaVerou
Copy link
Member

LeaVerou commented Feb 26, 2025

If *-scale() is out of the question, I would also go for *-interpolate() despite its shortcomings. As @bramus pointed out, it is used extensively in docs, as well as userland libraries (e.g. https://d3js.org/d3-interpolate ). Things like morph seem incredibly confusing to me.

WRT each, do we have enough use cases to warrant figuring this out now? It seems to me that this can be retrofitted later.

This also reminds me, can these be nested? How does that behave? (also not for the MVP)

@mirisuzanne
Copy link
Contributor

ease-all and ease-steps?

Re *-scale - In theatrical lighting design we would call this a sequence or a chase. I think sequence translates here decently. In CSS we already call the same concept a set of keyframes. Is there a way to re-use that terminology? These are really just inline keyframes, right?

@tabatkins
Copy link
Member

Hmmmm I was gonna complain that I didn't like using that for the generic function because it can take a keyframes name as an argument, but actually keyframes(media-value(width) my-kf-rule) looks pretty decent.

And that lets us link the concept super directly to that of easing between keyframes vs over the whole animation, etc.

@tabatkins
Copy link
Member

This also reminds me, can these be nested? How does that behave? (also not for the MVP)

Yeah, it Just Works, since they resolve to an appropriately typed value. (Including the generic one, as its arguments are , which the generic one matches.) None of this is based on any implicit state, it's a fully self-contained calculation, so there's no issues with nesting besides the obvious unreadability.

@bramus
Copy link
Contributor

bramus commented Feb 26, 2025

(#) interpolate is […] too complex to show up in an API name.

There is precedent:

  • SVG and CSS: color-interpolation
  • color-mix()’s
  • in gradients
  • interpolate-size

@tabatkins
Copy link
Member

The second and third aren't part of the "author-facing API", they're just spec terms.

The first and fourth, granted.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed Interpolate values between breakpoints, and agreed to the following:

  • RESOLVED: Rename the interpolation functions to *-interpolate()
The full IRC log of that discussion TabAtkins: we talked about this a few weeks ago. people liked -mix(), but not -map() or -scale()
lea: linked, we talked about interpolate, tween, range, scale
lea: we're looking for somethign that works for many types
lea: color-sacle is fine, calc-scale is fine, but image-scale is bad, and generic scale() conflicts with transforms
lea: i think tween() is obscure and tied to animations
lea: range() sounds like it returns a range
lea: intermediate() is as longa s interpolate().
lea: interpolate() is clearest, it's just long. but other than that i think it's best option
q+
lea: so if we can't do scale() i think interpolate() is best
lea: confusing is worth than long
q+
ack oriol
oriol: i had a minor question of what's different between mix() and interpolate()
TabAtkins: The mix functions do a weighted average, the interpolate functions take a sequence of values and an input value, like a gradient, and give you where your progress position landed on
ack kbabbitt
e.g. `color-scale(30%, white, red 30%, black)` would give you red
kbabbitt: i like the name interpolate(). agree it's long but it's used elsewhere
kbabbitt: bramus pointed out we use it in color-interopolation and interpolate-size
ack dbaron
kbabbitt: i agree clear is more important than short
dbaron: another idea is use the word tab just said, gradient()
dbaron: that's a thing people currently understand
TabAtkins: i think color-gradient() is a little funky
s/another idea/another idea (maybe a bad idea, but worth considering)/
astearns: have there been any arguments against interpolate() besides length?
TabAtkins: no
gradient reminds me of math gradient descent
q+
ack oriol
oriol: a shorter idea, pick()?
`image-pick()`, `color-pick()`, `calc-pick()`
TabAtkins: My only worry is that it sounds like you're picking one of existing options, not interpolating
TabAtkins: we can just go for interpolate()
lea: yeah i think anything else is just unclear. saving a fe wmore characters isn't worth it
astearns: anyone who would prefer not to resolve on interpoltae() today?
ntim: what's the sytnax of this function?
TabAtkins: [summarizing] it's basically the linear-gradient() syntax, more or less
astearns: objectiosn to using interpolate()?
RESOLVED: Rename the interpolation functions to *-interpolate()
lea: is it before or after the type particle in the name?
TabAtkins: after, like color-mix()
Yay!
astearns: but if yo uwant the other way, open an issue

@fantasai
Copy link
Collaborator

fantasai commented Jun 6, 2025

In accordance with the CSSWG resolutions from February and April, @tabatkins and I have rewritten the css-values-5 section on mixing functions to introduce the weighted-average mix() notations and the chained-interpolation interpolate() notations. See #6245 (comment) for the original proposal. The new spec text should appear shortly in the ED at https://drafts.csswg.org/css-values-5/#mixing.

Comments are welcome. Agenda+ to request a CSSWG review and publication. CC @scottkellum @mirisuzanne

@kbabbitt
Copy link
Collaborator

kbabbitt commented Jun 6, 2025

Step 4 in the "normalize mix percentages" algorithm:

If total is greater than 0%, or if total is greater than 0% and the force normalization flag is true, multiply every percentage in items by (100% / total).

I don't understand what difference the force normalization flag makes, given that both sides of the 'or' include "total is greater than 0%".


In the section on transform-mix(): As an aid to readers, suggest linking to the matrix interpolation algorithms in transforms-1 and transforms-2.


Personally, I found the section on the interpolate() family to be (out of necessity) heavy on abstract concepts and hard to wrap my brain around as a result. I feel like a generous sprinkling of examples, or a link to an explainer, might be helpful. Specific points I'll call out:

  • In the introductory section, after laying out the generic grammar and defining what the terms represent, one or two concrete examples of that grammar might be helpful.
  • An example for of type . There's one further down in 7.2.3, but I think it would be helpful to have one here - I find progress as a percentage or [0..1] value intuitive, but progress as a dimension didn't make sense to me until I realized that it was in the context of the subsequently defined stops.
  • In spots where the spec talks about "options" generically, I found myself wanting examples of what those options might be.
  • 7.2.2 uses the term "pinned" which I haven't seen elsewhere. How does it differ from "clamped"? Some examples of the effect of pinning might be helpful.

In one of the examples for interpolation, we have:

background-color: color-interpolate(300px in hsl, 200px: red, 500px: green, 600px: blue);

Reading "300px in hsl" without having yet wrapped my brain around the grammar threw me for a loop. The way the commas are placed through the rest of the value, it read to me like we were somehow supposed to convert 300px to hsl and use that as the progress value, which made no sense. Would it be feasible to separate the progress and global-options more clearly? Or would that be too inconsistent with already specified constructs?

@fantasai
Copy link
Collaborator

fantasai commented Jun 6, 2025

I don't understand what difference the force normalization flag makes, given that both sides of the 'or' include "total is greater than 0%".

Eh, that second one should be "less than".

@tabatkins
Copy link
Member

Eh, that second one should be "less than".

Not quite. There were two overlapping errors in that line - I missed that you'd applied the "clamped to 100%" to total as well (it should only be on specified sum), and the conditions should be "greater than 100%, or greater than 0% and force_normalization". The behavior we want is that, if normalization isn't being forced, the only scaling we do is to reduce a sum >100% to be exactly 100%; if normalization is being forced, then we also correct a sum < 100% to be exactly 100% (but leave a sum that's exactly 0% alone).

Fixed now.

@cdoublev
Copy link
Collaborator

cdoublev commented Jun 9, 2025

The interpolation notations in CSS include:

  • palette-interpolate(), for interpolating font-palette values

The definition of palette-interpolate() is missing. I guess it will be added later.


I assume how interpolation functions are restricted in some contexts will be clarified in #10982. For example, when they take <'animation-timeline'> or relative .


  • calc-interpolate(), for interpolating , , , and other dimensions representable in calc() expressions

This somewhat suggests that is invalid, which it is not, and interpolating does not seem to be a "niche" use case. The same applies for the description of calc-mix().


&& [by ]? && ? && ?

It is equivalent to && [by || || ]?, which seems simpler and makes the requirement on the mandatory more obvious, imo. I suspect you already considered binding and by . (edit) nope, it is not equivalent, sorry for this mistake.

is a generic production but it is not recognized as such by w3c/webref. Would it be possible to tweak the markup to avoid this? Perhaps by adding an informative class?

In my opinion, the description of the global option should be defined in a separate (preceding) section:

7.1.2. Defining the Interpolation Map

When appearing in the first argument, specifies the “default” easing function to be used between each stop.

This section is about the interpolation map (the stop list) while the first argument refers to the global options.


All interpolation functions requires two or more stops and do not allow omitting stop positions, while *-gradient() and linear() accept a single stop and allow omitting its position. Is it intentional?


The value range of is not explicitly defined.

Is it | | ? ?

7.2.1 Type Checking may suggest that it can also be <'animation-timeline'>:

Each and has a type, which can be proportional (, , or <'animation-timeline'>) or absolute (all other types, and <'animation-timeline'>).

It also suggests that there are multiple :

Each and has a type [...] all and values [...]

But then this confuses me:

The proportional types represent progress as a percentage; any mix of these types is valid. The absolute types represent progress in absolute dimensions, and all and values that are not proportional must have a consistent type for the notation to be valid.

Do you consider and as both representing progress values? Otherwise, how can you mix types when specifying progress? What does represent an input position specified with a proportional type?

I would have thought that a percentage for an input position in calc-interpolate() replacing , would represent a . (edit) never mind, I confused the input position with the output value.


Should the : between {1,2} and always be serialized without a leading whitespace and with a trailing whitespace?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Friday Morning
Development

No branches or pull requests