jQuery

Creating a “Filterable” Portfolio with jQuery

Tutorial Details
  • Technology: jQuery, CSS
  • Difficulty: Intermediate
  • Completion Time: 1-2 hours

If you have worked in your field for a while, there is a pretty good chance that you have a rather extensive portfolio. To make it a little easier to navigate, you will probably be tempted to break them into different categories. In this tutorial, I will show you how to make “filtering by category” a little more interesting with just a little bit of jQuery.

Final Product

1. The Markup

Our portfolio is nothing more than a simple unordered list:

<ul id="portfolio">
	<li><a href="#"><img src="images/a-list-apart.png" alt="" height="115" width="200" />A List Apart</a></li>
	<li><a href="#"><img src="images/apple.png" alt="" height="115" width="200" />Apple</a></li>
	<li><a href="#"><img src="images/cnn.png" alt="" height="115" width="200" />CNN</a></li>
	<li><a href="#"><img src="images/digg.png" alt="" height="115" width="200" />Digg</a></li>
	<li><a href="#"><img src="images/espn.png" alt="" height="115" width="200" />ESPN</a></li>
	<li><a href="#"><img src="images/facebook.png" alt="" height="115" width="200" />Facebook</a></li>
	<li><a href="#"><img src="images/google.png" alt="" height="115" width="200" />Google</a></li>
	<li><a href="#"><img src="images/netflix.png" alt="" height="115" width="200" />Netflix</a></li>
	<li><a href="#"><img src="images/nettuts.png" alt="" height="115" width="200" />NETTUTS</a></li>
	<li><a href="#"><img src="images/twitter.png" alt="" height="115" width="200" />Twitter</a></li>
	<li><a href="#"><img src="images/white-house.png" alt="" height="115" width="200" />White House</a></li>
	<li><a href="#"><img src="images/youtube.png" alt="" height="115" width="200" />YouTube</a></li>
</ul>

Note: I was by no means a part of creating these wonderful sites; I am just using them as examples.


2. Categorizing the Portfolio

We are going to assume that our portfolio can be broken down into 5 categories:

  • Design
  • Development
  • CMS
  • Integration
  • Information Architecture

In order to use the categories we have defined, we will convert them to lowercase and replace all spaces with hyphens:

  • Design = design
  • Development = development
  • CMS = cms
  • Integration = integration
  • Information Architecture = information-architecture

We are going to assume that each portfolio item could be in one or many categories, so we are going to randomly add our newly created categories as classes to the list items:

<ul id="portfolio">
	<li class="cms integration">
		<a href="#"><img src="images/a-list-apart.png" alt="" height="115" width="200" />A List Apart</a>
	</li>
	<li class="integration design">
		<a href="#"><img src="images/apple.png" alt="" height="115" width="200" />Apple</a>
	</li>
	<li class="design development">
		<a href="#"><img src="images/cnn.png" alt="" height="115" width="200" />CNN</a>
	</li>
	<li class="cms">
		<a href="#"><img src="images/digg.png" alt="" height="115" width="200" />Digg</a>
	</li>
	<li class="design cms integration">
		<a href="#"><img src="images/espn.png" alt="" height="115" width="200" />ESPN</a>
	</li>
	<li class="design integration">
		<a href="#"><img src="images/facebook.png" alt="" height="115" width="200" />Facebook</a>
	</li>
	<li class="cms information-architecture">
		<a href="#"><img src="images/google.png" alt="" height="115" width="200" />Google</a>
	</li>
	<li class="integration development">
		<a href="#"><img src="images/netflix.png" alt="" height="115" width="200" />Netflix</a>
	</li>
	<li class="information-architecture">
		<a href="#"><img src="images/nettuts.png" alt="" height="115" width="200" />NETTUTS</a>
	</li>
	<li class="design information-architecture cms">
		<a href="#"><img src="images/twitter.png" alt="" height="115" width="200" />Twitter</a>
	</li>
	<li class="development">
		<a href="#"><img src="images/white-house.png" alt="" height="115" width="200" />White House</a>
	</li>
	<li class="cms design">
		<a href="#"><img src="images/youtube.png" alt="" height="115" width="200" />YouTube</a>
	</li>
</ul>

Adding Category Navigation

Now that we have the portfolio pieces in place, we are going to need some way to navigate through them. Another unordered list should do:

<ul id="filter">
	<li class="current"><a href="#">All</a></li>
	<li><a href="#">Design</a></li>
	<li><a href="#">Development</a></li>
	<li><a href="#">CMS</a></li>
	<li><a href="#">Integration</a></li>
	<li><a href="#">Information Architecture</a></li>
</ul>

Since I want the default view of the portfolio to show All items, I have assigned a class of current to the first list item.

You will probably look at that and question me on the accessibility of this example. My thought is that you have 3 options to solve that problem.

  1. When creating a portfolio like this, there is a strong probability that it will be database driven. Thus, you should be able to create a separate page for each category. So if a user does not have JavaScript enabled, they can go to the separate page with the filtered portfolio.
  2. You can use a similar technique from my last tutorial: setting a parameter in the url.
  3. You could always just write in the navigation with JavaScript before the portfolio items:
    $(document).ready(function() {
    	$('ul#portfolio').before('<ul id="filter"><li class="current"><a href="#">All</a></li><li><a href="#">Design</a></li><li><a href="#">Development</a></li><li><a href="#">CMS</a></li><li><a href="#">Integration</a></li><li><a href="#">Information Architecture</a></li></ul>');
    });

Ok, you’ve got my notes on accessibility, so don’t criticize me for not thinking about it.


3. The CSS

This tutorial is not meant to be about CSS, so I’m going to run through the CSS pretty quickly.

I always start with some basic styles as a sort-of framework, so I’m not going to go over those styles right now. These styles basically just act as a reset and define some styling for basic elements.

To start, I just want to display the categories across the top horizontally with a border between each:

ul#filter { 
	float: left; 
	font-size: 16px; 
	list-style: none; 
	margin-left: 0; 
	width: 100%;
}
ul#filter li { 
	border-right: 1px solid #dedede;
	float: left;
	line-height: 16px;
	margin-right: 10px;
	padding-right: 10px;
}

Next, I want to remove the border from the last list item (in browsers that support it) and change the display of the links:

ul#filter li:last-child { border-right: none; margin-right: 0; padding-right: 0; }
ul#filter a { color: #999; text-decoration: none; }

I also want to make sure and differentiate the currently selected category:

ul#filter li.current a, ul#filter a:hover { text-decoration: underline; }
ul#filter li.current a { color: #333; font-weight: bold; }

Ok, now that we have the category navigation styled, let’s focus on the actual layout of the portfolio. Let’s plan on floating 3 list items next to each other with a border around each one:

ul#portfolio { 
	float: left; 
	list-style: none; 
	margin-left: 0; 
	width: 672px;
}
ul#portfolio li { 
	border: 1px solid #dedede; 
	float: left; 
	margin: 0 10px 10px 0; 
	padding: 5px;
	width: 202px;
}

Now, we just need to add some basic styling for the images and links:

ul#portfolio a { display: block; width: 100%; }
ul#portfolio a:hover { text-decoration: none; }
ul#portfolio img { border: 1px solid #dedede; display: block; padding-bottom: 5px; }

Compensating for Internet Explorer 6

Of course, let’t not forget about our friend IE6. Once you start clicking through some of the filters, the layout gets a little crazy.

IE Screenshot

From what I can tell, it is the dreaded IE Double Margin bug. I tried applying display: inline to the list items once they are filtered, but that didn’t seem to fix it. So my best solution was to just halve the right margin:

ul#portfolio li { margin-right: 5px; }

We are of course only going to serve this IE6 specific stylesheet using conditional comments:

<!--[if lt IE 7]>
<link href="stylesheets/screen-ie6.css" type="text/css" rel="stylesheet" media="screen,projection" />
<![endif]-->

Yeah, it doesn’t look as good, but you know what: I don’t care. If you are using IE6, you deserve it.


4. The jQuery

Ok, now that we have the markup and CSS all done, let’t get to the important part of this tutorial: the JavaScript.

We are going to start by including the latest version of jQuery in the head of our document.

<script type="text/javascript" src="scripts/jquery.js"></script>

Next, we want to execute our code once the document is loaded.

$(document).ready(function() {

});

Now, we don’t want to do anything until one of our categories is clicked. We also want to make sure that we do not follow the href value of the link, so we need to return false:

$('ul#filter a').click(function() {
	return false;
});

Once a category link is clicked, I want to do a couple of things: remove the outline on the clicked link, remove the class of current on the list item that has it, and add the class of current on the parent of the clicked link:

$(this).css('outline','none');
$('ul#filter .current').removeClass('current');
$(this).parent().addClass('current');

Next, we want to get the text inside of the clicked link, convert it to lowercase, and replace any spaces with hyphens (just like before when we were creating the category classes):

var filterVal = $(this).text().toLowerCase().replace(' ','-');

The first case of the script is when the All link is clicked. When that is clicked, we want to show all of the portfolio items and remove the class of hidden:

if(filterVal == 'all') {
	$('ul#portfolio li.hidden').fadeIn('slow').removeClass('hidden');
}

Otherwise, one of the actual categories were clicked. So we want to go through each portfolio item and check to see if it has the class that equals the value of the category clicked. If it does not have the class, we want to fade out the list item and add a class of hidden. It it does have the class, we want to fade it in and remove the class of hidden:

else {
	$('ul#portfolio li').each(function() {
		if(!$(this).hasClass(filterVal)) {
			$(this).fadeOut('normal').addClass('hidden');
		} else {
			$(this).fadeIn('slow').removeClass('hidden');
		}
	});
}

The Finished Script

Let’s take a look at the entire script:

$(document).ready(function() {
	$('ul#filter a').click(function() {
		$(this).css('outline','none');
		$('ul#filter .current').removeClass('current');
		$(this).parent().addClass('current');
		
		var filterVal = $(this).text().toLowerCase().replace(' ','-');
				
		if(filterVal == 'all') {
			$('ul#portfolio li.hidden').fadeIn('slow').removeClass('hidden');
		} else {
			$('ul#portfolio li').each(function() {
				if(!$(this).hasClass(filterVal)) {
					$(this).fadeOut('normal').addClass('hidden');
				} else {
					$(this).fadeIn('slow').removeClass('hidden');
				}
			});
		}
		
		return false;
	});
});

Some people may not like the effect, but I think it looks pretty cool how they all kind of dance around. This is definitely not the only way to accomplish something like this, and it could easily be built on to do other things.

This technique is actually evolved from the coding that I did for my company’s portfolio.

Final Product

5. One Quick Note

You may have noticed that I was adding and removing the class of hidden on the items as I was toggling the visibility. While I didn’t end up doing anything with the class, I try and make it a habit to add and remove classes to denote the state they are in. You may not use it immediately, but it can provide a hook for you do stuff with in the future.

  • Subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.


Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Pingback: Create a Filterable Portfolio Page in WordPress with jQuery \ We Are Pixel8

  • Pingback: Download PhotoBax: Photography Business Theme free – Daily Sharing Wordpress, Joomla, Magento free premium Template | Daily Download Wordpress, Joomla, Magento Templates

  • Pingback: PhotoBax: Photography Business Theme (Photography) | Themes Tub

  • Pingback: Themeforest PhotoBax: Photography Business Theme (Photography)

  • Pingback: PhotoBax: Photography Business Theme (Photography) » Feedstube

  • Pingback: PhotoBax: Photography Business Theme (Photography) : Quick Download

  • Pingback: Blagg: Premium Blog Template | ThemeForest - Themes - Vorlagen - Templates - WordPress - Magento

  • http://tellatek.com pingram3541

    Great tutorial. I struggled with other “plugins” like quicksand working with jquery masonary and was able to get this working…except that the hidden elements retain their space in the page.

    It would be nice if I could get it to work like the demo where the ‘filtered’ results re-align within the container left-to-right, top-to-bottom but I haven’t figured this out yet.

    • Ayj

      Did you ever figure this out. I’m having similar issues when attempting to apply this filter to roundabout.js ? I’m assuming it can be done, but one of my issues is based in my ‘s being generated in php based on a mysql_query for results to populate the ‘s.

  • http://www.anjieya.com Anjieya

    Could we make different color for the selected cursor?
    Because i only saw the link become no under line after selected cursor.
    awesome work.
    thanks you

  • Pingback: BusinessDesign » スライドアニメーションで切り替わる項目を絞り込む

  • http://www.gamesawy.net العاب فلاش

    Nice effect. thanks..

  • http://www.coaja.nl Bernard

    Thanks! It works great.

  • Pingback: PhotoPics | ThemeForest - Themes - Vorlagen - Templates - WordPress - Magento

  • Pingback: Progress Template | ThemeForest - Themes - Vorlagen - Templates - WordPress - Magento

  • http://www.52kards.com Asad

    Is it possible to implement a double filter? For example, if I wanted to display items that only belonged to both “Design” AND “Integration”. I very much need this functionality for my website and would like to know how difficult it would be to implement. Thank you for any information!

    - Asad

  • Pingback: 30 Useful jQuery Filter and Sort Plugins « prakashmca007

  • alek

    ive made some changes to avoid jumping objects on menu selection change

    ===============================================================
    $(document).ready(function() {
    $(‘ul#filter a’).click(function() {
    $(this).css(‘outline’,'none’);
    var filterVal = $(this).text().toLowerCase().replace(‘ ‘,’-');
    var itemsLength = $(‘ul#portfolio li:visible’).length;
    $(‘ul#portfolio li:visible’).each(function(i) {
    $(this).fadeOut(‘slow’, function(){if(itemsLength == ++i){show(filterVal);}});
    });
    return false;
    });

    });
    function show(filterVal){
    if(filterVal == ‘all’) {
    $(‘ul#portfolio li’).fadeIn(‘slow’);
    } else {
    $(‘ul#portfolio li’).each(function() {
    if($(this).hasClass(filterVal)) {
    $(this).fadeIn(‘slow’);
    }
    });
    }
    }
    =================================================================
    * ive removed additional styles
    * added time out then the last item will be faded out
    * hide all the items before removin some of them and fade in only those that must be showen

  • http://patrickruegheimer.deviantart.com Patrick

    This is amazing, thank you very much.

    It helped me a lot with getting a filterable portfolio!

  • Pingback: [Kỹ thuật web] 50 Kỹ Thuật và Bài Học về jQuery « LongKenj's Blog

  • http://ht-design.org Hugo Froes

    Hi guys, this is an incredible script, i’ve even used it for my bookmarks on my computer. I’m now trying to apply this on a site, but I’m having 2 problems.

    1. Instead of a menu, I wanted to use a dropdown (form list) to filter the images.

    2. I’m using a nth:child to take away the margin in every third , is there a way to add this function after the filter has run? My code is as follows:

    $(document).ready(function() {
    $(‘ul.filter a’).click(function() {
    $(this).css(‘outline’,'none’);
    $(‘ul.filter .current’).removeClass(‘current’);
    $(this).parent().addClass(‘current’);

    var filterVal = $(this).text().toLowerCase().replace(‘ ‘,’-');

    if(filterVal == ‘todos’) {
    $(‘ul.promos_gerais li.hidden’).fadeIn(‘slow’).removeClass(‘hidden’);
    } else {

    $(‘ul.promos_gerais li’).each(function() {
    if(!$(this).hasClass(filterVal)) {
    $(this).fadeOut(‘normal’).addClass(‘hidden’);
    } else {
    $(this).fadeIn(‘slow’).removeClass(‘hidden’);
    }
    });
    }

    return false;

    });
    });
    jQuery(‘.promos_gerais li:nth-child(3n+3)’).css({ marginRight: ’0px’ });

  • Pingback: 20 Powerful And Useful jQuery Tutorials Of Year 2011 | Easy jQuery

  • Pingback: 20 Powerful And Useful jQuery Tutorials Of Year 2011 | auto

  • Pingback: How to filter a gallery using Colorbox?

  • Jer

    I got this script working it’s great. I’ve very new to developing.

    How do I get on specific category to load when the page loads rather than all.

    I’d simply like it to either display nothing until button click or display on filtered catagory

  • http://operaionmurphy.tumblr.com Soumil

    Hi, I’d really like to add this to my tumblr account. Is there any tutorial available on NetTuts on how to add this or any Jquery plugin/code in tumblr?

  • http://kadash.net faisal abu jabal

    thanks for the post i liked it soo much and it was very useful but i have a question why doesn’t the custom field panel isn’t showing in the portfolio page in the admin panel can u please help me

  • http://jefsilva.com.br Jefferson Silva

    Hello,
    It’s a great idea, but i have a problem.
    I have a slider calling “http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js”, what i have to do to not conflict with the “scripts/jquery.js”?

  • Gautam

    I am a newbie to JQuery and this tutorial has just been fantastic.
    Well explained, easy to use and works like a charm.
    Thanks.

  • http://www.mbanw.com 模板网

    very cool, i like

  • http://tbemburyportfolio.net76.net Teresa

    If possible, please advise.
    The filter portfolio is works on my local browsers. I uploaded to my website and it does not work. Any insight?
    tbemburyportfolio.net76.net

  • http://www.alenteam.com/alenteam/index.php/component/mailto/?tmpl=component&link=aHR0cDovL3d3dy5hbGVudGVhbS5jb20vYWxlbnRlYW0vaW5kZXgucGhwL3VzbHVnZS93ZWIvM2QtZ2FsZXJpamEuaHRtbA%3D%3D web dizajn subotica

    Thank you for any other informative site. Where else may just I am getting that type of information written in such an ideal way? I’ve a undertaking that I am just now running on, and I have been on the look out for such info.

  • http://www.suus.nl SuuS

    Thank You So Much 4 sharing!!!!
    Finally I have the (Jack Moore) colorbox working with a filter.
    So now i can filter my portfolio and with a mouseclick open an Ifram with project information

    Tralalie :)

    • Gregory Gillis

      Can you share what you did to accomplish this? I’m trying to do the same thing using flterable.pack with Jack’s colorbox. I can’t seem to find anyone explaining how to do this.

  • Pingback: 10 New jQuery AJAX Techniques with Source Code | Web Insight Lab

  • http://www.drinkupsandiego.com LocalWally

    I want to thank, thank, thank you for this tut. Check out how I used it on my site! http://www.drinkupsandiego.com/sandiegobreweries.html

  • Pingback: 6 Best jQuery Plugins For Portfolio

  • Pingback: 10 JQuery Tips and Tricks to Make Killer Websites | FlashMint Blog

  • Spartan

    Isn’t there way to animate the item position rather than snapping in and out.

  • Thos

    Hi

    Is there a way to use pagination with that?

    thank you

  • Ozo

    Please how do i expand the number of pictures per line as it displays only 3 images per row

  • Altay

    Hello! First of all thanks to alek for the modified code. His version’s effect look much better but he forgot to include two lines and the script was in a situation where nothign changed on the category buttons when you clicked on them, it was always showing “ALL”. I’ve fixed his code.
    THIS IS THE BEST VERSION SO FAR:

    $(document).ready(function() {

    $(‘ul#filter a’).click(function() {

    $(this).css(‘outline’,'none’);

    $(‘ul#filter .current’).removeClass(‘current’);

    $(this).parent().addClass(‘current’);

    var filterVal = $(this).text().toLowerCase().replace(‘ ‘,’-');

    var itemsLength = $(‘ul#portfolio li:visible’).length;

    $(‘ul#portfolio li:visible’).each(function(i) {

    $(this).fadeOut(‘slow’, function(){if(itemsLength == ++i){show(filterVal);}});

    });

    return false;

    });

    });

    function show(filterVal){

    if(filterVal == ‘all’) {

    $(‘ul#portfolio li’).fadeIn(‘slow’);

    } else {

    $(‘ul#portfolio li’).each(function() {

    if($(this).hasClass(filterVal)) {

    $(this).fadeIn(‘slow’);

    }

    });

    }

    }

    • Molly

      Hi Altay,

      Thank you for this edit! This is exactly how i want it! I only have 1 problem, when i load the page for the first time the filtering works perfect but when i click all from another filter it displays none of the projects and the rest of the filter selections won’t display anything. It only happens once.

      Any ideas?

      Here is my code below:

      $(document).ready(function() {
      $(‘ul#filter a’).click(function() {
      $(this).css(‘outline’, ‘none’);
      $(‘ul#filter .current’).removeClass(‘current’);
      $(this).parent().addClass(‘current’);
      var filterVal = $(this).text().replace(/s+/g, “-”);
      var itemsLength = $(‘ul#portfolio li:visible’).length;
      $(‘ul#portfolio li:visible’).each(function(i) {
      $(this).fadeOut(‘slow’, function() {
      if (itemsLength == ++i) {
      show(filterVal);
      }
      });
      });
      return false;
      });

      function show(filterVal) {
      if (filterVal == ‘all’) {
      $(‘ul#portfolio li’).fadeIn(‘slow’);
      } else {
      $(‘ul#portfolio li’).each(function() {
      if ($(this).hasClass(filterVal)) {
      $(this).fadeIn(‘slow’);
      }
      });
      }
      }
      $(‘ul#portfolio li[class]‘).attr(‘class’, function(i, v) {
      return v.replace(/s+/g, “-”).replace(/^-|-$/g, ”).replace(/,/g, ‘ ‘);
      });
      });

      I have the last section there for replacing my characters in my classes.

      Thanks,

      Molly

      • Molly

        Nevermind, I just had to change “all” to “All” works perfectly! Thank you v much to you and Alek for this improvement!!