Another popular interface pattern, one that gained particular ascendancy after the release of iOS 7, is a “frosted” navigation bar that blurs page content scrolled behind it. It might appear that a CSS blur filter would be the easy and obvious way to achieve this on a web site, and it is, except for one vexing limitation: a standard CSS filter affects only content inside the element, not behind it. There is, as yet, no CSS effect would achieve a blur for elements underneath it, with the exception of the just-released -webkit-backdrop-filter
, an experimental property that is only available in Safari 9. However, there is a solution:
Making The Bar
For this version, the bar itself is an empty element with a fixed
position. The navigation goes on top of this element, using the same dimensions. Written in Sass, it could be expressed as the following, sans vendor prefixes:
#blurrycontent {
padding: 1rem;
top: 0; left: 0;
width: 100%;
height: 5rem;
overflow: hidden;
position: fixed;
filter: blur(4px);
}
nav {
@extend #blurrycontent;
filter: none;
text-align: right;
}
Turning The Page
The remaining content of the page goes inside a <main>
element, not within the <body>
, for reasons that will become clear in a moment:
<main id="content">
<h1>London</h1>
<p>With roots at least 7,000 years old, London is an accretion of artifacts old and new, from the remnants of wooden Neolithic settlements buried in the mud of the Thames to gleaming 21st century spires of glass and steel…
</main>
With the #blurrycontent
and <nav>
elements placed on top of each other in a fixed location at the top of the screen, the <main>
element is styled to take the rest of the content:
main {
margin: 0;
background: url(london_background.jpg);
background-size: cover;
padding: 2rem;
}
This works as far as it goes, but there’s no visible effect in the navigation area as the document scrolls upward. We’re about to change that.
Through a Glass, Darkly
As I said at the start , filter
only works for objects inside an element, not underneath it. So we’re going to take a copy of <main>
and place it inside #blurrycontent
using the very handy cloneNode
method, via a script added to the bottom of the page:
var pageContent = document.getElementById("content"),
pagecopy = pageContent.cloneNode(true),
blurryContent = document.getElementById("blurrycontent");
blurryContent.appendChild(pagecopy);
The effect will likely be barely visible at this stage, limited by the fact that the content in #blurrycontent
doesn’t scroll with the rest of the document. Let’s synchronize the motion of the two by adding a line to the script:
window.onscroll = function() {
blurryContent.scrollTop = window.pageYOffset;
}
Now the two are linked: scroll the page, and the matching content in #blurrycontent
moves at the same pace, made out-of-focus by the CSS filter. Because the <nav>
element is in a fixed position on top of #blurrycontent
, it remains unaffected.
City Limits
Obviously, taking a copy of a page’s main content and feeding it through a blur filter can tax both the browser and GPU, so you’ll want to be careful just how much content exists inside the <main>
element before you copy it. cloneNode
is a “live” copy method: any changes made to the original in the browser will be reflected in the blurred copy, although you may see a small delay as the two synchronize.
There are four final points to note:
- Because Internet Explorer doesn’t yet support CSS filters (and has abandoned its old proprietary version from older copies of IE) this won’t work in IE. The content will still scroll fine; you must won’t see any changes in the nav bar.
- You should be careful using fixed positioning on web pages in phones and tablets: until very recently, mobile browsers were notoriously bad at
position: fixed
, and locking off an area of an already small screen with your site navigation is pushy, at best. While the look may be derived from a mobile OS, it’s probably best to turn off the effect at small screen sizes, using a combination of media queries andmatchMedia
. (Alternatively, if you’re taking a mobile-first approach, don’t turn the effect on until the screen reaches a certain breakpoint.) - Accessibility should be carefully considered: screen readers interpret the DOM, not what you can see on the page, meaning that they will receive two copies of the page content by default. To avoid this, I’ve set an ARIA role on the
#blurrycontent
container:<div id="blurrycontent" aria-hidden="true"></div>
As a result, screen readers will see only the original content of the page, not the cloned copy.
- One should be careful in cloning elements with
id
attributes, as duplicatedid
values can lead to confusion and corruption in CSS and JavaScript. It works in this instance, but it is not a best practice.
Photograph by Swaminathan, licensed under Creative Commons.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/RNMbGG