Building an Image Gallery with Progressive Enhancement

Building an Image Gallery with Progressive Enhancement

Who doesn’t love to completely trick out their website with neat features? But what happens when your viewers aren’t using the latest browser, or they have JavaScript turned off? In today’s tutorial, you’ll learn how to create a image gallery that will work in almost all environments, using progressive enhancement techniques.


Introduction

Final Product

So what exactly is progressive enhancement? Formally, it is this:

Progressive enhancement is a strategy for web design that emphasizes accessibility, semantic markup, and external stylesheet and scripting technologies. Progressive enhancement uses web technologies in a layered fashion that allows everyone to access the basic content and functionality of a web page, using any browser or Internet connection, while also providing those with better bandwidth or more advanced browser software an enhanced version of the page. (Wikipedia).

Progressive enhancement is the opposite of graceful degradation, where you build your site/app with all the features, and then make sure it looks good and functions decently in older browsers. With progressive enhancement, we’ll lay a solid foundation for our image gallery that will work no matter where you view it. Then, we’ll layer on eye-candy and functionality until we’ve got a good-looking, well-functioning image gallery. Let’s begin!

What we’re After

Here’s what we want to end up with: if all the bells and whistles are turned on, we’ll be able to drag our images around to view them; it will be a very basic simulation of a stack of photos on your coffee table. When you click on one, it will slide open to reveal some details about the image. If JavaScript is turned off, we’ll have a nice grid of image to choose from; clicking them will take us to a page with a larger version of the image and the details. If there’s no CSS support, we’ll get an ugly (but working) list of the images.

Here’s a screen-shot of our final product:

Screenshot of finished gallery

Laying the Foundation: POSH

We start with some plain old semantic HTML. This is our foundation, since every browser out there is good at parsing HTML.

index.htm

<!DOCTYPE html>
<html>
<head>
	<meta charset='utf-8' />
	<title>Progressively Enhanced Image Gallery</title>
</head>
<body>
	<div id="container">
			<h1>Click on an image below to view it!</h1>

		<ul id="images">
			<li><div>
				<a href="3dOcean.htm"><img alt="3dOcean" src="images/thumbnails/3dOcean_tn.jpg"/></a>
			</div></li>
			<li><div>
				<a href="AudioJungle.htm"><img alt="AudioJungle" src="images/thumbnails/AudioJungle_tn.jpg"/></a>
			</div></li>
			<li><div>
			<a href="ActiveDen.htm"><img alt="ActiveDen" src="images/thumbnails/ActiveDen_tn.jpg"/></a>
			</div></li>
			<li><div>
				<a href="GraphicRiver.htm"><img alt="GraphicRiver" src="images/thumbnails/GraphicRiver_tn.jpg"/></a>
			</div></li>
			<li><div>
				<a href="ThemeForest.htm"><img alt="ThemeForest" src="images/thumbnails/ThemeForest_tn.jpg"/></a>
			</div></li>
			<li><div>
				<a href="VideoHive.htm"><img alt="VideoHive" src="images/thumbnails/VideoHive_tn.jpg"/></a>
			</div></li>
		</ul>

	</div>
</body>
</html>

That’s it; pretty basic stuff, eh? No browser worth that title should have a problem with it. And this is our finished first layer. No, it’s not pretty, but that wasn’t our goal: we wanted something that will work everywhere, no matter what. A few things to notice about this code: firstly, it’s semantic, as we said it should be. You might wonder about the divs inside the list items. What’s up with them? Even though we’re starting with the bare bones, we are anticipating that most of our viewers will have JavaScript enabled, in which case we’ll need those divs. We could insert them with jQuery, but since we do expect them to be used most of the time, it’s easier to hard-code it in. The other thing to notice is that it’s usable. Try viewing it in Lynx, or another text-only browser:

Gallery in Lynx

By the way, the pages linked to in the HTML above will be available in the downloadable source; they’re all similar to this:

<!DOCTYPE html>
<html>
<head>
	<meta charset='utf-8' />
	<title>Themeforest MarketPlace by Envato</title>
</head>
<body>
<h1>ThemeForest</h1>
<img src="images/ThemeForest.jpg" alt="ThemeForest" />
<p>Themeforest offers: HTML Templates, WordPress,
Joomla, Flash Sites, PSD Templates, Javascript, PHP Scripts</p>
</body>
</html>

On a real site, you’d surround this with your site template, but it’s just fine for our purposes.

Dressing the Structure: CSS

Although semantic HTML is nice, it looks a bit bare. Let’s dress it up with some CSS. Of course, we first have to reference the stylesheet:

<link type="text/css" rel="stylesheet" href="styles/default.css" media="screen" />

We’ll level the playing field first with a stripped-down Meyer reset:

/* Meyer's Reset */
html, body, div, h1, h2, h4, p, a, img, ul, li
{ margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }
/* remember to define focus styles! */
:focus { outline: 0; }
body { line-height: 1; color: black; background: white; }
ol, ul { list-style: none; }
/* END Meyer's Reset */

Now we have to style our gallery for use without JavaScript. We’ll start with some general elements and background styling:

body{
	font:13px/1.5 'Helvetica Neue',Arial,'Liberation Sans',FreeSans,sans-serif; /* <-- from 960.gs text.css */
	background: #36b4dd;
}
h1 { font-size: 30px; }
#container > h1 { padding: 10px;}
h4 { font-size: 20px; padding-bottom:10px;}

Now we’ll take care of our heading and list items.

#container h1 {
	padding: 10px;
}
#images li {
	float:left;
	background:#ececec;
	border:1px solid #ccc;
	margin:10px;
	width: 256px;
	padding: 10px;
	overflow: hidden;
}
#images li div {
	width: 512px;
	overflow:hidden;
}
#images li a {
	float:left;
}
#images li div.info {
	width: 246px;
	padding:0 0 0 10px;
	float:left;
}

You’ll notice that we’ve set a width on our list elements. We need to do that for our JavaScript functionality; that’s also why overflow:hidden is set. This is easy in our case, because I’ve made all the images the same width. If yours are different widths, you’ll probably have to set the width for each list item with JavaScript. That will work because the CSS only version doesn’t require the width. The div directly inside our list item (that wraps all the content) is 512px wide, with overflow hidden. We’ve floated our anchor to the left so we can float the div.info to the left beside it, as you see further on.

So, here are the fruits of our labours so far:

Our Gallery with CSS only

We’ll come back to CSS in a bit; but now, let’s turn to the JavaScript!

Adding the Functionality: JavaScript

We’ll be using jQuery here; so start by importing that from Google’s CDN. We’ll also need the jQueryUI library. We could get that from Google as well, but we don’t need the whole library. I’ve downloaded a copy from the jQueryUI site, with just the core and the draggable components, which is all we’ll need. You can do whichever you prefer.

<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>
<script src='js/jqueryui-core-drag.js'></script>

Before we start coding, let’s determine what we need to do.

  • The h1 we’ve hard-coded provides instruction for the non-JavaScript version. We’ll remove this and add different instructions.
  • We need to configure the dragging on the list elements; we’ll add a splash of fun: when the user releases the list item, it will slide a bit further and slow down (sounds like an iEffect). Like we said earlier, it’s supposed to be somewhat like a stack of photos on a table.
  • When a list item is clicked, it should ‘slide open,’ doubling in width. Before it does, however, we’ll send an Ajax call to get the page that the user would go to if JavaScript wasn’t enabled. Then, we’ll get the values we want from that page and plug them into our list item in a div. We’ll check for this div before making the call, though, so if the user has already clicked on it, we won’t send another request.

Alright, open up a script tag and let’s code!

var imgs;

$(document).ready(function () {

});

$(window).load(function () {

});

We’ll start by creating a global variable: an array of the list items (well, it will be an array soon). Then, we set up event handlers for a) when the DOM is ready, and b) when the window is finished loading. The effect we’ll do when the window loads (which I haven’t told you about yet) doesn’t require that we wait until then, but I think it will be nicer when the images have been loaded.

Now, this code goes in our document.ready function:

var  drag = {};
$('h1').remove();
$('#images').append('<li id='instructions'><h2>Toss the images around; if you see one you like, click on it!</h2></li>');

imgs = $('#images li');

Should be straightforward: we create an object that will hold some details about out dragging; then we remove the h1, append a list item with new instructions to our list, and put all the list items in our imgs variable.

Now we’ll build our dragging functionality. Really it’s as simple as this:

imgs.draggable();

But we’re going to add a few options. Here’s the code; persue it yourself and then we’ll stroll through it.

imgs.draggable({
			stack : { group : '#images li', min : 1},
			start : function () {
				$this = $(this);
				if($this.attr("id") === 'instructions') { $this.fadeOut().remove(); }

				imgs.each(function () {
				var $this = $(this);
				if($this.width() !== 256) {
					$this.stop().animate({width : 256 }).removeClass('top');
				}
			});

			drag.startTime = new Date();
			drag.startPos = $this.position();
		},
		stop : function () {
			var $this = $(this), top, left, time;
			drag.endTime = new Date();
			drag.endPos = $this.position();
			drag.leftOffset = drag.endPos.left - drag.startPos.left;
			drag.topOffset  = drag.endPos.top  - drag.startPos.top;

			time = (drag.endTime.getTime() - drag.startTime.getTime()) /60;

			top  = (drag.topOffset / time).toString();
			left = (drag.leftOffset / time).toString();

			$this.animate({
				top : '+=' + top,
				left: '+=' + left
			});
		}

});

We’ve added three properties to our draggable options object: stack, start, and stop. Stack controls the z-index of a group of objects, and takes an object with two properties of its own: group and min. Group is a jQuery selector; in our case, it’s the list items. Min is the minimum z-index any items in the group can take. So now, when you drag an item, it comes to the top of the pile.

The start function is run when you begin to drag an item. We start by caching $(this). Then, we check to see if the list item we grabbed has an id of ‘instructions.’ If it does, we fade it out and remove it. Then, we loop over each list item and if we find any that aren’t 256px wide, we animate the width to 256px and remove the class of ‘top.’ What’s ‘top’ do? We’ll style it in a few minutes, but it just gives the user some visual feedback when they click an item. After that, we do something very important: we set two properties on our drag object. One (startTime) is the time the dragging started, and the other (startPos) is the position the item started at. We’ll use this information to create our effect when the dragging stops.

Lastly, we have the stop function, which predicably runs when user stops dragging. Again, we start by caching $(this), as well as creating a few other variables we’ll give values to in a moment. Next, we put our end time and position into drag.endTime and drag.endPosition. Then we calculate our left and top offset by subtracting where we were from where we are; we can do this with the top and left properties that the position object has. Now for the slowing down animate logic: you could get very complicated with this algorithm, but we’re just going to keep it simple. We’ll find the time the drag took by subtracting our startTime from our endTime; the getTime method returns the number of milleseconds since 1970/01/01, so the difference is in milleseconds Then, we divide that value by 60, which I came up with through trial and error. On an average drag, this sets our time variable somewhere between 2 and 3. Then we divide our top and left offset by time, and convert those values to string, saving them in top and left. Finally, we animate the dragged list item, incrementing (that’s what ‘+=’ does) the value by top or left. At this point, you should be able to drag the images around and get our effect.

However, clicking the images will bring you to a new page. So let’s set up our click event handler.

imgs.click(function () {
			var $this = $(this);

		if ($this.attr('id') === 'instructions') {
			$this.fadeOut().remove();
		}
		else {
			if($this.width() !== 256) {
				$this.stop().animate({width : 256 }).removeClass('top');
			}
			else {
				if (!($this.find('.info').length)) {
					$.ajax({
						url : $this.find('a').attr('href'),
						dataType : 'html',
						success : function (data) {
							var $d = $(data),
								head = $d.filter('h1'),
								para = $d.filter('p');

							$this.children('div').append('<div class="info"></div>').find(".info").append(head, para);
						},
						error : function () {
							var msg = '<h1>Oops!</h1><p>It looks like there been a problem; we can\'t get this info right now.</p>';
							$this.children('div').append('<div class="info"></div>').find(".info").html(msg);
						}
					});
				}
				$this.css({'zIndex' : 8 })
					 .stop()
					 .animate({ width : 512})
					 .addClass('top')
						.siblings().removeClass('top')
								   .stop()
								   .animate({width : 256})
										.filter(function () { return $(this).css('zIndex') === '8' }).css({'zIndex' : 7});
			}
		}
		return false;
	});

Standard operating procedure today: begin by caching $(this). Once again, we check for the id of instructions; if it’s there, we fadeOut and remove the item. If its not there, we check the width of the element: if it isn’t 256px, that means this item has already been clicked, so we animate the width down to 256 and remove our top class (yes, we’ll get there). If the element is 256px wide, we check for a child element with the class of info. We can do this my calling the find method on the element, pass in the selector we’re looking for, and get the length property. If this element doesn’t exist, the result will be 0, which is a false value, so we wrap that in parentheses and use a ! to switch the boolean. Now, if there aren’t any child elements with a class of info, we’ll step into this block, which is our ajax call.

$.ajax() takes an object parameter, and we’ll use four properties: url, datatype, success, and error. Url and datatype are obvious: we simply find the anchor in our list item and set url to its href; our datatype is html. If our ajax call is successful, we’ll take the data we get, which is the entire HTML contents of the page, and turn it into a jQuery object. Then, we can filter out the heading and paragraph that we know we have there. Then we simply get the div inside our list item, append a div.info, and append the heading and paragraph to that. If our request fails, we’ll show an error message by a similar process, using the error function. After our ajax call, we want to perform some styling and animation on our list item. First, we want to set the z-index to 8, or any number higher than the number of draggable items we have. Then we want to stop all current animations on this list item and animate the width to 512px. Lastly, we’ll add that top class. Next, we get all the siblings, which are the other list items. We’ll stop any animation on them and then animate them to 256px wide. Finally, we’ll filter out only the elements with a z-index of 8 and change their z-index to 7. This allows the currently cliked list item to come ot the top. Right at the end, we return false, so we stay on our current page (because even though this is a click function on a list item, the users will most likely click our anchor-wrapped image inside the list item).

So that’s our click handler; only one piece of JavaScript left. If you give our example a try now, you’ll see it works … kind of. Whenever you click a list item to open it, it opens, but you’ll notice a rather shifty problem. It’s because the list items are floated to the left; let’s take care of that in our window ready handler.

$(window).load(function () {
	var $w = $(window);
	imgs.css({	position : 'absolute',
			left : $w.width() / 2 - imgs.width(),
			top  : $w.height() / 2- imgs.height() });
	for(var i = 0; imgs[i]; i++ ) {
		$(imgs[i]).animate({	left : '+=' + Math.random()*150,
						top  : '+=' + Math.random()*150 });
	}
});

If you’ve followed pretty well so far, you won’t flinch here: we simply use the jQuery’s css method to set the positioning to absolute and stack all the images so their right edges are aligned to the middle of the viewport, and their bottom edges are aligned to the vertical middle. Then we use a for loop to recurse over each list item and randomly animate it right and down. This creates the effect of a stack of images being scattered.

So that’s it for the JavaScript! Now, when a user loads the page, they should see something like this (after animation) :

Images scattered

Final Touches: CSS3

We could end there, but we want to reward those who use forward-thinking browsers, so it’s back to the CSS for a few minutes. And, yes, we’ll look at the top class.

The first thing we’ll do is add rounded corners to the selector #images li.

border-radius:5px;
-moz-border-radius:5px;
-webkit-border-radius:5px;

Then the top class, which list items only have when they are ‘open,’ looks like this:

.top {
	box-shadow:0 0 10px #000;
	-moz-box-shadow:0 0 10px #000;
	-webkit-box-shadow:0 0 30px #000;
}

Nothing incredibly fancy, but a few nice refinements nonetheless.

Closing Comments

Well, that’s it. We should now have an image gallery that works decently without CSS or JavaScript, but takes full advantage of them where those technologies are available. So, how would you improve our gallery? Let’s hear it in the comments!

Add Comment

Discussion 53 Comments

  1. niceoutput says:

    Very nice, maybe i’ll use it in my future projects.

    Thanks

  2. mary says:

    Wow, just looked at the demo and scrolled through the article and it looks good. Will have to make some time over the holiday to work through it.

    thanks!

  3. Andrei says:

    It is possible and for posts in wordpress?

  4. Awesome tut. Thanks to author

  5. hari says:

    hey great, nice work!

  6. Anjum nawab says:

    WOW ITS WONDERFUL … when u drag images on top u cant find them back :)

  7. Thomas says:

    Nice Work indeed.
    Could you please point out how to add working links to the image description?

  8. Ethan says:

    That’s really cool. I’ll use this!

  9. Nokadota says:

    This is an impressive tutorial, I look forward to experimenting with it.

  10. Could come in very handy, have had a skim, will have a better look through it later.

  11. Mansour says:

    Nice tutorial , i love it .

    Thanks

  12. John says:

    hate to rain on your parade. But If you toss an image to the top of the window, some times the images will move off the top of the page and there is no way to get it back. Other then that this is cool.

    • Fudgey says:

      I noticed this too right away… I also tossed it way off the screen to the bottom right and had to use the scroll bars to get to the image. It might be better to prevent the images (with an option) from leaving the view port.

  13. Aaron says:

    That’s pretty cool. …now if I can figure out a way that would make this useful :)

  14. Unfortunately this doesn’t work very well in Safari 4…but it’s just a matter of styling of course…

  15. jaded says:

    I dunno, feels gimmicky to me. The drag and toss thing is a good idea but the execution fails. Comes off as feeling very unnatural. If I had to navigate Flickr or Google Image search this way I wouldn’t use them.

    And what’s the point about testing in Lynx? What is this, 1984? The web was meant to be viewed with images or am I missing something? Besides, when was the last time anyone ever used Lynx? Progressive enhancement and graceful degradation are for those Luddites, Amish, and foil hat wearing-technophobes who turn JavaScript off.

    Oh and “Progressive enhancement is the opposite of graceful degradation” isn’t true. These concepts are related and both work towards making content more accessible.

    • Author

      You’re right; the drag and toss is a bit primitive. If done well, it can be pretty fun, though. Check this out: http://www.etsy.com/color.php. Of course, that’s done with flash. To do that in HTML and JavaScript, you’d probably need to use canvas.

      The point of testing in Lynx is to make sure we have a solid base; viewing a page in a text-only browser will give you a good idea of what search engines and screen readers will see.

      I’ve heard both views on progressive enhancement and graceful degradation. I tend to think of PE as something you have in mind from the beginning, which GD is an after-though. Here’s a quote from A List Apart:

      “In case you are scratching your head, trying to see how graceful degradation and progressive enhancement differ, I’ll say this: it’s a matter of perspective. Both graceful degradation and progressive enhancement consider how well a site works in a variety of browsers on a variety of devices. The key is where they place their focus and how this affects workflow.” (http://www.alistapart.com/articles/understandingprogressiveenhancement/)
      Read the whole article for a complete definition.

      Thanks for reading! :)

    • Juan C Rois says:

      Of course you are missing something here (and it’s funny that you even say “accessible”) and are not really seeing the point of testing text only browsers.
      As developers we might not have the time and resources to create our content in a way that it works in every instance.
      This gallery does that, and what you are failing to see is that it also takes into account screen readers for people with disabilities.
      In a perfect web all browsers would work the same and every developer would comply with standards, but since it is not perfect we have to take that into consideration.

      • jaded says:

        I use an actual screen reader to test a design, not Lynx. Why use a tool to test something for which it wasn’t designed?

  16. ShadowAssassin says:

    Okay, so I can fling the images off of the screen and I will never seem them again…

    …Make some soft of boundaries or something so that doesn’t happen =D

    Anyway, I don’t give much thought to users with JS disabled…If you want to use a site, have JS, more and more sites are using JS…=D

    • carlos says:

      Accessibility is a big issue in today’s day in age. Accessibility deals with people who are blind, use screen readers or are deaf (need sub titles for movies, videos, tutorial videos and such). Hint hint NetTuts. There are deaf developers too.
      BUT, if you are going to design or develop a site for a non-profit, big corporation, any company, even your own, if you show people you pay attention to these things, you are more likely to get business.

      In 2006 Target had a lawsuit against them for their website not being accessible to the visually impaired, in which the Americans With Disabilities Act applies to the Internet.

      So if you don’t want a lawsuit or to lose business. I would suggest that all developers and designers pay attention to your accessibility. That is why all Flash sites may not be the best thing for a website if people can see them.
      Screenreaders, which visually impaired persons use, don’t generally read Javascript.

      Food for thought.

  17. very nice tuts! thanks!

  18. CSS Gallery says:

    Very nice! I only got to skim the tutorial and see the demo. I think it would be nice to add a enlarge image function. Great work!

  19. Leo says:

    I wonder what happened with the asp.net tutorials , Are we getting more ?

  20. A really beautiful effect. Good job!

  21. Anthony says:

    Very cool! Looking forward to trying this one out!

  22. I try always to create something from ground up(HTML->CSS->JS), progressive enhancement is always on my mind, and I think it’s a good practice.

    Simple tutorial, but the idea is great!

  23. Nice Effect :) I hope this can be ued to drag images over web page.I Will try it soon

  24. boris badenov says:

    Great article. It would be interesting to add some more styling to make it look like a photographer’s light table and also tie this into a database of images. Maybe over the holiday weekend I can play some more with this.

    I have only one issue but then I have not spent too much time with the code yet. If I put a link in the ‘hidden’ content on the item pages (ie: ActiveDen.htm), the link will show that it is active. I can see that it is in the status bar but it does not call up the page I link to. I also would like to put a lightbox effect in the ‘hidden’ content. Because I am calling this content with ajax, I have a feeling this will not work either. Is there a work around this?

    THanks all and once again, thanks for this great tutorial.

    • b_d says:

      I second that, it’s really cool effect, but could be better if we can also use link.
      Is there any way to tweak this prob? really need to know

  25. Ben says:

    Great Post, going to try it out right away!

  26. Alex Stomp says:

    Don’t see an immediate use for it, but it’s a good thing to have in my skillset :)

    Thanks for the great TUT

  27. Ranjit says:

    Demo is not working properly. If you drag fully top, then we cant see the images. Otherwise its ok…

  28. tarneem says:

    wowwwwwwww it is so cute i like it so much great job

  29. designfollow says:

    great effect

    thank you

  30. Lawrence Taur says:

    There should be an option to ‘reset’ the displaced items. this can be great for making a small portfolio site.

    I wouldn’t prefer reloading the entire page.

  31. original art says:

    Awesome post, going to make good use of this and see how it goes, thanks for sharing.

  32. Rathindra says:

    Hmmmmmm…Good fun on web
    Thanks

  33. vinnie says:

    You’re the man dude!

  34. Reedyseth says:

    Thanks for sharings this mate, you really enlighted me !!

  35. Lee says:

    Great program but without links working it’s fairly limited, it took me a while to figure out the link that isn’t. Here goes click here.

    The onclick=”window.open(this.href);” goes in the hidden window for live linking. I have tried to get the “gettopup” script to open up but this doesn’t work directly but only once the new window has opened, haven’t tried lightbox. Here’s my work in progress http://nolimitwine.com/gallery/index.htm

  36. Lee says:

    Sorry about the above “click here”

    a href=”your_link.html” onclick=”window.open(this.href);” click here

    have taken out the “” on this post so you can see what I mean.

  37. cinek2 says:

    Great tutorial! easy to follow. Thanks for sharing

  38. ionut says:

    hi,
    how do you handle the flash of content during which styles are applied thru javascript?

  39. herman says:

    wowwwwwwwwwww likee it

  40. Kosta says:

    This is a fantastic tutorial and easy to follow. I was trying to mess with it a little bit but could not figure out how you could add a link inside the description to work without right clicking. when clicking on the link, is there a way around to not collapse the image box? It might be right in front of my but cannot figure it out! Thanks!

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.