Re: [csswg-drafts] [css-images] Add easing functions to color stops (#1332)

I had a question about this syntax and how it should be interpreted. It seems like there are two paths (as discussed above); one is expanding the function to a number of stops, the other would be to do per-pixel sampling directly in the render/shader code?

If we expand to a number of stops what would the sample size be? Picking a sample size of, say, 9 (which firefox does for midpoints), might be fine for the majority of gradients but with a sharp timing function (e.g. `cubic-bezier(1, 0, 1, 1)`) or a gradient on a very large page you might see banding, right?

On the other hand doing per-pixel easing in the shaders would allow for extremely precise gradients with zero banding across very large sizes or sharp curves, but might increase implementation complexity and maybe degrade performance to resolve on every pixel? I don't know enough about the graphics pipeline to qualititively answer that so this is just a guess - perhaps someone else can clarify here. One definite downside is that polyfills/bundlers/etc would _have_ to sample either way. So perhaps a timing function should be pre-computed as opposed to per-pixel?

There exists a postcss plugin for easings (https://github.com/larsenwork/postcss-easing-gradients) which picks a sample size of 15 by default. That's quite a bit of extra CSS compared to 9 stops.

To illustrate the issue, if we look at `linear-gradient(to bottom, rgb(255, 0, 0), ease-out, rgb(0, 0, 255));` with 9 stops:

```css
linear-gradient(
  to bottom,
  rgb(255, 0,   0)   0%,
  rgb(201, 0,  54)  12.5%,
  rgb(157, 0,  98)  25%,
  rgb(121, 0, 134)  37.5%,
  rgb( 91, 0, 164)  50%,
  rgb( 65, 0, 190)  62.5%,
  rgb( 42, 0, 213)  75%,
  rgb( 20, 0, 235)  87.5%,
  rgb(  0, 0, 255) 100%
);
```

With 15 stops:

```css
linear-gradient(
  to bottom,
  rgb(255, 0,   0)   0%,
  rgb(226, 0,  29)   7.14%,
  rgb(197, 0,  58)  14.29%,
  rgb(168, 0,  87)  21.43%,
  rgb(137, 0, 118)  28.57%,
  rgb(110, 0, 145)  35.71%,
  rgb( 87, 0, 168)  42.86%,
  rgb( 65, 0, 190)  50%,
  rgb( 45, 0, 210)  57.14%,
  rgb( 29, 0, 226)  64.29%,
  rgb( 15, 0, 240)  71.43%,
  rgb(  5, 0, 250)  78.57%,
  rgb(  1, 0, 254)  85.71%,
  rgb(  0, 0, 255)  92.86%,
  rgb(  0, 0, 255) 100%
);
```

There's more repetition at 15 stops which implies it's a wasted sample size.

_Perhaps_ one solution to this is that the timing function could provide a step count. We could borrow the `steps()` function syntax, and expand it to including timing functions. Therefore web developers are able to fine-tune the number of steps, balancing their own needs; for a gradient easing spanning a wide midpoint on a large area they can ramp up the step count, but for small/many stops (i.e. mesh style gradients) they can lower the step count:

```css
linear-gradient(to bottom, rgb(255, 0, 0), steps(15, ease-out), rgb(0, 0, 255));

linear-gradient(
  to bottom,
  rgb(255, 0, 0),
  steps(6, ease-in),
  rgb(255, 165, 0) 30%,
  steps(12, linear),
  rgb(0, 200, 0) 65%,
  steps(4, ease-out),
  rgb(0, 0, 255)
);

conic-gradient(
  from 0deg,
  rgb(255,0,0) 0deg,
  steps(5, ease-in) 72deg,     /* large early bands */
  steps(20, ease-out) 180deg,  /* fine bands in the emphasis zone */
  rgb(0,0,255) 360deg
);
```

We could retain the simple syntax (`linear-gradient(to bottom, rgb(255, 0, 0), ease-out, rgb(0, 0, 255));` and perhaps specify that it resolves to a steps function with a pre-defined number of steps (e.g. it's shorthand for `linear-gradient(to bottom, rgb(255, 0, 0), steps(9, ease-out), rgb(0, 0, 255));`)

-- 
GitHub Notification of comment by keithamus
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/1332#issuecomment-3649356666 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Saturday, 13 December 2025 12:31:07 UTC