• 10 . 03 . 10
  • A novel CSS+Javascript effect that allows a site to blend from one full-page image to another as the visitor scrolls down the page.

  • Tags

    , , , , ,

  • StumbleUpon

CSS Technique: Morning Sunset

After coming across an excellent article on generating full page images, I had the idea that it would be cool to blend between multiple images as a user scrolled down the page. The somewhat artistic conceit that a user could scroll a similar scene from morning to sunset, ironically came to me at sunset on Saturday and was finished by morning!

The effect is pretty simple to set up, though does require a specific bit of markup. We start with 2 images and set a z-index on them, such that the first one is in front of the second. With the full page image CSS rules, the first image fills the screen and the second is completely obscured.

<img id="morning" class="bg" style="z-index: 2;" src="morning.jpg" /> 
<img id="sunset" class="bg" style="z-index: 1;" src="sunset.jpg" /

For the CSS, In addition to the standard full page image rules, I add an additional higher z-index, plus a little bit of transparency This allows the content to sit on top of both of these images, and for the effect to be more pronounced.

div#content {
	/* This is the only important rule */
	/* We need our content to show up on top of the background */
	position: relative;
	z-index: 10;
 
	/* Added some opacity to demonstrate the effect better */
	opacity: 0.8;
	filter: alpha(opacity=80);
}

So far, so what? We’ve arranged for a user to download an image he can’t see – not so good. The magic comes with a blending function tied to the scrollbar. The idea is that the top layer becomes more and more transparent as the user scrolls through the content.

In working with the Zend Framework, I’ve been getting to grips with Dojo and its supporting classes, so I was happy to see that Dijit had tools for getting the dimensions of the viewport. With this information, I was able to calculate the scroll ratio, which gave me a number ranging from 0 at the top of the page to 1 at the bottom.

Dojo also has a great style() function, which allows you to set opacity and have it “just work”, across all browsers, regardless of their non-standard filter() shennanigans. At the outset, the top layer image has an opacity of 1. Subtracting the scroll ratio from this allows it to be fully opaque at the top of the page, and fully transparent at the bottom.

dojo.subscribe("/window/scrolled", function(e){
	// Calculate the scroll percentage, and adjust the opacity of the top layer, appropriately.
	var vp = dijit.getViewport();
	dojo.style("morning", {
		"opacity": 1 - (vp.t / (document.documentElement.scrollHeight - vp.h))
	});
});

There is some rate-limiting code going on to prevent the event firing continuously and slowing down the page. That came from a helpful Dojo Cookie by Peter Higgins over at Dojo Campus. I haven’t played fully with the rate limiting yet, but 50ms seemed to give a reasonable balance of subtle movement without overloading the page.

Degradation is variable – without Javascript, the user just sees the top image and there’s some overhead of the second image that is never seen. Without CSS, there’s more of a problem, as the images are inlined in the page, which pushes all the content down. If there is a full page image solution using background images only, I’d love to hear about it.

There’s a working, self-describing example of the effect so you can try it out for yourself. It works best in Chrome, with its superior Javascript handling, but works in all modern browsers to a reasonable degree.

The next step is to generalise the code, so that I can pass it an array of image URLs and have it automatically build the markup necessary to generate the effect. With more work on the blending function, I’ll be able to have it blend between multiple layers and potentially follow different rates – I might investigate Dojo curves for that.

I’m not aware of this technique being described anywhere else, but if there are other approaches to doing this, I’d be interested to see them.