How to make a navigable & horizontally scrollable portfolio

Posted on Sunday, October 26th, 2008

In this short tutorial I’ll be demonstrating how easy it is to create your own horizontal scrolling portfolio. Obviously you can use this effect for things other than a portfolio; you could use it for a short photo gallery, slideshow or a product showcase…

Have a look at the demo to see what I’m talking about.

Download the demo ZIP

I’ll be using jQuery for this tutorial. We won’t need any plugins. Right let’s start!

We’ll start with a basic XHTML document with jQuery, our StyleSheet and our JS file linked to:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Portfolio - Horizontal Scrolling</title>
    <script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js"></script>
    <script type="text/javascript" src="portfolio.js"></script>
    <link href="portfolio.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div id="container">
 
		<!-- Stuff goes here -->
 
    </div>
</body>
</html>

The next step is to markup the portfolio. I’ll be using an unordered list with each of the portfolio pieces as a list item:

<h1>Portfolio</h1>
 
<ul id="portfolio">
    <li id="piece1">
        <img src="img/1.png" alt="portfolio piece 1 preview" />
        <h2>Portfolio piece 1</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece2">
        <img src="img/2.png" alt="portfolio piece 2 preview" />
        <h2>Portfolio piece 2</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece3">
        <img src="img/3.png" alt="portfolio piece 3 preview" />
        <h2>Portfolio piece 3</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece4">
        <img src="img/4.png" alt="portfolio piece 4 preview" />
        <h2>Portfolio piece 4</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece5">
        <img src="img/5.png" alt="portfolio piece 5 preview" />
        <h2>Portfolio piece 5</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece6">
        <img src="img/6.png" alt="portfolio piece 6 preview" />
        <h2>Portfolio piece 6</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece7">
        <img src="img/7.png" alt="portfolio piece 7 preview" />
        <h2>Portfolio piece 7</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece8">
        <img src="img/8.png" alt="portfolio piece 8 preview" />
        <h2>Portfolio piece 8</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece9">
        <img src="img/9.png" alt="portfolio piece 9 preview" />
        <h2>Portfolio piece 9</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
    <li id="piece10">
        <img src="img/10.png" alt="portfolio piece 10 preview" />
        <h2>Portfolio piece 10</h2>
        <p>Lorem ipsum dolor...</p>
        <p><a href="#">More details &raquo;</a></p>
    </li>
</ul>

We’ll also need to add some basic styles: (portfolio.css)

body,ul,li,h1,h2,p,img {
    margin: 0;
    padding: 0;
    list-style: none;
    border: none;
}
 
#container {
    width: 900px;
    margin: 0 auto;
    font-size: 0.8em;
    font-family: Arial, sans-serif;
}
 
h1 {
    font-size: 2.4em;
    text-align: center;
    padding: 30px 0;
}
 
h2 {
    font-size: 1.3em;
    padding: 0.5em 0;
}
 
#portfolio li {
    overflow: hidden;
    padding: 10px;
    margin: 10px 0;
}
 
#portfolio li img {
    float: left;
    margin: 0 10px 0 0;
}
 
#portfolio li p {
    padding: 0.3em 0 0.5em 0;
}

You’ll notice that each portfolio item has an H2 which contains the title, an IMG (which has a preview of the portfolio piece) and a paragraph explaining the portfolio piece. The next step is to extract the required information using jQuery and generate a navigation menu which will allow users to navigate through each slide of the portfolio.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// portfolio.js
$(function(){ // Function initiates when DOM is ready
 
    // Apply styles to #portfolio and wrap it in #portfolio-wrapper
    $('#portfolio')
        .css({
            height: '163px', // 163 is the height of each portfolio piece 
            width: ($(this).width() * $('#portfolio li').size()) + 'px', // i.e. 900x10 = 9000
            position: 'relative'
        })
        .wrap('<div id="portfolio-wrapper" style="width:900px;overflow:hidden;position:relative;"></div>'); // Wrap in new DIV element (required for scroll to work properly)
 
    $('#portfolio li')
        // Looping through each list-item:
        .each(function(){
 
            var newNavItem = addPortfolioNavItem( $(this).attr('id') , $('img:eq(0)',this).attr('src') , $('h2',this).text() );
 
            newNavItem.children('a:eq(0)').click(function(){
                // When portfolio nav-item is clicked:
                $('img',this).css({opacity:0.8}); // Dim to .8 opacity (to show the user they've been there)
                var id = $(this).attr('href').split('#')[1]; // FIX: IE returns actual HREF instead of href attribute
                var difference = $('#portfolio').offset().left-$('#portfolio li#' + id).offset().left; // leftOffset of ul#portfolio minus leftOffset of selected portfolio piece
                $('#portfolio').animate({left: difference}, 700); // Animate to the value of different over 700 milliseconds
                return false; // prevent default action of links
            });
 
        })
        .css({
            width: '880px', // Specify width as 880 (900 minus 10px padding on each side)
            margin: 0, 
            float: 'left'
        });
 
});
 
function addPortfolioNavItem(id,imgSrc,title) {
 
    // If the new navigation menu has NOT been created yet:
    if(!$('#portfolio-nav').get(0)) { // Test whether nav-menu already exists
        $('<ul id="portfolio-nav"/>')
            .insertBefore('#portfolio-wrapper'); // If it does not exist then create it and insert before #portfolio-wrapper (an element which will be created later)
    }
    // creates a new list item and appends it to #portfolio-nav
    return $('<li><a href="#' + id + '" title="' + title + '"><img width="90" src="' + imgSrc + '" alt="' + title + '" /></a></li>').css({display:'inline'}).appendTo('#portfolio-nav');
 
}

You’ll see that we’re looping through each list item of the portfolio and then calling the addPortfolioNavItem function which will create a new navigation item and append it to the newly created ul#portfolio-nav (which is inserted into the DOM before ul#portfolio. When one of the thumbnail images (in the navigation menu) is clicked it dims to 0.8 opacity to show the user what slides they’ve already viewed, then it uses jQuery’s animate method to smoothly change the left CSS property of ul#portfolio to match the offset of the selected slide. I hope the comments in the code above are sufficient, if you have any questions feel free to ask below.

The DEMO shows a very basic example of what can be achieved with the above JavaScript, obviously you could achieve something far snazzier with a little bit more CSS…

Download the demo ZIP

20 Responses to “How to make a navigable & horizontally scrollable portfolio”

  1. Gravatar #1 :: Evan Says:

    Nice, but it has a bug. If you quickly click many navigation items it will switch to the first one, then the second one, then to the third one, and so on and so forth instead of just going to the last one you clicked.

  2. Gravatar #2 :: James Says:

    Yeh I noticed that, but I see it as a feature, not a bug. If a user clicks multiple times it makes logical sense for it to queue up and then run through the queue progressively. Plus I’d imagine most users won’t be clicking around that quickly…

    It’s easily fixable though, just before the #portfolio is animated its animation queue can be emptied:

    $('#portfolio').stop();
  3. Gravatar #3 :: Sérgio Soares Says:

    Really cool :) thanks

  4. Gravatar #4 :: maro Says:

    where is the .zip example???????????? it’s more easy

  5. Gravatar #5 :: James Says:

    Glad you like it Sergio! :)

    @maro, what do you mean? If you want to download the source it’s pretty easy - just go to the demo - it’s all there. I might offer it all as a downloadable zip, just haven’t got time at the moment.

  6. Gravatar #6 :: James Says:

    @maro - I just added a ZIP file with all the DEMO contents (HERE). Enjoy!

  7. Gravatar #7 :: Andy Gongea Says:

    I really like your JS skills. You’re sick, you know that?!

    Cheers and thanks for this great tut!

  8. Gravatar #8 :: will haven Says:

    There is an issue. If I tab through the page, including some of the “more details” links, then the position of the items is changed. Then when I keydown or click on the “navigation” it shifts all the portfolio items off page.

    Basically, it is assuming that the items will be in a certain position but they can be moved when you focus on links and then the js positioning “breaks”.

  9. Gravatar #9 :: James Says:

    Andy, thanks! :)

    Will, woah, thank you for pointing that out. Right now I can’t see a solid way of solving this but I’ll try to look into it. Again, thanks for letting me know!

  10. Gravatar #10 :: Randy Says:

    I have made some changes to the js. You can view the changes in firefox, safari (both Mac and PC) but IE 6 & 7 do not work and give an error. I switched back to the original code and still it does not render the top navigation. I am using jQuery 1.2.6

    Thanks,
    RLC

  11. Gravatar #11 :: Randy Says:
  12. Gravatar #12 :: ali Says:

    thanksss good codes

  13. Gravatar #13 :: Sarah Says:

    This is just what I need.One question though.
    Sorry if it’s very obvious but it seems to escape me. What part of the code determines the size of the clickable thumbnails?

  14. Gravatar #14 :: Sarah Says:

    Never mind. I should have looked at the code till the end.

  15. Gravatar #15 :: Sarah Says:

    I fugured out how to add an active class to the portfolio nav-item that is clicked but I’d like to have the active class applied to the first portfolio nav-item without it being clicked first. How can I do this? Help?
    Thsi is how I added the active class:
    $(’img’,this).css({opacity:0.5}).parents(’li’).addClass(’active’).siblings(’li’).removeClass(’active’);

  16. Gravatar #16 :: Andi Says:

    Dude!! This is exactly what I need for a project of mine. Thanks.

  17. Gravatar #17 :: Pat Says:

    Hi,

    I like this App.Thanks for the good work.

    Just one thing i can’t explain. The sliding container position get buggy when we use keybord tabulation. After doing it, there’s no easy way to set it the good way.
    Would you have any idea on what’s happening and how to prevent that behaviour ?

  18. Gravatar #18 :: Rohit Says:

    Nice Stuff man…!!!!!!!!!!!
    Can u tell me the difference between DOM and JAVASCRIPTS??????????

  19. Gravatar #19 :: Chris Says:

    Hi James,

    Thanks a lot for this great tutorial!
    I really appreciate the graceful degradation for users that haven’t activated JavaScript…

    As long as you haven’t found a solution for the (really bad) tabbing problem, here’s my little workaround that I came up a few minutes ago:

    Put a skip link _after the last link_ of each portfolio piece, visible only to tabbers and prohibiting them from tabbing on to the next link (first link of the second portfolio piece).

    HTML:
    e.g.
    Please go back to top of page via this link to prevent problems with tab navigation!
    or something like this.

    CSS:
    e.g.
    a.skip3 {position:absolute;left:-9999px;top:-9999px;font-size: 100%;font-weight: normal; color: #cc0000 !important;}
    a.skip3:focus,
    a.skip3:active {z-index:100;position:relative;top:1px;left:4px;color:#333;background-color:#fafafa;outline:1px dotted #e4e4e4 !important;padding:2px 5px 3px;text-decoration:underline; }

    Important:
    position:relative for a.skip3:focus and a.skip3:active! Otherwise the skip link won’t show up once you are past the first piece of portfolio.

    Hope this helps and thanks again!

    Cheers, Chris

  20. Gravatar #20 :: Chris Says:

    Here’s are the HTML and CSS parts again:

    HTML:
    e.g.
    Please go back to top of page via this link to prevent problems with tab navigation!
    or something like this.

    CSS:
    e.g.
    a.skip3 {position:absolute;left:-9999px;top:-9999px;font-size: 100%;font-weight: normal; color: #cc0000;}
    a.skip3:focus,
    a.skip3:active {z-index:100;position:relative;top:1px;left:4px;color:#333;background-color:#fafafa;outline:1px dotted #e4e4e4;padding:2px 5px 3px;text-decoration:underline; }

    Cheers, Chris

Leave a Response

Allowed elements include: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <em> <i> <q cite=""> <strike> <strong>.
Please wrap any multi-line code snippets in the <pre> element. (Characters are automatically escaped this way) - also you can specify the language within the lang attribute, e.g. <pre lang="php">echo 'hello';</pre>. (Available languages: "php", "javascript", "html4strict")