I was reading Jake’s “Cross-fading any two DOM elements is currently impossible” which is a wonderfully nerdy deep dive into how there is no real way to literally cross-fade elements. Yeah, you can animate both of their opacities, but even that isn’t quite the same. Turns out there is a Chrome/WebKit-only CSS function call called -webkit-cross-fade()
that does the trick. MDN says it’s specced, but the implemented version differs, so it’s a bit of a mess… but it does exist:
.el {
background: -webkit-cross-fade(url(img1.svg), url(img2.svg), 50%);
}
I didn’t even know that was a thing.
The first thing I thought of was: if one of the images was just a blank transparent image, wouldn’t that apply partial transparency to the other one? And thus it’s kind of a proxy for background-opacity
(which doesn’t exist but feels like it should).
I gave it a test to see if it would work:
It seems to work! This is the meat of it:
.el {
background-image: -webkit-cross-fade(
url(image.jpg),
url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),
50%
);
That’s a 1px transparent GIF Base64 encoded.
It doesn’t work in Firefox but it does in everything else. Plus, you can test for support right in CSS and do something different if this isn’t just an enhancement.
@supports (background: -webkit-cross-fade(url(), url(), 50%)) {
/* Only apply the idea if supported, do the Firefox fallback outside of this */
}
That’s baked into the demo above.
You could also use an data:image/svg+xml URL including
<image href="…" opacity="50%">
, though sizing can be troublesome and you can’t use custom properties and potentially animate the opacity.If you really want to go off the deep end with this technique and get back background position and size stuff, you could put HTML inside the SVG like this:
All sorts of other crazy stuff you could do if you really wanted to. Composition has a habit of leading to some pretty wild consequences.
If you want to shave few byts off, you could also use svg
--transparent: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>");
I’d go for a CSS image to make it even shorter, and perhaps more understandable at a glance:
oooo that appears to work. that’s good – I also like the understandability. could name it like
--transparent-image-in-which-to-combine-with-others-to-fake-background-opacity
FYI: There’s a spec from css-images-4 that would allow:
image(transparent)
But for some reason browsers just haven’t bothered to implement much of anything from css-images-4 even though it’s been around for 5 plus years at this point.
https://www.w3.org/TR/css-images-4/#color-images
Tracking bug for Firefox implementation:
https://bugzilla.mozilla.org/show_bug.cgi?id=1657516
LeaVerou made a recent proposal for an @image syntax for css-images-5 that would allow CSS manipulation of images before use in background etc. This looks like the long term solution:
https://github.com/w3c/csswg-drafts/issues/6807
Could this work as a solution for Firefox?
There is a way of controlling the opacity of a
background
layer, but it’s only available in Safari. In fact, it’s been available in Safari for a over half a decade (check out the date of this article!), but no other browser has implemented it since.I’m talking about the
filter()
function. This takes an image, any image, and a CSS filter (in this case, we useopacity()
) and returns the filtered image (here, the original image made semitransparent):Here’s a live test with a repeating gradient on the
body
behind.