1. Code
  2. JavaScript

A Bullet-Proof Content Viewer

Scroll to top
11 min read

In this tutorial, we're going to look at how we can easily create an attractive and space-saving content viewer which even works with JavaScript disabled. We'll build a solid core of semantic HTML, styled with some basic CSS and we'll then use jQuery to add further enhancements in the form of transition animations.

The following screenshot shows what we'll end up with by the end of the tutorial:


Getting Started

First, let's create the underlying HTML page for our content viewer; in a new file in your text editor create the following page:

1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2
<html>
3
	<head>
4
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
		<title>Bullet-proof Content Viewer</title>
6
        <link rel="stylesheet" type="text/css" href="contentviewer.css">
7
	</head>
8
	<body>
9
    	<div id="viewer">
10
        	<ul id="nav">
11
            	<li class="thumb1"><a href="#panel1" title="Panel 1">Panel 1</a></li>
12
				<li class="thumb2"><a href="#panel2" title="Panel 2">Panel 2</a></li>
13
				<li class="thumb3"><a href="#panel3" title="Panel 3">Panel 3</a></li>
14
				<li class="thumb4"><a href="#panel4" title="Panel 4">Panel 4</a></li>
15
				<li class="thumb5"><a href="#panel5" title="Panel 5">Panel 5</a></li>
16
			</ul>
17
			<div id="panels">
18
				<div id="slider">
19
               		<div id="panel1">
20
                    	<img src="img/image1.jpg" alt="Image 1">
21
						<p>Supernova 1994D, visible as the bright spot at the lower left, occurred in the outskirts of the disk galaxy NGC 4526.</p>
22
	               	</div>
23
 					<div id="panel2">
24
                    	<img src="img/image2.jpg" alt="Image 2">
25
						<p>Radiation from hot stars off the top of the picture illuminates and erodes this giant, gaseous pillar.</p>
26
					</div>
27
					<div id="panel3">
28
	               		<img src="img/image3.jpg" alt="Image 3">
29
	 					<p>V838 Mon is located about 20,000 light-years away from Earth at the outer edge of the Milky Way.</p>
30
					</div>
31
					<div id="panel4">
32
	               		<img src="img/image4.jpg" alt="Image 4">
33
						<p>The Sombrero Galaxy is an unbarred spiral galaxy in the constellation Virgo approximately 30 million lights years away.</p>
34
					</div>
35
               			<div id="panel5">
36
	               		<img src="img/image5.jpg" alt="Image 5">
37
						<p>This region of active current star formation is part of a diffuse emission nebula about 6,500 light-years away.</p>
38
					</div>
39
				</div>
40
			 </div>
41
		</div>
42
		<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
43
		<script type="text/javascript">
44
		</script>
45
	</body>
46
</html>

Save this as contentviewer.html. We'll start off with some clean and semantic HTML, using no more elements than is strictly necessary. We have an outer container for the viewer in its entirety, which contains a navigation structure for selecting which content panel to view and a second container for the content panels themselves.

Within the content panel container, we have another container used to enclose all of the content panels (this is needed to display the content panels correctly) and the content panels themselves. Each panel contains an image and a span describing the image.

The main images are added to the document as proper HTML images using the <img> element - this is because they are content and should be visible to assistive technologies or users with both scripting and CSS disabled or otherwise not available. The navigation structure will also contain images, but as these are not classed as content they do not need to be visible in all situations and can therefore be added using CSS, hence the additional class names on the <li> elements.

Right now the page should appear like this:

It doesn't look great, but the document flows correctly and the elements are all clearly visible and usable.

Making it work with CSS

We can now use CSS to transform the content viewer into a functioning interface that doesn't look terrible. W already linked to the style sheet in the head of our page so let's create it now; in a new file in your text editor add the following selectors and rules:

1
#viewer { width:700px; margin:auto; }
2
#nav { width:200px; float:left; margin:0; padding:0; list-style-type:none; }
3
#nav li { width:200px; height:100px; padding:0; }
4
#nav li a { display:block; width:100%; height:100%; text-indent:-9999px; overflow:hidden; background:url(img/thumbs.png) no-repeat 0 0; }
5
#nav li a:hover, #nav li a.on { background-position:-200px 0; }
6
#nav li.thumb2 a { background-position:0 -100px; }
7
#nav li.thumb2 a:hover, #nav li.thumb2 a.on { background-position:-200px -100px; }
8
#nav li.thumb3 a { background-position:0 -200px; }
9
#nav li.thumb3 a:hover, #nav li.thumb3 a.on { background-position:-200px -200px; }
10
#nav li.thumb4 a { background-position:0 -300px; }
11
#nav li.thumb4 a:hover, #nav li.thumb4 a.on { background-position:-200px -300px; }
12
#nav li.thumb5 a { background-position:0 -400px; }
13
#nav li.thumb5 a:hover, #nav li.thumb5 a.on { background-position:-200px -400px; }
14
#panels { width:500px; height:500px; overflow:hidden; position:relative; float:left; }

Save this as contentviewer.css in the same directory as the HTML page. I've kept the design minimal so that we can focus on what makes it work; the navigation and viewing panel are floated next to each other and the individual list items are given their background images and hover states. We've also added on states as well. This part of the CSS is purely for layout/presentation and does not affect functionality.

What's important is how the containers and content images are arranged. The outer container (#panels) is given a fixed size that matches the height and width of a single content image and has its overflow property set to hidden to ensure that only a single image is displayed at any one time. This is the only really required CSS for the content viewer to work to a basic degree. If you look at the page now, you'll see that you can click any of the thumbnails and the corresponding full-sized image will be displayed in the viewing panel:

This is great because it remains functional and accessible without relying on JavaScript. We'll move on to use jQuery to add some smooth transitional effects in just a moment, but first we should add a few more styles that are required for the animations, and to display the paragraphs correctly. Add the following code to the bottom of contentviewer.css:

1
#slider { width:2500px; height:500px; }
2
#slider div { float:left; position:relative; }
3
#slider p { position:absolute; bottom:0; left:0; color:#fff; font:16px "Trebuchet MS"; margin:0; width:90%; height:45px; padding:5px 5% 10px; background-color:#000; }

The inner container (#slider) is given a fixed height equal to a single content image, but a width equal to all of the images. Then the individual containers holding the images and paragraphs are floated to the left to make them stack up horizontally. Finally, the paragraphs are styled and positioned as well so that they overlay each image:

Floating the individual content panels to the left and setting the size of the slider is not strictly necessary, without these the images will just stack up vertically. This would mean that any animations we added would have to move the content panels vertically as well, but we'll be animating them horizontally.

One point I should make here is that the code so far does not work in Opera; for some reason, Opera cannot use the anchors on the page to show the different content panels when one of the navigation items is clicked. This is a big fail and seems to be a problem in more than one version of Opera. There is a fix apparently and anyone that uses Opera as their main browser will have hopefully implemented this fix already. It isn't a problem when the JavaScript has been added though.

Adding the jQuery Effects

As the page now works on its own, we can add the JavaScript that will turn this from a functional page into an attractive interface. We left an empty <script> element at the bottom of our page, let's fill it up now; start with the following code:

1
(function($){
2
	//code here...

3
	})(jQuery);

What we're doing here with this first bit of code is ensuring that the $ character will always refer to the jQuery object; if this code is to be used on a page with another JS library which also uses the $ character we can be sure that within our code $ will always refer to jQuery. We're creating an alias for jQuery that matches the $ character by passing the jQuery object into a self-executing anonymous function. This is a tip I picked up from Cody Lindley's excellent jQuery Enlightenment book.

Within our self-executing anonymous function add the rest of the code:

1
//object containing margin settings

2
var margins = {
3
	panel1: 0,
4
	panel2: -500,
5
	panel3: -1000,
6
	panel4: -1500,
7
	panel5: -2000
8
}
9
10
//handle nav click

11
$("#nav a").click(function(e){
12
13
	//stop browser default

14
	e.preventDefault();
15
16
	//remove on states for all nav links

17
	$("#nav a").removeClass("on");
18
19
	//add on state to selected nav link

20
	$(this).addClass("on");
21
22
	//set margin of slider to move

23
	$("#slider").animate({
24
		marginLeft: margins[$(this).attr("href").split("#")[1]]
25
	});
26
});

First of all we define a simple object that is used to hold the margin positions of each of the different panels; to show the first panel, the left margin of the #slider container should be at 0, to show the second panel it should be at -500px, and so on and so forth. Using an object like this is an easy way to store the values for each panel.

Next we add a click-handler for the navigation items; we want to show the panels using a nice animation so we have to prevent the browser from following the anchor. We do this using the event object (e) which we pass into our click-handler. The event object has the preventDefault() method built into it, so we call this on the event object and it stops the browser from executing its default action.

We use this part of the script to set the on class for the currently selected navigation item; this is the only part of the script that relies purely on JavaScript to function and simply removes the on class from any of the items on which it already exists, then adds back to the item that was clicked. There may be a way of doing this purely with CSS using the :active pseudo class, but as it is purely for a visual aid I don't think it matters as much if this aspect doesn't work with scripting disabled.

We now need to show the correct panel by animating the #slider container. Remember, we do this by setting the margin-left style property of the slider. In order to get the correct value for the marginLeft depending on whichever navigation link is clicked we just get the href attribute using the $(this) reference (which will point to whichever link was clicked) and perform a standard JavaScript split based on the # symbol.

We're doing this the most semantic way - by using information we already have available, in this case the href attribute. We could add id attributes to each of the navigation links, but then we're adding unnecessary extra information purely for our script which really we should avoid doing. Using the href makes sense but we can't use # at the start of our object keys (panel1, panel2, etc) so we need to get rid of this character from the value that is returned.

Save the page and view it in your browser; you should find now that when one of the navigation items is clicked, the corresponding panel will slide smoothly into view. There's just one more thing we need to do - the descriptive paragraphs don't need to be visible all of the time (as long as JavaScript is enabled) so we can set these so that they are initially hidden from view and are shown when the content image is moused-over. We can add this behaviour easily using just a little bit more script; directly after the click-handler add the following code:

1
//hide descriptive text

2
$("#slider p").hide();

By hiding the paragraphs with JavaScript we ensure that the content is only hidden if scripting is enabled. This means that the content remains accessible even if JavaScript is disabled on the client. Next we just need to show the text when the main image is hovered over, to do that we can use jQuery's hover() method; after the code we just added continue with the following:

1
//show descriptive text on mouseover (hide on mouseout)

2
$("#slider").hover(
3
	function() {
4
		$(this).find("p").slideDown();
5
	}, function() {
6
		$(this).find("p").slideUp();
7
});

All we do is use the slideDown() and slideUp() methods within the hover() method to show and hide the paragraphs when appropriate.

Summary

This now brings us to the end of this tutorial; we've seen how we should build our interfaces so that they work even when JavaScript is disabled, and that, instead of relying on JavaScript for functionality, we're just relying on it to add attractive effects. We also saw that when we want to show content using JavaScript, such as the descriptive paragraphs, we first hide it using JavaScript so that it is only hidden when scripting is enabled which ensures the content is accessible even without scripting.

Your thoughts?

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We're looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you're of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.