Bullet-proof Content Viewer

A Bullet-Proof Content Viewer

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:

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

#viewer { width:700px; margin:auto; }
#nav { width:200px; float:left; margin:0; padding:0; list-style-type:none; }
#nav li { width:200px; height:100px; padding:0; }
#nav li a { display:block; width:100%; height:100%; text-indent:-9999px; overflow:hidden; background:url(img/thumbs.png) no-repeat 0 0; }
#nav li a:hover, #nav li a.on { background-position:-200px 0; }
#nav li.thumb2 a { background-position:0 -100px; }
#nav li.thumb2 a:hover, #nav li.thumb2 a.on { background-position:-200px -100px; }
#nav li.thumb3 a { background-position:0 -200px; }
#nav li.thumb3 a:hover, #nav li.thumb3 a.on { background-position:-200px -200px; }
#nav li.thumb4 a { background-position:0 -300px; }
#nav li.thumb4 a:hover, #nav li.thumb4 a.on { background-position:-200px -300px; }
#nav li.thumb5 a { background-position:0 -400px; }
#nav li.thumb5 a:hover, #nav li.thumb5 a.on { background-position:-200px -400px; }
#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:

#slider { width:2500px; height:500px; }
#slider div { float:left; position:relative; }
#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:

(function($){
	//code here...
	})(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:

//object containing margin settings
var margins = {
	panel1: 0,
	panel2: -500,
	panel3: -1000,
	panel4: -1500,
	panel5: -2000
}

//handle nav click
$("#nav a").click(function(e){

	//stop browser default
	e.preventDefault();

	//remove on states for all nav links
	$("#nav a").removeClass("on");

	//add on state to selected nav link
	$(this).addClass("on");

	//set margin of slider to move
	$("#slider").animate({
		marginLeft: margins[$(this).attr("href").split("#")[1]]
	});
});

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:

//hide descriptive text
$("#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:

//show descriptive text on mouseover (hide on mouseout)
$("#slider").hover(
	function() {
		$(this).find("p").slideDown();
	}, function() {
		$(this).find("p").slideUp();
});

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

Add Comment

Discussion 66 Comments

  1. Josh says:

    Oh looks pretty cool, thanks

  2. Adit Gupta says:

    Nice content viewer with very simple jquery!

  3. Qbessi says:

    Looks Good.

    Would of been more impressed if it was in raw JavaScript.

    GJ

    • Dan Wellman says:
      Author

      Thanks for reading :)

      Everything done here is possible with pure JS and not even that difficult for something this simple, the problem with raw JS is that it’s long-winded and annoying in the sense that you have to code all of the browser work arounds yourself, so instead of the 6 lines of code for the click handler that shows each content panel with jQuery, you have about 20 lines of code – half for the good way (addEventListener) and the other half for the IE way. jQuery means you don’t have to worry about that.

    • David Beveridge says:

      If you’re want a purist take on JavaScript, I recommend David Flanagan’s “JavaScript: The Definitive Guide” (O’Reilly) ISBN: 0-596-10199-6. It’s a monster of a book, but I’ve never seen a better resource on core JavaScript. Mr. Flanagan’s documentation on objects, functions, and scope is particularly good.

  4. lawrence77 says:

    A pretty nice tut, A pretty nice tut, :D

  5. Aziz Light says:

    I haven’t read the tutorial yet, but I saw the demo and added the article to my ReadItLater list.

    There is a small bug in the demo page though: if you hover over the mouse over the main image several time the label’s animation won’t be interupted, instead the label will appear and disappear the same amount of times you hovered over with your mouse. Once again, besides that the tut looks very interesting. So good job in advance.

  6. Cool concept, but definitely not bulletproof…

    In Safari, when on the 4th image down, the text is cut off by the bottom of the viewer. Also, it doesn’t account for zooming in. If I zoom my text in, bam cut off again.

    It also took me a bit to figure out there was text on the image. There’s not much of a visual queue that I can hover over it for the text to show.

    Some things to think about….

    • Abhijit says:

      This is a good tutorial and it explains the technique very well. This however is not a bulletproof plugin so some minor issues will be there.

  7. nix says:

    very cool. Thanks

  8. Nikki says:

    Reminds me a little of the Contrast Theme on ThemeForest… http://themeforest.net/item/contrast/full_screen_preview/68089
    I love what the author has done with that theme.

    Nice tut :)

  9. Rerenwo says:

    Awesome effect!

  10. Juan C Rois says:

    Thanks Dan for the tutorial, I’m sure this can be the a good base for anyone needing a content viewer and customize it pretty easily.
    It’s a little buggy though, besides the two issues mentioned above, I noticed that it breaks when you enable or disable JS, after disabling or enabling JS, I refreshed the page (have to anyways).
    I like it because little code is used to create it but it needs a little more work in order to make it work flawlessly.
    Thanks Dan.

  11. Alexander says:

    There is an Error in the hover: if u hover at the bottom of the img, the hoverOut gets fired because the slidingIn-Information goes under the mouse which results in blinking.

  12. Sam says:

    How would someone of below average intelligence go about making this bad boy autoslide?

  13. That’s great !

    Thanks !

    That’s useful !

  14. Raoul says:

    Niiice ! Think i could even try to use it x) (according to my poor coding lvl)

    Tks !!
    R.

  15. qwerty says:

    so cool!!

  16. Chris says:

    Nice one! But there is always something to fix, in this case being the outline displayed around the images on click. You may want to remove that in your CSS when using this content viewer for clients etc.

    Cheers

  17. thelastpulse says:

    I really like how this works with Javascript disabled. It’s really important for a site to degrade gracefully and…I often overlook its importance. Nevertheless, awesome tutorial!

  18. Meshach says:

    Amazing Tut!

  19. Towel says:

    Great tut, I’m certain that I will use some of the methods very soon. Btw, JW, the site’s CSS farted and some of the content overflowed into the sidebar, just wanted to let you know.

  20. jdsans says:

    Cool content viewer

  21. Cameron says:

    Is there a rotating slideshow function for this slider? That would make this exactly what I was looking for… If not, is there a way to do this using an additional script? Any recommendations?

  22. That is really neat.

  23. Kevin Martin says:

    Over all, the concept is great, but the text could be reworked a bit better as to first show that there is text there (like an arrow pointing up) and when hovering over the text, it stays up instead of hiding.

    Moving my mouse over it is a bit buggy. Great content viewer and article!

    1up.

  24. hiro says:

    The event of ‘#slider’ should be:
    $(“#slider”).hover(
    function() {
    $(this).find(“p”).stop(true, true).slideDown();
    }, function() {
    $(this).find(“p”).stop(true, true).slideUp();
    });
    or, while I move the mouse multi-time over ‘#slider’, slideUp() will be executed repeatedly!

  25. Ben says:

    It may work without Javascript but will it work without jQuery?

    • Juan C Rois says:

      Jeffrey Way might be more qualified to answer this, but in my understanding Jquery is basically a javascript framework. So, if javascript is disabled Jquery and any other framework won’t work.

      • Mike says:

        Correct, Jquery is a Javascript framework. So yes, if Javascript is disabled, Jquery & any other framework will be disabled as well as it depends on Javascript to run..

  26. dizelbox says:

    That is very nice.

  27. Haziq says:

    when i hover over the description, it starts hopping madly! LOL!

  28. silvers says:

    what a well thought out and in depth tutorial. i really enjoyed reading and i think i learned from it too.

    thank you

  29. Edge1 says:

    Wow, it just so happens I am looking for something almost exactly like this. Very nice tutorial, thank you.

    Anyone know of a full featured version of something like this??

    I am interested in a content viewer that has vertical boxes/buttons on left like this example and auto rotates with image on right changing. Also the image on right needs to be hyperlinked or text layered on top that can be linked.

    I am not a coder of any sort, any help would be much appreciated. Thank you.

  30. David Annez says:

    I assumed that when you said “bulletproof” that you meant it was cross browser compatible and works without javascript.

    Sadly, it does not work in Opera; so I wouldn’t really say it’s bulletproof by any means. Something that is bulletproof should work on every browser flawlessly. This does not.

    Thanks for the tutorial though, it sure looks nice on FF, where it does work.

    • Dan Wellman says:
      Author

      Yep that’s what I meant by bullet-proof. I did dedicate a paragraph in the tutorial explaining that it didn’t work in Opera. If you scale the page right back to just the HTML and CSS, it still doesn’t work in Opera and if you google ‘Page anchors don’t work in Opera’ you will see masses of information relating to the fact that simple in-page anchors often don’t work in Opera, which has been a problem for several major revisions of the browser.

      The YUI has a component that deals with menus, including a right-click context menu. That doesn’t work in Opera either and if one of the biggest, most popular websites in the world can openly produce and support code that dosn’t work in Opera, I think this simple little page (that does with with JS enabled btw) is probably going to be ok :)

      • David Annez says:

        My bad! I missed that paragraph entirely.

        Yeah, Opera likes to be fussy at points with certain things (too bad I use it as my main browser!).

        Although you seem to state (correct me if I read it wrong) that it works with javascript ENABLED, the page, but for me (using the latest version of Opera and JS is always enabled) the javscript either doesn’t load or doesn’t work. When the page loads up, it’s as if it’s loading only the HTML and ignores the javascript, which is what I don’t understand. My post was actually directed at it not working AT ALL, rather than just without the javascript. You seem to be getting different results though, so it could be my end… But I certainly haven’t edited anything to make the javascript not work… Which is strange.

      • Dan Wellman says:
        Author

        Yeah, my version of Opera (Win7, default install, latest version, no fancy add-ons) runs the JS perfectly so all of the content panels can still be navigated.

        Just tested the demo linked to this tutorial also (in case there was a wp issue that I didn’t see at home) on my work machine (vista, default opera install, latest version) and that works perfectly as well.

  31. jmarreros says:

    very nice article, knowing this stuff is very usefull, thanks.

  32. good tutorial. Thajnk

  33. These tutorials just keep getting better. Can’t wait to see what you guys come up with next.

  34. Vincent says:

    When quickly hovering over the image, it goes on sliding when your not focusing it. (Sorry for my bad english)

    You should rather use this code:

    # $(“#slider”).hover(
    # function() {
    # $(this).find(“p”).stop().slideDown();
    # }, function() {
    # $(this).find(“p”).stop().slideUp();
    # });

    ( I added the stop() )

  35. felipe says:

    autoslide would be really cool. also, the first panel could be selected when the page load. anyone?

  36. tomi says:

    Thanks for such a great tutorial.

    The buttons for the images are placed vertical.

    So if someone wants the slide to go vertical.

    Go to the html page:

    Where the javascript is write marginTop instead of marginLeft

    When your high of the content image is not 500 px you need to change the margins, were the javascript is:

    var margins = {

    panel1 = 0,
    panel2 = -500,

    and so on

    But if you made it the same way like in the tutorial you dont need to change that margins.

    Also very important, go to the css file and in #panels and #slider div
    remove float:left;

    That´s it.

    • tomi says:

      A little bit of correction:

      VERY important ! Just remove float:left; from #slider div

      NOT from #panels !!

      the float: left; from #panels is necessary !!!!!!!

  37. tomi says:

    Ahh another good thing is when you make the first button activated at the beginning.

    To make this just go to the html file and make from this:

    Panel 1

    This (just class=”on” added):

    Panel 1

  38. tomi says:

    Uppps it rendered the html code xD

    so again:

    To make this just go to the html file and make from this:

    …. a href=”#panel1″ title=”Panel 1″ ….

    This (just class=”on” added):

    …. a class=”on” href=”#panel1″ title=”Panel 1″ ….

  39. Alex says:

    What do I have to do if I want to add more than 5 thumbs?

    • Tyler says:

      Obviously make sure you have the margins set even if you arent using all the panels. But once you have that done edit the #slider width and you should be good.

  40. sdvs says:

    if any images fail to load, which happened to 2 images when i clicked the demo, the entire thing falls apart

  41. ApfelBeisser says:

    Can anybody edit the autoslide and first-panel-selected script? I’ve been searching for this. Thx

  42. e11world says:

    Thank you very much for this amazing slider and thanks for all the comments that improved it as well!

  43. Web Dizajn says:

    Im using this for my client website.. thx soooo much!!

  44. James May says:

    I couldn’t get it to work. I got as far as making it work like a disjointed rollover which I can already do. There is no text, no sliding of the images into place. Did it 3 times. Couldn’t get it to work.

  45. Thank you for another excellent article. Where else could anybody get that type of information in such a perfect means of writing? I have a presentation next week, and I am at the search for such info.

  46. moona says:

    thanx soooo much!!! you SAVED my life…

  47. alovilla says:

    nice slider.I love jquery

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.