London

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.

The city is so old that the origins of its very name are unknown: it's first recorded title, Londinium, was provided by the Romans in the first years of the Common Era, but the etymology of the word is completely obscure.

This extends to some of its architecture: the London Stone, an irregular block of limestone at 111 Cannon Street, has been guarded by an iron grill for centuries, but no-one has known what the stone's purpose is, or why it is there, for the past 1000 years.

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 , 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 and matchMedia. (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 duplicated id 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