Blurry Transparent Header Effect from iOS7 in CSS

Guest Author //

The following is a guest post by Fabrice Weinberg. Fabrice is a regular contributor around here. He's always looking forward to the latest technologies and possibilities. Here on the outset of iOS7, he looks into recreating a particular effect that you might not guess is even possible on the web. It's tricky, uses bleeding edge features, but doable!

Let's start with the backstory. @simurai asked a while ago if it where possible to do a blur effect on arbitrary DOM nodes to simulate the iOS7 Translucent Effect on the Control Center (you can find his Pen here).

I won't explain exactly how to build this effect for the Control Center but rather focus on how to build something similar to the blurred header effect you can find in iMessage and many other apps in iOS7. But the tricks you will learn will make it possible to build the Control Center effect too.


iMessage on iOS 7

Used technologies

This solution makes heavy use of CSS Regions which is still a working draft but thanks to Adobe it is implemented in WebKit/Blink today.

With the release of iOS7, Mobile Safari is the first browser to enable this feature by default (still -webkit- prefixed). In Google Chrome 29 you just need to enableexperimental-webkit-features. in Google Chrome 30+ it is experimental-web-platform-features under about:flags.

CSS Regions

Quick introduction to CSS Regions: it is all about splitting the structure of the page from the content. There are two new CSS properties flow-into: named-region; and flow-from: named-region; these two are used to defined a so called named-region. You can think of it as a variable which will hold arbitrary content.

The element with flow-into on it is like a reservoir of content. That content "flows" into other elements with flow-onto on them.

A simple example:

See the Pen %= penName %> by Fabrice Weinberg (@FWeinb) on CodePen

Assuming your browser supports regions, you'll be able to see the structure is separated from the content. Using flow-into: content the content from the div with the class .main-content is flowed though both divs with the class .region using flow-from: content;

For a more in depth look at CSS Regions read the great article by Arno Gourdol CSS3 regions: Rich page layout with HTML and CSS3

Getting Started

Let's begin by defining the HTML structure. It is quite simple. We need some sort of a header and a container for the messages. Because of the use of CSS Regions, another container is needed to hold the content which will flow though the header and message container.

See the Pen # by Fabrice Weinberg (@FWeinb) on CodePen

There is CSS here to blur the .header.

Like in the first example, everything that doesn't fit in the .header flows though the .content-container.

That is not the behavior we are looking for. We want to have the whole content in the .content-container and only blur content that is "hidden" by the .header. Let's fix that.

Using CSS Regions to "beam" the DOM

This part is kinda hacky because the behavior of putting overflow-y: auto on an element that gets the CSS property flow-into: named-region; isn't described in the spec and thus dependent on the implementation.

CSS regions allow us to define the flow of arbitrary DOM nodes and even let us add overflow-y: auto to make the content scrollable. Additionally, there is the need of a defined height.

Let's use the sample from above, adding a fixed height and overflow-y: scroll to the .main-content:

See the Pen %= penName %> by Fabrice Weinberg (@FWeinb) on CodePen

That doesn't look that good yet. Scrolling is working but the .header stays empty.

The fun part comes now, we are about to "beam" the content from .main-content to the .header element. This can be done with CSS3 and the ability to transform the whole content layer of the .main-content by using transform: translateY() to move the .main-content upwards by the amount of the .headers height.

See the Pen %= penName %> by Fabrice Weinberg (@FWeinb) on CodePen

That looks better! But still something looks wrong. There is always some content hidden by the .header now. To fix this we just have to add the height of the .header as padding-top to the .main-content to accommodate for the space taken.

See the Pen b8cc5b9a310ff94de8c707c24a3c99d0 by Fabrice Weinberg (@FWeinb) on CodePen

That's it. This is the core technique used to achieve the iOS7 translucent/blurry effect. Based on this technique, I built this little playground where you can customize the header and even add a tint color.

See the Pen iOS7 Translucent CSS by Fabrice Weinberg (@FWeinb) on CodePen

Performance Matters

Let's have a look at the render performance of this effect:

Chrome DevTools "Before" Timeline
Hint: Use the Debug View on CodePen for measuring timelines. Be sure to disable all your plugins. Just navigate to http://codepen.io/[username]/debug/[penid]. Remember this is only works for Pens you own and while logged in.

Above you see a timeline taken while scrolling up and down, you can see that nearly half the time we have per frame (16.66ms) is used for painting. Before we look into that I want to tell you about a great new feature in the Chrome DevTools called Layers. It's a new tab in the DevTools where you can see what elements Chrome promotes to its own layer in a document. It is still experimental so you have to enable the devtools-experiments flag in about:flags. Be sure to restart Chrome afterwards. Then to activate the Layers experiment open the dev tools click on the wrench icon in the lower right corner (or just hit ?) to open the settings.

Enabling the Layers Panel

Using The Layers Tab

Above you see the Layers tab open. Chrome created just one layer for the whole document, that means that any change on the page will trigger a repaint of the whole document. That is expensive! You may have noticed the checkbox in the upper left corner. It is used to apply transform: translateZ(0) on the .header element. You can find the Pen here. The transform: translateZ(0) hack is used to promote an element to its own layer on the GPU.

Hint: You can rotate the Layers in the Layers Tab by dragging with the mouse.

As you can see there is an additional layer for the div.header now. Let's do another timeline, now using the transform: translateZ(0) hack:

Chrome DevTools "After" Timeline

This looks so much better. Paint costs are only approximately 1/10th of the overall time per frame now. With the new layers tab it is now much easy to see where Chrome created layers and to see potential performance bottlenecks.

Wrapping It Up

It's fun to see that this is possible today in WebKit/Blink. But (there is always is a but) this technique is not ready for production. Though it's working quite well on the desktop it's not working on iOS7 at the moment, it's slow and scrolling is not working at all.

I created a GitHub Repository where you can find the source for the playground.

If anyone has any idea on how to archive scrolling on iOS7 (preferably without JavaScript) I would like to know! I hope you have learned one or two new tricks. Thanks for reading!