Filtered background with fallback for legibility
You know what’s hot right now? Big, high-quality, Retina®-crisp JPEGs, blurred into a header background:
The problem is if filters aren’t supported, the text risks being unreadable. That violates accessibility standards, and can ruin it even with perfect vision.
So you make a pre-blurred version of the image, but what if the design calls for dynamic blurring, like how Apple’s all about these days?
I happened upon a trick that lets non-filter
-supporting browers get a color overlay instead, which isn’t as pretty, but preserves readability:
The setup
I’m using a pseudo-element as a background, because filter
on that is better supported than filtering background-image
.
.backdrop { position: relative }
.backdrop::after {
content: "";
/* Stretch it across the entire parent element */
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
/* Shove it underneath the parent element's contents, otherwise it’s more of a foreground */
z-index: -2;
/* Display the background like you normally would on the parent element */
background: #222 url("inspirational-landscape-and/or-laughing-with-salad.jpg");
}
The filter
In this case, the design called for blurring and slightly darkening the backdrop.
/* Don't forget your prefixes, because Safari >9.1 and all Chromes still need -webkit- */
filter: blur(4px) brightness(75%);
But we can’t do only that, because browsers that don’t support filter
risk The Illegible.
The trick
Did you know there’s an opacity()
filter effect? It didn’t seem very useful to me compared to the opacity
property, but its existence enables a clean fallback:
.backdrop::before {
/* Do the stuff from earlier to make it act like another background */
content: "";
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
/* Position it on top of the other pseudo-element, but still behind the element’s contents */
z-index: -1;
/* Make it dark enough that the text will show up over any given image */
background: rgba(0,0,0, 0.5);
/* Use a filter to... completely hide it?? */
filter: opacity(0%);
}
If filters are supported, then the pseudo-element with the image is blurred and darkened, and the other with the dark color becomes invisible. If filters aren’t supported, then things aren’t as pretty, but the text is legible.
I wrapped it up in a pen, if you prefer:
This trick will probably come in handy for other uses of filter
, so I’ll keep it in my back pocket.
Why not use @supports
instead?
As mentioned by @iamvdo on Twitter, the handy-dandy feature-detection @supports
rule has been implemented in roughly the same browsers that support filters, which could make your code clearer and less hacky:
@supports (filter: blur(4px) brightness(75%)) or (-webkit-filter: blur(4px) brightness(75%)) {
/* NOW do stuff that requires filters to work */
}
You can do that instead. I crunched CanIUse’s usage data and my trick works in a few places @supports
doesn’t:
- Chrome 18–27
- Safari 8.x
- UC Browser 9.9 (current as of this writing)
The combined usage share as of May 2016 is ≈3.6% USA and 10.5% worldwide. These numbers will decrease with time, and we’re using these filters as an enhancement, so @supports
can be preferable if you want explicit CSS.