-
Notifications
You must be signed in to change notification settings - Fork 695
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-images-4] Clarifying CSS gradient rendering for edge cases involving "longer hue" interpolation #11381
Comments
To follow up, here is what I think my codepen example ought to look like, by my reading of the spec: Currently, none of the three tested browsers produces this rendering. So if my understanding is correct, and assuming we don't want to modify the spec in the light of what browsers are actually doing, then all three engines have bugs here. |
It occurs to me that it should be possible to emulate a I made a copy of my codepen example using this, and this does indeed render all the examples as I expected(*). I'm feeling increasingly sure that the current behavior we see of "projecting" the So unless I'm missing some extra part of the spec that supports these behaviors, I'm intending to file bugs against the browsers and create some WPT tests to check the behavior. (*) Except that Safari renders swatch (l) in solid red instead of blue, as mentioned at the end of the original description. That is surely a webkit bug. |
I would be very very worried about changing the appearance of sites that have come to depend on the current behavior, though maybe not many do. Is it possible to craft a query against HTTPArchive that looks for the gradient syntax that you are testing (i.e. gradient styles that hit the conditions of interest). I would try to change the spec rather than browsers if this turns out to be something that has significant usage. Even though this does look to me like some kind of browser problem with the treatment of identical or near-identical stop colors. |
It's not "identical or near-identical stop colors" that are at issue here; it's what browsers do with |
I have gone ahead and filed bugs against all three engines, as ISTM their behavior is a clear violation of the spec, and doesn't really make any sense from an author's point of view either. |
I wonder if this Chromium bug that my apprentice @DmitrySharabin recently found could also be relevant. |
That looks like an unrelated color-mixing/interpolation bug, I think. |
The new tests here are reftest versions of the test swatches from w3c/csswg-drafts#11381. The references for existing "single-stop-longer" tests are also changed, because they were based on a faulty interpretation; the spec does not call for any kind of gradient to be extrapolated beyond the first and last defined color stop positions, so a "single stop" gradient actually renders a single solid color. Differential Revision: https://phabricator.services.mozilla.com/D233217 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1939948 gecko-commit: fcdb3710b9837f0de8fd19cfe533f9fba8d010d0 gecko-reviewers: longsonr
…. r=longsonr The new tests here are reftest versions of the test swatches from w3c/csswg-drafts#11381. The references for existing "single-stop-longer" tests are also changed, because they were based on a faulty interpretation; the spec does not call for any kind of gradient to be extrapolated beyond the first and last defined color stop positions, so a "single stop" gradient actually renders a single solid color. Differential Revision: https://phabricator.services.mozilla.com/D233217
The new tests here are reftest versions of the test swatches from w3c/csswg-drafts#11381. The references for existing "single-stop-longer" tests are also changed, because they were based on a faulty interpretation; the spec does not call for any kind of gradient to be extrapolated beyond the first and last defined color stop positions, so a "single stop" gradient actually renders a single solid color. Differential Revision: https://phabricator.services.mozilla.com/D233217 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1939948 gecko-commit: fcdb3710b9837f0de8fd19cfe533f9fba8d010d0 gecko-reviewers: longsonr
…. r=longsonr The new tests here are reftest versions of the test swatches from w3c/csswg-drafts#11381. The references for existing "single-stop-longer" tests are also changed, because they were based on a faulty interpretation; the spec does not call for any kind of gradient to be extrapolated beyond the first and last defined color stop positions, so a "single stop" gradient actually renders a single solid color. Differential Revision: https://phabricator.services.mozilla.com/D233217
The new tests here are reftest versions of the test swatches from w3c/csswg-drafts#11381. The references for existing "single-stop-longer" tests are also changed, because they were based on a faulty interpretation; the spec does not call for any kind of gradient to be extrapolated beyond the first and last defined color stop positions, so a "single stop" gradient actually renders a single solid color. Differential Revision: https://phabricator.services.mozilla.com/D233217 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1939948 gecko-commit: fcdb3710b9837f0de8fd19cfe533f9fba8d010d0 gecko-reviewers: longsonr
I'm worried that this will break the compatibility of the site. Currently, there are many places that use .foo {
background: linear-gradient(to right in hsl longer hue, red 0 0);
} With this proposed change, this fails to produce a gradient and will only show up as a red background. This breaks compatibility with existing sites and CSS authors must change the CSS above to: .foo {
background: linear-gradient(to right in hsl longer hue, red 0 100%);
/* or */
background: linear-gradient(to right in hsl longer hue, red, red);
} So, can we relax the restriction so that a gradient can be generated when there is only one color stop.
cc @LeaVerou @mysteryDate is implementing |
That example isn't one color stop, tho - it's two color stops (two positions with one color, defining two stops). You say "many sites" are relying on this to generate rainbows, but I don't actually see rainbows very much in practice on the web. Is this just a cute CSS trick that has gotten some shares, or do we have evidence it's actually used in a non-trivial number of sites (and, ideally, have evidence that rendering them as a solid color would actually harm the rendering, rather than just change the rendering)? That said, I'm not strongly opposed to specifying that we always add one additional color stop at the beginning/end of the list, placed either at 0%/100% or on top of the existing first/last color stop if they're beyond the 0-100% range, and with the same color as the first/last stop. If this does indeed look to be web-required, that's acceptable, but I'd prefer to avoid it if we can, and just fix the browser bugs. As an aside, we should also probably think about exactly what behavior we do want between a double-position stop like that. Double-position stops were added to make it easy to generate solid-color strips in a gradient without having to repeat your color; I suspect that people might expect that to remain true even if they're doing longer-hue interpolation otherwise. |
Hmm, yes, I suppose that might take people by surprise. The spec text currently says that:
which means that The spec goes on to mention that:
Perhaps a note should be added here clarifying that this does not apply to |
FWIW, we fixed this gradient-rendering behavior in Firefox 136 (currently in Beta), so that the regions before the first and after the last color stops now render solid colors, not an "extra" full-cycle gradient. This also means, of course, that While it's still early days (this has only recently reached the Beta channel, and won't reach full Release until early March), we have not yet seen any breakage as a result. It's true that there are "demos" such as @yisibl mentioned which will no longer work, but it should be simple for their authors to change to the correct form of gradient definition: just use I'm pretty skeptical that such "rainbow gradients generated from a single position" are something the web really depends on to a significant degree. I would much prefer to see browsers fix their rendering bugs rather than enshrine this illogical quirk as a special case in the spec. |
Yeah, on further thought I think I'd prefer to avoid complicating this, too. The note just needs an amendment, as you say. |
I'm definitely in favor of it! Also, hopefully some examples can be added for rainbow gradients to avoid more misunderstandings. |
After rethinking, I also agree that it would be better for browsers to fix it, which prevents the specification from getting complicated and reduces the learning cost for users.
I raised the concern that it could lead to broken compatibility in #issuecomment-2652590989 above. I've done more research for this and this is the initial conclusion. Looking back, since all three browsers implemented the specification incorrectly. This led to a misunderstanding by many CSS authors that Thankfully, this feature may not be used by many people at the moment, and it's not too late. @CGQAQ and I will be working on fixing this in Chrome(See patch 6247246). And possibly add a Chrome UseCounters to count exactly how many sites are affected. If necessary, we can issue a warning in the console In addition, I've collected some data to make it easier to assess the impact. Potentially Affected SitesHere are some real use cases found through GitHub searches:
Affected front-end build toolsStarting with esbuild 0.19.9, support for converting gradients with /* input */
.test-longer {
background: linear-gradient(in hsl longer hue, red);
}
/* esbuild output */
.test-longer {
background: linear-gradient(red, #ffbf00 12.5%, #ffef00, #dfff00 18.75%, #7fff00, #20ff00 31.25%, #00ff10, #00ff40 37.5%, #0ff, #0040ff 62.5%, #0010ff, #2000ff 68.75%, #7f00ff, #df00ff 81.25%, #ff00ef, #ff00bf 87.5%, red);
} Opening esbuild's test page in Firefox 136+, we found that this compatibility is broken. I can file an issue for
|
@jfkthame For cases smaller than 1px, Firefox's rendering is not quite the same. Firefox renders as red, other browsers are gradient colors. data:text/html;charset=UTF-8,<!DOCTYPE html>
<style> div {
width: 200px; height: 50px; border: 1px solid black;
background:linear-gradient(90deg in hsl longer hue,
transparent 0 var(--x), red var(--x) 100px, transparent 0 100%);
--x: 99px;
}
</style>
<div></div> This only seems to happen when the width is greater than 128px, and by zooming in we can see this more clearly. data:text/html;charset=UTF-8,<!DOCTYPE html>
<style>
div {
--w: 100px;
width: var(--w); height: 5px; border: 1px solid black;
background:linear-gradient(90deg in hsl longer hue,
transparent 0 var(--x), red var(--x) calc(var(--w) / 2), transparent 0 100%);
--x: calc(var(--w) / 2 - 1px);
zoom: 10;
}
div:hover {
--w: 130px;
}
</style>
<div></div> |
For what it's worth, though the spec has existed for a long time, CSS hue interpolation methods have only been available in browsers for less than two years: https://caniuse.com/mdn-css_types_gradient_linear-gradient_hue_interpolation_method It was only shipped in Firefox last summer.
I agree with this approach. |
This CL added counts of Rainbow gradient pattern[1] that will not work after this CL[2] [1]w3c/csswg-drafts#11381 (comment) [2]https://crrev.com/c/6253537 Bug: 387475844 Change-Id: I2db9c1e7e8dfd37fa7f0322ae3d9fc162a558128 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6320615 Reviewed-by: Rune Lillesveen <futhark@chromium.org> Reviewed-by: Steinar H Gunderson <sesse@chromium.org> Commit-Queue: Jason Leo <m.jason.liu@gmail.com> Auto-Submit: Jason Leo <m.jason.liu@gmail.com> Commit-Queue: Rune Lillesveen <futhark@chromium.org> Commit-Queue: Steinar H Gunderson <sesse@chromium.org> Cr-Commit-Position: refs/heads/main@{#1427612}
…te about color before/after the stop lists. #11381
All right, at least for now I've updated the note about double-position creating a stripe, and added a note emphasizing that the spec means what it says about the color before the first stop and after the last stop. ^_^ Agenda+ to confirm that this should be closed No Change otherwise. |
That change looks correct to me. |
I am seeing differences between Firefox, Chrome and Safari regarding how gradients with the "longer hue" interpolation method are rendered. I think I understand what the spec expects, but none of the browsers tested get this right, so I'd like to confirm my interpretation — and perhaps some clarification or additional examples may be needed in the spec.
Testcase: https://codepen.io/jfkthame/pen/azopZOx
(Note that there have been some recent patches to gradient rendering in Firefox. The description below refers to current Nightly builds; older versions have some different and less self-consistent results.)
This renders a number of 100px-wide gradient swatches, all of them defined as linear-gradient(to right in hsl longer hue, ...) with two color stops, red and blue, at a variety of positions. (The red and blue tick-marks above and below each swatch indicate the positions of the color stops defining the gradient.)
Results I'm seeing in Firefox Nightly, Chrome Canary, and Safari Tech Preview:

The simplest case is (a), with the red stop at the left edge and the blue stop at the right. No issues here: we simply render a gradient from red to blue, going "the long way around" the HSL wheel.
What happens if the first and last stops defining the gradient do not cover the full extent of the area to be rendered? In (b), the blue stop is in the middle, so what happens in the right-hand half of the swatch? According to https://drafts.csswg.org/css-images-4/#coloring-gradient-line,
which seems to imply that the right-hand half of (b) should be solid blue. However, all three tested browsers actually paint a further gradient here, with a full cycle of the HSL wheel from the blue stop at 50px all the way around to blue again at the 100px edge. I presume the browsers are adding an "implicit" blue color stop at the right-hand edge, and then performing a longer-hue interpolation between the actual blue stop at 50px and that implicit one. I don't see any justification in the spec for this behavior: I think it's a bug, but given that all three browsers behave this way, maybe I'm missing something?
Example (c) is similar: here, both the red and blue color stops are moved in from the edges of the swatch, so I think the expected rendering should have 20px of solid red at the left, and 25px of blue at the right, with the longer-hue gradient from red to blue in between. But all three browsers also render a complete 360° gradient in both the left and right portions, outside the defined color stops. Bug?
(Aside: the description of linear-gradient on MDN https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient
may be a bit misleading here. That first sentence might be understood to imply creating an extra stop at 0%, duplicating the first declared color, rather than simply painting that color from its stop position back to 0%; and analogously, although the wording is less ambiguous, an implementer might think they should duplicate the last declared color at 100%. And that would indeed result in the observed behavior. But if my understanding of the actually-expected behavior is correct, I'll suggest rewording this for better clarity.)
Interestingly, in example (d), where the blue stop is moved beyond the right-hand end of the swatch, Chrome and Safari suddenly switch their behavior and now do not use a gradient in the area to the left of the red stop: they turn this into solid color. (Firefox, on the other hand, continues to apply a gradient there, just like in the preceding examples.) I think the Chrome/Safari behavior here is what the spec requires, but I don't understand why they only do this correctly once the blue color stop is beyond the end of the rendered area.
Example (e) shows the equivalent case with the red stop placed beyond the left edge: now, Chrome and Safari render a solid color for the area beyond the blue stop, while Firefox still paints a gradient there.
Examples (f) and (g) serve to illustrate the abrupt change in behavior in Chrome and Safari. The two stops are just shifted right by 1px in (g) compared to (f). In Firefox, this just results in a barely-perceptible shift in the gradient; but in Chrome and Safari it drastically changes the result.
QUESTION 1: Am I right in thinking that any area before the first color stop or after the last color stop should simply render as a solid color, or is longer-hue interpolation expected to somehow "project" into these areas (as the browsers currently do, though not entirely consistently)?
The second group of swatches, (h) through (l), have the two color stops at the exact same position. My reading of the spec is that the correct rendering would have solid red to the left of the stop position, and solid blue to the right. But all three browsers actually render a complete 360° gradient: to the right of the color stops, they render blue-to-blue (all the way around the hue circle), and to the left they render red-to-red (all the way around). For (h) through (j), this result is consistent across all the browsers, and appears to result from adding "implicit stops" at the left and right edges of the swatch, duplicating the color of the nearest real stop.
In example (k), the position of the red and blue stops has been moved beyond the right-hand edge of the swatch. In Firefox, this "stretches" the gradient to the right; it still begins with red at the left edge, but the visible gradient no longer cycles all the way back to red because the stop is outside the swatch area. In Chrome and Safari, on the other hand, the end of the 360° gradient seems to be "clamped" to the right-hand side of the swatch, so (k) looks identical to (j). I can't see any justification for that behavior — though I think the browsers are wrong to be rendering a gradient at all in this case.
Finally, in (l) the stops have been moved before the left-hand edge of the swatch. Again, Firefox stretches the gradient; Chrome "clamps" the left edge, so that (l) is identical to (h). Safari renders this example the same as (j) and (k), which I think is a bug.
QUESTION 2: Is a longer-hue linear gradient with stops at a single position a special case that should render some kind of gradient (as all browsers currently do, regardless of where the stops are positioned), or is that just an implementation bug?
(Note that there are a couple of existing WPT tests that expect this behavior; e.g. see http://wpt.live/css/css-images/gradient/gradient-single-stop-longer-hue-hsl.html. I'm suggesting this may be wrong. Does the web depend on it?)
The text was updated successfully, but these errors were encountered: