Creating a Modern Lightbox with CSS3 and JavaScript 76
Today we have a guest post by Andy Walpole sharing his experience from creating the Lightbox Ultra on Mozilla Demo Studio. Several years ago developer Lokesh Dhakar created the familiar Lightbox script with Prototype and script.aculo.us. It's smooth animation and sleek aesthetics were an instant hit in the web design community and it was used on a multitude of different projects.
There have been many imitations - Fancybox and Thickbox to name just two - but Lightbox still remains as the favoured way to present images to a user. This, primarily, is due to its 3d animation and background dimming which allows the user to view the image without any distraction. It's a useful, timeless technique.
Since CSS animation first started to be used a few years ago, coders have enjoyed creating their own CSS-only Lightbox versions. Indeed, there is a real craze for CSS-only animation experimentation. This is something we should all welcome. In the near future CSS will completely take over from JavaScript on the presentation layer so using it now for this purpose is perceptive forward thinking.
Just recently I had a CSS project exhibited on the Mozilla Demo site called Lightbox Ultra. Its core is CSS3 - in particular 3d animation - but in the process of creating the demo I realised that JavaScript added important usability and design features.
This guide is not a line-by-line explanation of the code I have written for my Lightbox project. Going into such tiny detail will only confuse the reader. Instead, I'll explain some of the key concepts behind creating a modern take on the popular Lightbox script. That way you can then use these ideas in your own coding projects.
CSS and Lightbox
Firstly, the HTML is as below:
I used definition lists but I could have as easily used unordered lists. Note how the HTML code block for each image is split into two different parts - one for the thumbnail and the other for the full sized image.
The full-sized image needs to be set to "display: none" so that the initial state for the viewer is one where only the thumbnail is visible:
The Lightbox approach is for the full-sized image to appear after user interaction with the thumbnail.
This is possible with JavaScript events. The small code block below changes the CSS to "display:block" when the user clicks on the thumbnail (Here I use the querySelector() method which only has partial cross-browser support):
var thumbnail = document.querySelector(".first-image img");
var fullSize = document.querySelector(".second-image img");
thumbnail.onclick = function () {
fullSize.style.display = "block";
}
The closest we have to JavaScript events in CSS is the :target pseudo-class. I don't know whose idea it was to introduce :target into the CSS3 spec, but its inclusion was a minor act of ingenuity.
Instead of a script event like above it is possible to use the following CSS:
.second-image dl:target {
display: block;
}
:target works by using a fragment in the URL. The link in the thumbnail has the following fragment: "#block-1". The ID for the full size image is block-1. Clicking on the link with the fragment then triggers the HTML with an ID that is exactly the same as long, of course, as you specify this in your style sheet.
Because it is so different to other CSS, :target may appear a little confusing at first. The best method to become familiar with it is by rolling your sleeves up and experimenting with this pseudo-class on your own code.
:target will become one of the most used CSS3 features in the future. The reason it has only had limited use so far is that it is not supported in Internet Explorer versions 8 and below. I'll discuss workarounds in the next section.
The actual CSS keyframe animation is fairly straightforward:
/* animation for main image */
.second-image dl:target .animation {
-moz-animation: image3D 2000ms cubic-bezier(0.950,0.050,0.795,0.035);
-webkit-animation: image3D 2000ms cubic-bezier(0.950,0.050,0.795,0.035);
-ms-animation: image3D 2000ms cubic-bezier(0.950,0.050,0.795,0.035);
-o-animation: image3D 2000ms cubic-bezier(0.950,0.050,0.795,0.035);
animation: image3D 2000ms cubic-bezier(0.950,0.050,0.795,0.035);
}
/* image3D is used on the main image */
@-moz-keyframes image3D {
0% {
-moz-transform: rotateY(90deg);
}
100% {
-moz-transform: rotateY(0deg);
}
}
@-webkit-keyframes image3D {
0% {
-webkit-transform: rotateY(90deg);
}
100% {
-webkit-transform: rotateY(0deg);
}
}
@-o-keyframes image3D {
0% {
-o-transform: rotateY(90deg);
}
100% {
-o-transform: rotateY(0deg)
}
}
@-ms-keyframes image3D {
0% {
-ms-transform: rotateY(90deg);
}
100% {
-ms-transform: rotateY(0deg);
}
}
@keyframes image3D {
0% {
transform: rotateY(90deg);
}
100% {
transform: rotateY(0deg);
}
}
I will not explain the code above in detail because this article isn't a tutorial on CSS3 animation. For that I would advise you to read introductions written by Tim Van Damme, Louis Lazaris and Tom Waterhouse.
Note in the code above I used Webkit, Firefox, Microsoft and Opera prefixes. As of yet there is no keyframe support in Internet Explorer or Opera but it will be coming soon so it's best to future-proof your code.
JavaScript and Lightbox
On my Mozilla Demo page I had a lot of fun creating a modern take of Lightbox with CSS3. I used box-shadows, the calc() function, gradients and CSS3 transforms. If you turn off JavaScript in your browser and try the demo out in a modern non-IE browser you will see it is in perfect working order.
However, there are are number of pitfalls with a CSS-only approach.
Firstly, CSS3 animation only fires once. This is an issue previously well documented by Oli Studholme and Chris Coyier.
CSS3 keyframes animation will only work again if the page is refreshed. One way around is to use transitions instead of keyframes. Another way is to use JavaScript to rebuild the node every time the animation is fired.
In the change_animation() method I remove the old class, clone the relevant part of the DOM, randomly add a new class, delete the online node and replace it with the DOM clone:
change_animation: function (htmlElement) {
var items, random, regEx, result, key, mainBlock, cloneBlock, parentBlock;
// below finds the unique key which is in the parent div id
function removeArrayElement(element, index, array) {
return (!element.search(_private.REMOVE_SEARCH));
}
regEx = /.([0-9]+)$/;
result = regEx.exec(htmlElement.parentNode);
key = result.filter(removeArrayElement).toString();
// this unique key is then used to find the large image in the overlay
// this is an alternative to some bonkers DOM transversal method
mainBlock = document.querySelector("#block-" + key + " img");
// remove previous animation class
mainBlock.removeAttribute("class", "");
// In order for CSS animation to run more than once on the same element
// It is necessary to rebuild the node
// See for further details: http://css-tricks.com/restart-css-animation/
cloneBlock = mainBlock.cloneNode(true);
// select a class at random
items = this.classes;
random = items[Math.floor(Math.random() * items.length)];
// rebuild the html adding the new class
cloneBlock.setAttribute("class", random);
parentBlock = mainBlock.parentNode;
parentBlock.removeChild(mainBlock);
parentBlock.appendChild(cloneBlock);
}
I'm sure you could write less verbose code if you used jQuery.
What this means is the global object user interface is as below:
lightbox.init({
// declare CSS classes that are used for animation
classes: ['image3D', 'flipper', 'bulge', 'bouncing', 'side-spin', 'top-spin', 'shadow-play', 'rush']
});
In order to change the animation just add a new class to your CSS and to the JavaScript facade. Referenced above, "top-spin" CSS looks like this:
/* TOPSPIN */
.top-spin {
-moz-animation: topSpin 2000ms cubic-bezier(0.000, -0.600, 0.000, 1.650);
-webkit-animation: topSpin 2000ms cubic-bezier(0.000, -0.600, 0.000, 1.650);
-ms-animation: topSpin 2000ms cubic-bezier(0.000, -0.600, 0.000, 1.650);
-o-animation: topSpin 2000ms cubic-bezier(0.000, -0.600, 0.000, 1.650);
animation: topSpin 2000ms cubic-bezier(0.000, -0.600, 0.000, 1.650);
}
@-moz-keyframes topSpin {
0% {
-moz-transform: rotateX(0deg);
-moz-transform-origin: 0% 50% 0;
}
100% {
-moz-transform: rotateX(360deg);
-moz-transform-origin: 0% 50% 0;
}
}
@-webkit-keyframes topSpin {
0% {
opacity: 0.1;
-webkit-transform: rotateX(0deg);
-webkit-transform-origin: 0% 50% 0;
}
100% {
opacity: 1;
-webkit-transform: rotateX(360deg);
-webkit-transform-origin: 0% 50% 0;
}
}
@-o-keyframes topSpin {
0% {
-o-transform: rotateX(0deg);
-o-transform-origin: 0% 50% 0;
}
100% {
-o-transform: rotateX(360deg);
-o-transform-origin: 0% 50% 0;
}
}
@-ms-keyframes topSpin {
0% {
-ms-transform: rotateX(0deg);
-ms-transform-origin: 0% 50% 0;
}
100% {
-ms-transform: rotateX(360deg);
-ms-transform-origin: 0% 50% 0;
}
}
@keyframes topSpin {
0% {
transform: rotateX(0deg);
transform-origin: 0% 50% 0;
}
100% {
transform: rotateX(360deg);
transform-origin: 0% 50% 0;
}
}
The second issue facing a CSS3 Lightbox is the lack of support in Internet Explorer. Unfortunately, versions 8 and below do not recognise the :target pseudo-class.
One way around this is to use a polyfill such as Dean Edwards IE7.js or Keith Clarke's Selectivizr (the latter has support for IE8 only).
As the Sizzle engine has full CSS3 support I presume as well that you can use jQuery or Dojo.
However, my aim for my demo was to create it fully independent of any other JavaScript library. This is a tough call.
If a browser does not recognise a CSS pseudo-class or pseudo-element it will parse out the entire block of code. That means it is not possible to change the CSS using the styleSheets object because the CSS block using :target is not there to alter.
My solution was to use the XMLHttpRequest API to save the stylesheets to memory, change :target to a class called ieTarget, remove the old style sheets and then add the new altered CSS to the head. The ieTarget is then dynamically added and removed from the HTML depending on a click event being fired on the thumbnail or full-size close link.
Admittedly, it is quite a drastic move and one that leaves open all sorts of potential bugs and issues.
Lastly, if you are going to use JavaScript for Lightbox then why not use the new Full-screen API. This was introduced into Webkit in 2011 and made its first appearance in Firefox at the beginning of 2012.
The API is relatively straightforward to implement. I did so in the following function:
full_page: function () {
var img, x, docElm, l;
img = document.querySelectorAll(".first-image img");
for (x = 0, l = img.length; x < l; x += 1) {
// call full screen when clicking on the thumbnail
img[x].addEventListener("click", function (evt) {
docElm = document.documentElement;
if (docElm.requestFullScreen) {
docElm.requestFullScreen();
_private.add_warning();
_private.request_smallscreen();
} else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
_private.add_warning();
_private.request_smallscreen();
} else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
_private.add_warning();
_private.request_smallscreen();
}
}, false);
} // end for loop
}
It is now possible to access the global object in the following interface, with the Full-screen API being switched on and off with a boolean value:
lightbox.init({
// declare whether you want to use the Full-screen API
fullscreen: false,
// declare CSS classes that are used for animation
classes: ['image3D', 'flipper', 'bulge', 'bouncing', 'side-spin', 'top-spin', 'shadow-play', 'rush']
});
Conclusion
You may well wonder if there is a need for a JavaScript fullback for a CSS-based Lightbox, then why not just a use a wholly JavaScript approach and cut out the CSS animation? That, of course, is your pejorative. But let us put the sign up for everybody to read: animation for web design will soon be exclusively done by CSS, not JavaScript. In anticipation of this it makes sense to familiarise yourself with CSS animation now.
Using JavaScript too also means you can gain access to some of the funky new APIs like Full-screen.
If you do use CSS 3d animation then do so with caution. I think it is important to use it subtly rather than creating some garish 360 plus flipping and twisting. To do so would put the animation at the centre of attention rather than the images that are being presented.
About Author
Andy Walpole is a London-based freelance coder. He is currently collaborating with others on a number of different projects including a WordPress plugin for affiliate marketing and a Twitter service for teenagers. You can find him hanging around Twitter most days: @andywalpole
Excelent !.
Tnkx for sharing !
Really nice!
Seems like not working properly in Opera. Kindly check Thanks
On Firefox it looks good.
Wahab, it’s not going to work the same in Opera as Firefox because the Full-screen API is, so far, only available in Firefox and Webkit-based browsers.
Not working properly in latest versions of Chrome or Firefox. Didn’t have behaviors I would expect from a lightbox at all. Couldn’t navigate using the gallery at the bottom on my 768 height screen since only a bit of the thumbnail showed. Didn’t have the standard arrows that appear on hover over the image to move from one picture to another either. So basically, I couldn’t navigate at all.
It’s working fine in the latest versions of Firefox and Chrome
I’ve only tested it out on the desktop so it’s not optimized for mobile devices
No there is no navigation, yet, it’s a means of presenting single images
Pretty awesome tutorial, Thanks for sharing!
Great tutorial, thanks for sharing!
I recommend correcting “pejorative” to “prerogative” since they mean very different things :) there are also some other typos (including an it’s/its) so maybe you’d like to do a once-over. Thank you for the article.
Well spotted. Perhaps Nick can change that mistake
This post is very informative. We also help in improving websites and we manage web designs, development and SEO. Easy Web Services
Thanks for giving tutorial based on css3 and javascript with detailed description including code. I read your post really i get some knowledge.
Its working nice in firefox. Thanks for this tutorial. I wanna work in other browsers. Hope it works.
Thanks!!! it works wonders, I’ve been looking for a modern lightbox like this! I will for sure implement!!
Well, it really works for me for any browsers, this tutorial is really a good idea for us web designers.. Thanks for sharing it..
Very informative post and i have implemented it also. It feels good to say that works for any browser. Thanks Andy.
Awesome display of the capabilities that are available within CSS and Javascript. I would love to see a responsive version, well when I say see… I mean use ;)
Cheers
It doesn’t work properly in Firefox. And the inability to go through images with a mouse click or keyboard doesn’t make any sense.
Good explanation about css
Really over-designed. Just because you can do something, doesn’t mean you should. I would say that this actually enhances the worst aspects of lightboxes. Nice effort, but designers should think carefully before implementing something this over-done.
A good example for the implementation you have certainly packed a lot in.
Thanks for sharing!
Thanks for this awesome tutorial, liked the CSS.
I can’t wait for CSS3 to be fully supported or until people stop using the older browsers. Great post!
Well, it was nice knowing Flash. Maybe it’ll be buried next to Silverlight so I can visit them at the same time…