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-values] Proposal for a 'progress' function to calculate progress between two <length> values #7268

Open
johannesodland opened this issue May 11, 2022 · 17 comments

Comments

@johannesodland
Copy link

johannesodland commented May 11, 2022

The proposal is to add a new function to css-values-4 that calculates the progress between two <length> values in percent. The new function could be used together with the new mix function to improve readability of fluid sizes.

The syntax of the progress functions could be as follows:

progress(<input-length> ','  <min-length> ',' <max-length>)

All arguments are <length> values, while the return value would be a <percentage> value representing the progress of the input between the min and max values. Progress would be clamped between 0% and 100%.

The function could then be used together with mix() to calculate fluid sizes:

--progress: progress(100vw, 375px, 1920px);
font-size: mix(var(--progress), 24px, 32px);

This would work well with container query units as well

--progress: progress(100qw, 200px, 800px);
font-size: mix(--progress, 18px, 22px);

The proposed function is not new functionality, but would be syntactic sugar for

clamp(0%, 100% * (<input-length> - <min-length>) / (<max-length> - <min-length>), 100%)

Name to be bikeshedded.

Motivation

Fluid typography has been around for years, but the css necessary to achieve it is hard to create and read.

The clamp() function makes it possible to create fluid typography with less bloat: Example copied from article above:

font-size: clamp(2.25rem, 2vw + 1.5rem, 3.25rem);

It is still complicated to calculate the slope and intercept values necessary to create a fluid type that scales from a minimum to a maximum viewport size. It is not possible understand the min and max viewport sizes from reading the notation.

Preprocessors or other tools can be used to calculate the values, but this does not improve the readability.

The new mix funcion could let us interpolate between two font-sizes, as long as we have a progress argument:

--progress: progress(100vw, 375px, 1920px);
font-size: mix(var(--progress), 24px, 32px);
@Loirooriol
Copy link
Contributor

mix() doesn't clamp (#6701), so this probably shouldn't either.

And could also work with calculation values other than lengths.

@johannesodland
Copy link
Author

I guess this issue would be solved if we could have custom mathematical functions

@tabatkins
Copy link
Member

Having the ability to reproduce it by hand doesn't necessarily preclude us adding it in natively, if it's sufficiently useful and the manual reproduction is sufficiently annoying. I think this qualifies.

@johannesodland johannesodland changed the title [css-values-4] Proposal for a 'progress' function to calculate progress between two <length> values [css-values] Proposal for a 'progress' function to calculate progress between two <length> values Jul 14, 2022
@LeaVerou
Copy link
Member

I agree this should be easier.

Some thoughts that might help with naming/syntax:

Both mix() and the proposed progress() are basically linear range mapping operations. To take the example above:

--progress: progress(100vw, 375px, 1920px);
font-size: mix(var(--progress), 24px, 32px);

progress(100vw, 375px, 1920px) maps the [375px, 1920px] range to the [0, 1] range, and mix(var(--progress), 24px, 32px) maps the [0, 1] range to the [24px, 32px] range.

If we have ad hoc functions like that, I think it should somehow be obvious from naming that one is the inverse of the other: mix() interpolates (takes a percentage and gets you the corresponding value), and progress() does the opposite (takes a value and gets you the corresponding percentage). While I like progress() as a name, with the proposed naming they seem like entirely separate things. Perhaps mix-reverse() or mix-inv() or something? (tempted to suggest xim() and duck 🤣)

But what if we exposed the actual range mapping operation? We could have a map() function that takes two ranges and a value and maps the value from one range to the other. This would basically give us the result above in one fell swoop, without the intermediate percentage:

font-size: map(100vw in 375px to 1920px into 24px to 32px);

or

font-size: map(100vw in [375px, 1920px] to [24px, 32px]);

or something (syntax TBB).
Perhaps we could even have shortcuts to facilitate the common case where you want percentages as input or output.

Note that these could also be stored in variables to be reused for every conversion:

:root {
	--page-widths: [375px, 1920px];
}
/* ... */
font-size: map(100vw in var(--page-widths) to [24px, 32px]);

or even:

:root {
	--page-width-progress: 100vw in [375px, 1920px];
}
/* ... */
font-size: map(var(--page-width-progress) to [24px, 32px]);

@johannesodland
Copy link
Author

While I like progress() as a name, with the proposed naming they seem like entirely separate things. Perhaps mix-reverse() or mix-inv() or something?

I agree that the name should reflect that this function is the inverse of mix(), like asin() is the inverse of sin().
mix-inv() or inverse-mix()or something similar would be great 👍

(tempted to suggest xim() and duck 🤣)

🤣

But what if we exposed the actual range mapping operation? We could have a map() function that takes two ranges and a value and maps the value from one range to the other. This would basically give us the result above in one fell swoop, without the intermediate percentage:

...

font-size: map(100vw in [375px, 1920px] to [24px, 32px]);

A range mapping function would be great!

At the moment mix() does linear interpolation, and map() would do linear range mapping.
But; there's an open issue on adding easing functions to calc (#6697).

Easing could let us do something like

--progress: inverse-mix(100vw, 375px, 1920px);
font-size: mix(var(--progress) ease-in-out, 1rem, 1.25rem)

It would be great to be able to use easing functions in map as well, so the syntax should have room for extending in the future:

map(100vw in [375px, 1920px] to [24px, 32px] ease-in-out)

Also, while a map() function would be great, separate mix() and inverse-mix() functions would be composable with other functions.

@johannesodland
Copy link
Author

--progress: inverse-mix(100vw, 375px, 1920px);
font-size: mix(var(--progress) ease-in-out, 1rem, 1.25rem)

I seem to have provided an example where inverse-mix would not necessarily be the inverse of mix() 🤦😅

@Loirooriol
Copy link
Contributor

Just some notes:

  • progress() or whatever it's called should probably provide the input progress value, typically in [0, 1], but actually [-∞, ∞].
  • mix() is top-level. Something else will be needed for component values, see [css-values-4] Validity of generic interpolation function mix() #6700. I think progress() should also be for component values, so names like inverse-mix() can be misleading.
  • progress() can only be the inverse when the easing function is injective (e.g. linear). With the ability to specify the easing function, if it's not injective, we can't have a global inverse. It's not clear which of the possible input progress values should be returned. Also, easing functions may not be surjective (e.g. steps()), so what should progress() return for values outside the domain? I think it may be better to just avoid easing functions in progress() and always use linear.

@tabatkins
Copy link
Member

Yes, just determining the interpolation progress within a range, in a linear fashion, seems sufficient, and avoids all the complexities and incongruities of trying to invert an easing function, which as you note is not in general an invertable function!

@dbaron
Copy link
Member

dbaron commented Jul 15, 2022

It might be useful to provide a way to invert the easily-invertible easing functions that we have. I believe the inverse of a cubic-bezier(x1, y1, x2, y2), where 0≤y1≤1 and 0≤y2≤1, is cubic-bezier(y1, x1, y2, x2).

@Loirooriol
Copy link
Contributor

Note that cubic-bezier(1,0,0,1) in only invertible in [0,1]. I think y1 > 0 and y2 < 1 are also needed for the general case.

@johannesodland
Copy link
Author

There are discussions in #6245 for alternative ways of interpolating values between breakpoints. I still think a ‘progress’ or ‘position-of’ function would be useful for individual properties and more advanced calculations.

@johannesodland
Copy link
Author

Seems like progress() is in the css-values-5 ED now: https://drafts.csswg.org/css-values-5/#progress-func

@yisibl
Copy link
Contributor

yisibl commented Dec 29, 2023

In range input, progress has different calculation modes. In the following example, the right edge of progress is always in the middle of the thumb.

This prevents the rounded corner radius from changing and overflowing when dragged to the far left.

2023-12-29.16-30-27.mp4

With clamp(), you can see that the width of progress is not growing evenly, and I want the tooltips above to always be centered horizontally with the thumb.

.slider-progress {
  --p1: calc((var(--range) - var(--range-min)) / (var(--range-max) - var(--range-min)));
  --w1: clamp(28px, calc(100cqi * var(--p1)), calc(100cqi - 28px));
}
2023-12-29.16-31-01.mp4

My question is, how can I implement such an algorithm using clamp() or progress()? I think this is the scenario that progress() should consider.

@johannesodland
Copy link
Author

@fantasai @mirisuzanne Could this issue be closed now that progress() is already specced in css-values-5?

@mirisuzanne
Copy link
Contributor

While it's in the Editor's Draft, I don't see any official resolution from the WG. We would need to get that approval before we publish a new Working Draft (and close the issue). Seems the last time we brought it up (in #6245) was just to introduce the proposal, without asking for a resolution. If we think we're ready for a resolution, we could bring one of these issues back to the group.

@andruud or @danielsakhapov - I think you were exploring a prototype at one point, and had some questions that we might want to address? Are those documented as issues anywhere?

@danielsakhapov
Copy link
Contributor

@mirisuzanne I don't think we've found anything bad, apart from the fact that browsers will have a lot of work to implement it for non-length accepting properties, since none of them (my estimation) are ready to deal with cases like - number-producing math function with relative units dependency (I guess you can see it now with sign() function here).
But that's not a spec problem, just a delay for ship. I've already started fixing it for Chrome.

@andruud
Copy link
Member

andruud commented Jun 28, 2024

@mirisuzanne I think we should probably change this part:

The computed value of a value specified with <'animation-timeline'> is the computed <'animation-timeline'> and <easing-function> (if any).

and have it compute to the actual (numeric) progress of the timeline instead (post-easing). We generally resolve things at the earliest point they can be resolved, and I'm not sure what we gain from delaying the computation here except increased implementation difficulty.

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

11 participants