Open meyerweb opened 4 years ago
Perhaps we should really just add pseudo elements for background (and foreground) layers and use the same (SVG) properties everywhere.
https://discourse.wicg.io/t/pseudo-elements-for-stacked-box-layers/4193
body::background {
repeat: repeat-x;
position: absolute; /* fixed*/
}
body::layer(1) {
content: url(waves.png);
position: center; /* top: 50%; left: 50%; */
filter: none;
}
body::layer(2) {
content: url(waves.png);
position: 0 50%; /* top: t0%; left: 0%; */
filter: blur(0.2px) opacity(0.8);
}
body::layer(3) {
content: url(horizon.jpg);
position: 50% 100%; /* left: 50%; bottom: 0%; */
filter: blur(1px);
}
As Eric notes, this use case was supposed to have been addressed by the filter()
image function (which takes a CSS image + a list of filters and returns a new image with the filter applied, and which could be used anywhere a CSS image is accepted).
I believe WebKit has an implementation of filter()
, not sure how comprehensive it is. No one else has implemented it, to my knowledge, even behind a flag.
If a background-filter
property is more likely to be implemented, then it would address the most common use cases for filter()
. There isn't a strong demand for filtering list images, after all. But filtering mask images would be useful, though. And when support for CSS images in SVG fill & stroke finally happens, filtering them would be useful, too. All of which could be supported by similar paired properties (fill-filter
, mask-image-filter
, etc.) if that's the route we want to take.
It might be slightly more author-friendly to have a separate property (or properties, considering the other use case) instead of trying to jam everything into background-image
. But I think most CSS authors would just be happy for an easy way to do blurred backgrounds or semi-transparent background overlays, without having to hack around with absolutely positioned pseudo-elements.
No one else has implemented it, to my knowledge, even behind a flag.
Not sure how we missed the filter()
function - it looks very useful. It was quite easy for us to do as it turns out, at least for bitmap images - so now there are two implementations!
I agree filter()
seems much more versatile - careful use with border-image
could give you a drop-shadow on the border alone, which might be useful. You could also use it within an image()
function specifyng multiple resolutions - for instance, applying a 10px blur at higher resolution, and 5px otherwise. You couldn't do this with background-filter
.
(bit off topic, but this last one is quite interesting for us. All the CSS to print engines that I know of have a custom property allowing you to specify the resolution that the SVG filter is supposed to apply at: generating a filtered image at 96dpi isn't really an option for print. Combining image()
and filter()
would allow a standardised way to achieve the same results, at least in some cases)
On the other hand, background-filter
would be applied after compositing multiple background layers into one before filtering, which isn't an option with the filter()
method(*), so it's not a direct replacement.
(*) possible with an SVG filter containing multiple feImage primitives, but impractical.
@Crissov a full pseudo-layer for backgrounds is quite a bit more work, not least because you'd then be able to set properties on it - z-index
, border-radius
, background
, border
etc. - and each of these would need to be excluded or have their behaviour defined. Same for the border
pseudo-element you mentioned on your wicg page, which is particularly problematic for tables. It's a very big hammer for a use case which, as Amelia points it, is largely wanting to blur or add opacity.
Is this unique from backdrop-filter
? See Example Link
@una Yes. This applies filters to the background images, instead of filtering the backdrop behind those images. The closest analogue is the filter()
function, which is a better solution by dint of being more broadly usable; see https://www.w3.org/TR/filter-effects-1/#FilterCSSImageValue for details.
The CSS Working Group just discussed background-filter
, and agreed to the following:
RESOLVED: Close #4736 and #4715 as no-change; filter() is what's intended to solve these cases.
@meyerweb CSSWG resolved to close this as no-change, in favor of using the filter()
function. You may want to read the minutes above. Does this work for you, or do you think we need to reconsider something?
Note that the use case of filtering all the layers of the background composited together, or or filtering the images after they've been stretched is not handled by using filter()
, it would need a non-list-valued background-filter
property.
I suppose another thing that's not handled by filter()
is just the poor cascading ergonomics: you can't cascade the filter independently of the image source. Which is often useful to do.
You can use variables to handle that, if you need it - set that layer as var(--bg, url(...))
, then set in :hover --bg: filter(...);
.
But yeah, "making the entire bg stack partially transparent" does need something other than filter()
. Solving that completely probably requires porting over SVG's filter tree (I have a rough draft of a proposal for that!), but we probably can hit a large chunk of the use-cases with a single-valued background-filter
that just applies a filter to the whole composited stack, yeah.
Regarding filter() on images that have been stretched, I've been doing some testing on this (see https://github.com/web-platform-tests/wpt/pull/23429/files).
In Webkit and now our implementation, the filter is applied after any stretching that occurs in background-image
. I think this is almost certainly correct - for example:
div { background: filter(url(small-20x20.png), blur(5px)) left top / 200px 200px }
should give a 5px blur as specified by the user, not a 50px blur because the image has been scaled up by a factor of 10.
I suppose you could make this argument either way, but after a few days working with it this approach seems the right one. It makes resizing or substituting an image easier, and it means you can apply a consistent blur to image-set()
, for example, without having to worry about the resolution of the images. It's also much easier to apply to border-image
, where scaling is less intuitive.
Either way, the approach has to be consistent for all cases where images can be specified as a url()
and have their size overridden: background-image
, mask-image
, border-image
, mask-border
and content
(did I miss any?). Webkit does this with background-image
and mask-image
, doesn't with border-image
and doesn't yet support images as replaced content, apparently. No other browser support filter()
yet.
Aside: although not strictly relevant to the original issue, an example we've been working on is at https://jsbin.com/xisohum/edit?css,output - testing filter()
on border-image
and background-image
, as well as initial-letters
. It largely works in Safari, although there are a few issues - note the much-less-than-specified blur on the frame, due to the filter being applied to the very high resolution image before scaling:
Here's how we render it:
Tomorrow's technology: bringing you the look of yesterday, today.
In Webkit and now our implementation, the filter is applied after any stretching that occurs in background-image. I think this is almost certainly correct - for example:
That is surprising!
I agree that the effect is likely what authors want, but it strongly feels like a layering violationg - filter()
should return an image, which is then stretched by background-image
. Instead, I guess filter()
is returning an "image with filtering instructions", which is then stretched by background-image
, and then the "filtering instructions" are applied before the image is composited with other layers?
If that's what we want, we'll have to be very careful to describe that correctly, along with the timing instructions for when, precisely, to apply the filtering instructions.
it means you can apply a consistent blur to image-set(), for example, without having to worry about the resolution of the images.
I'll note that this happens no matter what - image-set() returns an image with resolution-adjustment already applied, so if one of the options is twice the size but 2x resolution, it'll look the same size to future steps. (At least, conceptually.)
TBH it surprised me too, and I had the same recoil regarding the operations being in the wrong order. But treated as an image-with-filtering-instructions (which is exactly what we do) it works out nicely.
But the topic is wandering so I'll raise another issue to continue discussion.
@meyerweb CSSWG resolved to close this as no-change, in favor of using the
filter()
function. You may want to read the minutes above. Does this work for you, or do you think we need to reconsider something?
I think filter()
works fine for the use case I had in mind, though the comments subsequent to yours (from you as well as others) do point to use cases I hadn’t had in mind, and seem to me to speak to a need for a new property or properties. (And I love your illustrations, @faceless2!)
I’m proposing a property:
background-filter
. It allows the application of filters to background layers, parallel to the waybackground-blend-mode
applies blending modes.As an example:
Unless browsers would like to just implement https://www.w3.org/TR/filter-effects-1/#FilterCSSImageValue, of course. That would address the need, but much more generally. (Thanks to Dan Wilson for the pointer!)