Blog (my web musings)

Find out what's interesting me, get tips, advice, and code gems that don't fit elsewhere.


Search Blog


Want a simple, responsive cross-fading slideshow, with images that are lightweight AND retina-ready? You got it, with the Responsive CSS3 Cross-Fading Slideshow:

Demo

This post covers 2 topics that can be used separately OR combined. First up... the slideshow.

Responsive CSS3 Cross-Fading Slideshow

This simple slideshow stands on its own merits. It's powered with a few lines of JavaScript that sequentially adds and removes a class on a series of divs. The class applies a cross-fade effect to the divs (and their background-images), achieved by animating opacity with CSS transitions. If you don't want to use background-images, there's also a version using <img> tags, which works on the same logic.

HTML markup;

<div id="slideshow">
    <!-- define each background-image in CSS -->
    <div></div> <!-- #slideshow :nth-child(1) -->
    <div></div> <!-- #slideshow :nth-child(2) -->
    <div></div> <!-- #slideshow :nth-child(3) -->
    <div></div> <!-- #slideshow :nth-child(4) -->
</div>

JavaScript;

<script>
document.documentElement.className = 'js'; // adds .js class to <html> tag
var slides = document.getElementById('slideshow').getElementsByTagName('div');
function slideShow(i){ // http://jsfiddle.net/Wja4r/
    slides[i].className = '';
    if(i == slides.length -1){ slides[0].className = 'show'; i = -1; }
    if(i > -1 ){ slides[i+1].className = 'show'; }
    setTimeout(function(){ slideShow(++i % slides.length) }, 5000); // show each slide for 5 secs
    }
setTimeout(function(){ slides[0].className = 'show'; }, 1); // style first slide on page load
setTimeout(function(){ slideShow(0) }, 5000); // show first slide for 5 secs
</script>

Core CSS;

#slideshow { position:relative; max-width:64em; padding-bottom:48.828% } /* (img px height / img px width x 100) = padding-bottom % */
#slideshow div { position:absolute; width:100%; height:100%; background-size:cover; -webkit-transition:opacity 3s; transition:opacity 3s }
.js #slideshow div { opacity:0 } /* only hide images if js enabled */
.js #slideshow .show, #slideshow .show { opacity:1 }

Note the #slideshow { position:relative; max-width:64em; padding-bottom:48.828% } line in the CSS above. Without padding-bottom, or an explicit height defined, the div would just collapse (because it's empty). But, the problem with setting an explicit height is that it doesn't work well with responsive design, so we somehow need to give a height that will scale relative to the width of the container (the #slideshow div), and internal padding does just that. The padding-bottom:48.828%; value is calculated very simply, like this;

image height in pixels / image width in pixels x 100

And here comes the first clever bit - using stacked media queries to serve big images to desktop and smaller versions to mobile (resulting in faster page views for mobile visitors);

CSS Media Queries;

@media(max-width:576px) { /* mobile images (half size of desktop) total weight = 115kb */ 
    #slideshow :nth-child(1) { background-image:url(_images/001-1024x500.jpg) }
    #slideshow :nth-child(2) { background-image:url(_images/002-1024x500.jpg) }
    #slideshow :nth-child(3) { background-image:url(_images/003-1024x500.jpg) }
    #slideshow :nth-child(4) { background-image:url(_images/004-1024x500.jpg) }
}

@media(min-width:577px) { /* desktop images (max display of 1024 x 500) total weight = 350kb */
    #slideshow :nth-child(1) { background-image:url(_images/001-2048x1000.jpg) }
    #slideshow :nth-child(2) { background-image:url(_images/002-2048x1000.jpg) }
    #slideshow :nth-child(3) { background-image:url(_images/003-2048x1000.jpg) }
    #slideshow :nth-child(4) { background-image: url(/_images/004-2048x1000.jpg) }
}

The media queries are entirely optional and, if you can't be bothered with them, feel free to omit them and use the same background-image for all media and devices. They do however offer a clear advantage for mobile when stacked this way -

  • when the viewport is 576px wide or under, the smaller, lighter images will be downloaded and served
  • When the viewport is 577px wide or over, the bigger, heavier images will be downloaded and served

Mobile users will thank you for being so considerate when your lovely slideshow loads in super-quick time :)

Please bear in mind that while I've chosen 576px as the threshold for mobile, this size is not a bullet-proof dimension for targeting mobile devices with CSS. In fact, you can't really do it with CSS alone because device screen sizes on the market are always evolving, so this is just a rough guideline for small screens, which I'm assuming are mobile, that could (at times) be accessing the internet without WiFi. Of course larger screen mobile / portable devices could be access the internet without WiFi too, so if you want something more concrete, maybe look towards something like one of the Detect Mobile Browsers scripts to serve device-specific background-images when mobile conditions are met. For example, using the PHP detect mobile script, we can check for mobile and set a variable if true (put before the web page doctype);

PHP Code;

<?php 
$useragent
=$_SERVER['HTTP_USER_AGENT'];
if(
preg_match// regex omitted as it overly inflates this post, but head over to http://detectmobilebrowsers.com/ to get a copy (the PHP version)
$mob 1/* set the $mob variable to '1' when mobile is detected */
?>

And then serve the 2 sets of images like this (written directly to the <head> of the web page);

<style> 
<?php if ($mob == 1){ /* mobile detected */ ?>
    #slideshow :nth-child(1) { background-image:url(_images/001-1024x500.jpg) }
    #slideshow :nth-child(2) { background-image:url(_images/002-1024x500.jpg) }
    #slideshow :nth-child(3) { background-image:url(_images/003-1024x500.jpg) }
    #slideshow :nth-child(4) { background-image:url(_images/004-1024x500.jpg) }
<?php } else { /* desktop detected*/ ?>
    #slideshow :nth-child(1) { background-image:url(_images/001-2048x1000.jpg) }
    #slideshow :nth-child(2) { background-image:url(_images/002-2048x1000.jpg) }
    #slideshow :nth-child(3) { background-image:url(_images/003-2048x1000.jpg) }
    #slideshow :nth-child(4) { background-image: url(/_images/004-2048x1000.jpg) }
<?php ?>
</style>

This will serve the desktop CSS, and therefor download the larger desktop images, when the web page is viewed on a desktop (or other large screen device), and write the mobile CSS, and therefor download the smaller mobile images, when the web page is viewed on a mobile (or other small screen device).

Now for the second clever bit - creating lightweight images that look sharp on retina screens.

Retina-ready Images

I'm sure all web developers have been there at one time or other - we dutifully optimise our images for web, but when we view the web page on a retina display (such as an iPhone 4S, 5, 6, iPad Mini, etc.) the images look fuzzy and a bit rubbish... and they make us look a bit of a rubbishy web developer in the process :/

So then we try NOT optimising them, and sure enough, these un-optimised images look better on retina screens, BUT they take an age to download (and may even crash a feeble mobile browser) due to their over-inflated filesize... and once again, they make us look a bit of a rubbishy web developer because of poor performance. It seems like we just can't win! Well, maybe we can...

This slideshow demo incorporates a retina image technique based on the "Retina Revolution" blog findings of Daan Jobsis - low quality, twice-size images, scaled down to less than half-size so that pixel density doubles, which bizarrely produces crispy sharp images, even on retina displays, despite smaller filesize.

Put simply, if you want an image that displays at 1000 x 250 pixels max in your web page, use an image that is twice the size at 2000 x 500 pixels, compress the heck out of it (even past the point of it looking like a pixelated mess), and resize it by half on screen. Strange... But it works!

Read the article for a thorough explanation (it's really rather interesting) but as Daan briefly sums up;

The bottomline is that heavy compression doesn't affect the final image as much as you would expect. This is because of the greater amount of pixels in the Retina image, compression artefacts are scaled down and therefore almost unnoticeable.

So, on a desktop, the heavily compressed images in this slideshow will display at a maximum size of 1024 x 500 pixels. Their dimensions are actually twice the size at 2048 x 1000 pixels, yet their total filesize is only ~350kb, while still being retina-ready.

Compare that to correctly sized images (1024 x 500 pixels displayed at 1024 x 500 pixels) compressed using more typical methods ('Save For Web' optimisation in Photoshop, at 60% quality), where the total filesize is more like ~500kb, and the images are NOT retina-ready.

This really can add-up to some spectacular savings AND look fantastic!!! WIN-WIN!

Compatibility

Works in modern browsers and IE9+ (no fade effect in IE9). IE8 users just see a static image - but you can make IE8 act like IE9 with some CSS mods (see below).

Make IE8 act like IE9

You can make IE8 behave like IE9, giving users a really basic slideshow without the fades, by using this CSS wrapped in IE8 conditional comments;

<!--[if IE 8]>
<style>
#slideshow .show:first-child { background-image:url(path/to/1024x500/image-ie8-01.jpg) }
#slideshow :first-child + div.show { background-image:url(path/to/1024x500/image-ie8-02.jpg) }
#slideshow :first-child + div + div.show { background-image:url(path/to/1024x500/image-ie8-03.jpg) }
#slideshow :first-child + div + div + div.show { background-image:url(path/to/1024x500/image-ie8-04.jpg) }
#slideshow :first-child + div + div + div + div.show { background-image:url(path/to/1024x500/image-ie8-05.jpg) }
</style>
<![endif]-->

To round things off, I'll leave you with the Responsive Cross-Fading Slideshow demo link again:

Demo

I hope you find it, and the contents of this blog post, both enlightening and useful.