Build a Responsive, Filterable Portfolio, with CSS3 Twists

Build a Responsive, Filterable Portfolio, with CSS3 Twists

Tutorial Details
  • Topic: Site build
  • Difficulty: Intermediate
  • Estimated completion time: 1-2 hours

Final Product What You'll Be Creating

The inherent visual appeal of filterable portfolios (like the Tuts+ hub) has made them very popular. Today, we’ll be making one using straight-forward markup, CSS3 and a little bit of jQuery.


Step 1: The File Structure

We’ll be using the following file structure for our project:

File Structure

Pull a project together based on these files (you’ll need to grab HTML5 Shiv) and let’s get started with the HTML markup in index.html.


Step 2: HTML Head

Let’s keep tempo high and rattle off a list of things we need to do to create the <head>:

  • Declare the media type and character set of the page.
  • Set our viewport’s width to be the same as the device’s width and set the initial zoom to 1 (Read more about this here)
  • Give our page a title.
  • Attach a favicon (interested in retina-proof favicons?)
  • Attach our main style sheet (style.css)
  • Attach our style sheet for handling media queries (media-queries.css)
  • Link to the latest version of jQuery.
  • Add an HTML5 Shiv for handling HTML5 compatibility issues with old browsers.

And here’s what we get:

<!doctype html>
<html lang="en">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>John Doe's Portfolio</title>
		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.ico">
		<link rel="stylesheet" href="css/style.css" media="screen">
		<link rel="stylesheet" href="css/media-queries.css" media="screen">
		<script src="http://code.jquery.com/jquery-latest.min.js"></script>
		<script src="js/js.js"></script>
		<!--[if lt IE 9]>
			<script src="js/html5shiv.js"></script>
		<![endif]-->
	</head>

Step 3: HTML Basic Markup

In the body, we first add a ‘container’ to hold all our elements within a set width, centered on the page. Within that we add (get ready for another rapid fire list):

  • A <header> for our heading (‘John Doe’).
  • A basic navigation (<nav>) comprising a <ul> menu, with five items, each with its respective ID.
  • A <section> for the thumbnails with the class ‘work’.
  • A <footer> with all the copyright stuff.
<body>
	<div class="container">
		<header>
			<h1 class="title">
				John Doe
			</h1>
		</header>
		<nav>
			<ul>
				<li id="all">All</li>
				<li id="web">Web</li>
				<li id="print">Print</li>
				<li id="illustration">Illustration</li>
				<li id="logo">Logo</li>
			</ul>
		</nav>
		<section class="work">
		</section>
		<footer>&copy; 2012 John Doe. Valid HTML5.</footer>
</body>

Step 4: HTML Figure and Image

We’ll be using the <figure> tag for our thumbnails and will apply the class of the respective category which it belongs to. Within the figure, we’ll add an <a> tag comprising the image (<img>) for the background of the thumbnail and a description list (<dl>) for the caption.

<figure class="illustration">
	<a href="#">
		<img src="images/1.png" alt="" />
		<dl>
		</dl>
	</a>
</figure>

Step 5: HTML Caption (DL, DT, DD)

As mentioned above, we’ll be using a description list for our caption. Our description terms (<dt>) will be our small headings — Client and Role, for our descriptions (<dd>) — Envato and Illustration, respectively.

<figure class="illustration">
	<a href="#">
		<img src="images/1.png" alt="" />
		<dl>
			<dt>Client</dt>
				<dd>Envato</dd>
			<dt>Role</dt>
				<dd>Illustration</dd>
		</dl>
	</a>
</figure>

Step 6: The Complete HTML

Here’s what our completed HTML markup looks like:

<!doctype html>
<html lang="en">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>John Doe's Portfolio</title>
		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.ico">
		<link rel="stylesheet" href="css/style.css" media="screen">
		<link rel="stylesheet" href="css/media-queries.css" media="screen">
		<script src="http://code.jquery.com/jquery-latest.min.js"></script>
		<script src="js/js.js"></script>
		<!--[if lt IE 9]>
			<script src="js/html5shiv.js"></script>
		<![endif]-->
	</head>
	<body>
		<div class="container">
			<header>
				<h1 class="title">
					John Doe
				</h1>
			</header>
			<nav>
				<ul>
					<li id="all">All</li>
					<li id="web">Web</li>
					<li id="print">Print</li>
					<li id="illustration">Illustration</li>
					<li id="logo">Logo</li>
				</ul>
			</nav>
			<section class="work">
				<figure class="illustration">
					<a href="#">
						<img src="images/1.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Illustration</dd>
						</dl>
					</a>
				</figure>
				<figure class="print">
					<a href="#">
						<img src="images/2.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Print</dd>
						</dl>
					</a>
				</figure>
				<figure class="logo">
					<a href="#">
						<img src="images/3.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Logo</dd>
						</dl>
					</a>
				</figure>
				<figure class="web">
					<a href="#">
						<img src="images/4.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Web</dd>
						</dl>
					</a>
				</figure>
				<figure class="print">
					<a href="#">
						<img src="images/5.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Print</dd>
						</dl>
					</a>
				</figure>
				<figure class="web">
					<a href="#">
						<img src="images/6.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Web</dd>
						</dl>
					</a>
				</figure>
				<figure class="print">
					<a href="#">
						<img src="images/7.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Print</dd>
						</dl>
					</a>
				</figure>
				<figure class="web">
					<a href="#">
						<img src="images/8.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Web</dd>
						</dl>
					</a>
				</figure>
				<figure class="illustration">
					<a href="#">
						<img src="images/9.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Illustration</dd>
						</dl>
					</a>
				</figure>
				<figure class="print">
					<a href="#">
						<img src="images/10.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Print</dd>
						</dl>
					</a>
				</figure>
				<figure class="web">
					<a href="#">
						<img src="images/11.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Web</dd>
						</dl>
					</a>
				</figure>
				<figure class="logo">
					<a href="#">
						<img src="images/12.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Logo</dd>
						</dl>
					</a>
				</figure>
				<figure class="illustration">
					<a href="#">
						<img src="images/13.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Illustration</dd>
						</dl>
					</a>
				</figure>
				<figure class="web">
					<a href="#">
						<img src="images/14.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Web</dd>
						</dl>
					</a>
				</figure>
				<figure class="logo">
					<a href="#">
						<img src="images/15.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Logo</dd>
						</dl>
					</a>
				</figure>
				<figure class="print">
					<a href="#">
						<img src="images/16.png" alt="" />
						<dl>
							<dt>Client</dt>
								<dd>Envato</dd>
							<dt>Role</dt>
								<dd>Print</dd>
						</dl>
					</a>
				</figure>
			</section>
			<footer>&copy; 2012 John Doe. Valid HTML5.</footer>
		</div>
	</body>
</html>

Let’s move on to the styling now.


Step 7: CSS Selection and Scrollbar

We’ll start by dealing with some playful elements; the selection state and the scrollbar (which are entirely optional) plus we’ll haul in some fonts.

@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300);
@import url(http://fonts.googleapis.com/css?family=PT+Sans+Narrow);
::selection {
	background: #333;
	color: #FFF;
}
::-webkit-scrollbar {
	width: 9px;
}
::-webkit-scrollbar-track {
	background:#eee;
	border: thin solid lightgray;
	box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.1) inset;
}
::-webkit-scrollbar-thumb {
	background:#999;
	border: thin solid gray;
}

In the above code, we imported two webfonts from Google — Open Sans and PT Sans Narrow. Then, we simply set a dark gray background and a white text color for user selections.

We then set a width of 9px for our scrollbar (in Webkit browsers) and gave the ‘track’ a light gray background, a thin border and a mild inset box-shadow. Then, we gave a dark gray background to the scrollbar thumb and added a thin border to it.

Note: For more information on webkit-scrollbars, see Chris Coyier’s post.


Step 8: CSS Body

We’ll give our body a light gray noise background and apply the ‘Open Sans’ font we imported earlier. We’ll also add a red top border for an enhanced finesse.


Make some noise…
body {
	font-family: 'Open Sans', sans-serif;
	background: url('../images/bg.gif');
	padding: 0;
	margin: 0;
	border-top: 10px solid #9d2e2c;
}

Step 9: CSS Container

Now, for our container, we’ll set a width of 960px, a 10px top and bottom margin, and center it on the page by setting the right and left margins to ‘auto’. We’ll also force hardware acceleration on (some) mobile devices by using ‘-webkit-transform: translateZ(0);’.

.container {
	width: 960px;
	margin: 10px auto;
	-webkit-transform: translateZ(0);
}

Step 10: CSS Header

We’ll simply increase our heading font-size, center the text and give it a font-weight of 300 for a sleeker look.

header {
	text-align: center;
	font-weight: 300;
	font-size: 700%;
}

Step 11: CSS Footer

We’ll center align the text horizontally, add the top and bottom margins of 50px each, set the text color to gray, and position it below the ‘work’ section by using ‘clear: both’.

footer {
	text-align: center;
	height: 100px;
	line-height: 100px;
	color: gray;
	clear: both;
}

Let’s work on the navigation now.


Step 12: CSS Navigation

We’ll start by removing all the default styling from our <ul>. Then, we’ll add a 50px top and bottom margin and align the text to the center.

nav ul {
	list-style: none;
	padding: 0;
	margin: 50px 0;
	text-align: center;
}

Now, by using ‘display: inline’, we’ll get all our <li>s to display in one line. We’ll set the cursor to ‘pointer’ and add a 10px right margin to each <li>. The default text color will be a light shade of gray which will turn black on hover.

We’ll also add a small transition time to animate the color changes.

nav ul li {
	display: inline;
	cursor: pointer;
	margin-right: 10px;
	color: #666;
	transition: 0.3s;
	-webkit-transition: 0.3s;
	-moz-transition: 0.3s;
	-o-transition: 0.3s;
	-ms-transition: 0.3s;
}
nav ul li:hover {
	color: #000;
}

Since the last <li> is, umm, the last-child, it doesn’t need any right margin. So, we’ll remove it.

nav ul li:last-child {
	margin-right: 0;
}

Now, let’s add the slashes after the <li>s. We’ll accomplish this by using the ‘:after’ pseudo-selector. By setting its ‘content’ to ‘/’, color to light gray, and an appropriate left margin, we can produce a simple-yet-effective system of link separation. We’ll also ensure that the slashes don’t change color on hover by forcing their default color on li:hover too.

nav ul li:after {
	margin-left: 10px;
	content: '/';
	color: #bbb;
}
nav ul li:hover:after {
	color: #bbb;
}

Again, we’ll have to remove the slash from the last <li>.

nav ul li:last-child:after {
	content: '';
}

That’s all for the navigation, let’s get to the thumbnails now.


Step 13: CSS Thumbnails

First, we’ll add a 20px top and bottom margin to the ‘.work’ section.

.work {
	margin: 20px 0;
}

Next, we’ll style each ‘.work figure’ (i.e. all our thumbnails). We’ll use ‘float: left’ to get them lined up. We’ll then add a 20px margin, set a height and width of 200px, and add a mild sepia effect by using ‘-webkit-filter: sepia(0.4)’. Then, we’ll set the position to relative so that we can absolute position other elements (the caption in this case) within the figure. We’ll then add a mild box-shadow which will also work as our border. We’ll also add a small transition time for our boxes to grow and scale down.

.work figure {
	float: left;
	margin: 20px;
	width: 200px;
	height: 200px;
	background: #9d2e2c;
	line-height: 200px;
	-webkit-filter: sepia(0.4);
	position: relative;
	padding: 0 !important;
	box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.5);
	transition: 0.6s;
	-webkit-transition: 0.6s;
	-moz-transition: 0.6s;
	-o-transition: 0.6s;
	-ms-transition: 0.6s;
}

We’ll ensure that our image always fits the thumbnail by setting its height and width to 100%.

.work figure a img {
	height: 100%;
	width: 100%;
}

That’s all for our basic thumbnails. Let’s work on their captions now.


Step 14: CSS Captions

Description List

As we don’t want our caption to be visible initially, we’ll set its opacity to 0. Then, we’ll absolute position it (within the figure) and make it fill the figure fully by setting all the 4 properties — top, right, bottom, and left — to 0.

We’ll then set its line-height to 2.5 and give it a dark, translucent background. Since we’re using a dark background, we’ll set its text color to white. We’ll also add a small transition time to animate its opacity on figure:hover.

.work figure dl {
	opacity: 0;
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	top: 0;
	padding: 20px;
	margin: 0;
	line-height: 2.5;
	background: rgba(0, 0, 0, 0.8);
	color: white;
	transition: 0.6s;
	-webkit-transition: 0.6s;
	-moz-transition: 0.6s;
	-o-transition: 0.6s;
	-ms-transition: 0.6s;
}

As we want it to appear on hovering on the thumbnail, we’ll set its opacity to 1 on figure:hover.

.work figure:hover dl {
	opacity: 1;
}

Description Terms

First, we’ll set their font-family to ‘PT Sans Narrow’. To make them appear a bit smaller than their descriptions, we’ll set their font-size to 80%. Then, we’ll convert our description terms (Client and Role) into uppercase using ‘text-transform:uppercase’. We’ll also set a negative bottom margin to avoid excessive spacing between the terms and their descriptions.

.work figure dl dt {
	text-transform: uppercase;
	font-family: 'PT Sans Narrow';
	font-size: 12px;
	margin-bottom: -16px;
}

Definition Descriptions

We don’t need to do much here – we’ll just set the margin to 0. (By default, <dd>s have a slight left margin.)

.work figure dl dd {
	margin-left: 0;
}

Step 15: CSScurrent’/‘not-current’ Thumbnails

.current

When the thumbnails of a certain category are given the .current class (through JS), we want them to grow and get their normal tone back (i. e. remove the sepia). We’ll scale them up by using ‘transform: scale(1.05)’, thus scaling it to 1.05 times the original size on both the x and y axes and remove the sepia we had added earlier by using ‘-webkit-filter: sepia(0)’.

.current {
	-webkit-filter: sepia(0) !important;
	-webkit-transform: scale(1.05);
	-moz-transform: scale(1.05);
	-o-transform: scale(1.05);
	-ms-transform: scale(1.05);
	transform: scale(1.05);
	-webkit-backface-visibility: hidden;
	-moz-backface-visibility: hidden;
	-o-backface-visibility: hidden;
	-ms-backface-visibility: hidden;
	backface-visibility: hidden;
}

.not-current

Here, we’ll do the exact opposite of what we did to the .current thumbnails — we’ll scale them down to 75% using ‘transform: scale(0.75)’ and make them black and white using ‘-webkit-filter: grayscale(1)’.

.not-current {
	-webkit-transform: scale(0.75);
	-moz-transform: scale(0.75);
	-o-transform: scale(0.75);
	-ms-transform: scale(0.75);
	transform: scale(0.75);
	-webkit-filter: grayscale(1) !important;
}

.current-li

We’ll simply set the text color of the ‘.current-li’s to black.

.current-li {
	color: #000;
}

Step 16: The Complete CSS

Here’s what our completed CSS looks like:

/* Style */
@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300);
@import url(http://fonts.googleapis.com/css?family=PT+Sans+Narrow);
::selection {
	background: #333;
	color: #FFF;
}
::-webkit-scrollbar {
	width: 9px;
}
::-webkit-scrollbar-track {
	background:#eee;
	border: thin solid lightgray;
	box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.1) inset;
}
::-webkit-scrollbar-thumb {
	background:#999;
	border: thin solid gray;
}
/* --------------------------
	Body
----------------------------- */
body {
	font-family: 'Open Sans', sans-serif;
	background: url('../images/bg.gif');
	padding: 0;
	margin: 0;
	border-top: 10px solid #9d2e2c;
}
/* --------------------------
	Container
----------------------------- */
.container {
	width: 960px;
	margin: 10px auto;
	-webkit-transform: translateZ(0);
}
a {
	text-decoration: none;
}
/* --------------------------
	Header
----------------------------- */
header {
	text-align: center;
	font-weight: 300;
	font-size: 700%;
}
/* --------------------------
	Footer
----------------------------- */
footer {
	text-align: center;
	height: 100px;
	line-height: 100px;
	color: gray;
	clear: both;
}
/* --------------------------
	Navigation
----------------------------- */
nav ul {
	list-style: none;
	padding: 0;
	margin: 50px 0;
	text-align: center;
}
nav ul li {
	display: inline;
	cursor: pointer;
	margin-right: 10px;
	color: #666;
	transition: 0.3s;
	-webkit-transition: 0.3s;
	-moz-transition: 0.3s;
	-o-transition: 0.3s;
	-ms-transition: 0.3s;
}
nav ul li:last-child {
	margin-right: 0;
}
nav ul li:hover {
	color: #000;
}
nav ul li:hover:after {
	color: #bbb;
}
nav ul li:after {
	margin-left: 10px;
	content: '/';
	color: #bbb;
}
nav ul li:last-child:after {
	content: '';
}
/* --------------------------
	Main Image Box
----------------------------- */
.work {
	margin: 20px 0;
}
.work figure {
	float: left;
	margin: 20px;
	width: 200px;
	height: 200px;
	background: #9d2e2c;
	line-height: 200px;
	-webkit-filter: sepia(0.4);
	position: relative;
	padding: 0 !important;
	box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.5);
	transition: 0.6s;
	-webkit-transition: 0.6s;
	-moz-transition: 0.6s;
	-o-transition: 0.6s;
	-ms-transition: 0.6s;
}
.work figure a img {
	height: 100%;
	width: 100%;
}
.work figure dl {
	opacity: 0;
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	top: 0;
	padding: 20px;
	margin: 0;
	line-height: 2.5;
	background: rgba(0, 0, 0, 0.8);
	color: white;
	transition: 0.6s;
	-webkit-transition: 0.6s;
	-moz-transition: 0.6s;
	-o-transition: 0.6s;
	-ms-transition: 0.6s;
}
.work figure:hover dl {
	opacity: 1;
}
.work figure dl dt {
	text-transform: uppercase;
	font-family: 'PT Sans Narrow';
	font-size: 12px;
	margin-bottom: -16px;
}
.work figure dl dd {
	margin-left: 0;
}
.current-li {
	color: #000;
}
.current {
	-webkit-filter: sepia(0) !important;
	-webkit-transform: scale(1.05);
	-moz-transform: scale(1.05);
	-o-transform: scale(1.05);
	-ms-transform: scale(1.05);
	transform: scale(1.05);
	-webkit-backface-visibility: hidden;
	-moz-backface-visibility: hidden;
	-o-backface-visibility: hidden;
	-ms-backface-visibility: hidden;
	backface-visibility: hidden;
}
.not-current {
	-webkit-transform: scale(0.75);
	-moz-transform: scale(0.75);
	-o-transform: scale(0.75);
	-ms-transform: scale(0.75);
	transform: scale(0.75);
	-webkit-filter: grayscale(1) !important;
}
.not-current:hover dl {
	opacity: 0 !important;
}

Let’s start working on the JS now.


Step 17: JS The Algorithm

Here is what we’re going to do through our Javascript (in chronological order):

  1. Detect nav > li press.
  2. Scale down all the thumbnails by giving them the .not-current class.
  3. Add the .current-li class to the selected category’s corresponding <li>.
  4. Remove the .not-current class only from the thumbnails of the selected category.
  5. Add the .current class to all the thumbnails of the selected category.

#2 here will be done using the scaleDown() function and #3, #4, and #5 will be done using the show(category) function.


Step 18: JS The scaleDown() Function

Using the removeClass and addClass methods, we’ll remove the .current class from the elements which have it and add the .not-current class to all of them. We’ll also remove the .current-li class from any <li which currently has it.

function scaleDown() {
	$('.work > figure').removeClass('current').addClass('not-current');
	$('nav > ul > li').removeClass('current-li');
}

Step 19: JS The show(category) Function

This function will be implemented each time an <li> is clicked. First, we’ll call the scaleDown() function to scale down all the thumbnails. Then, we’ll add the .current-li class to the <li which corresponds to the selected category. We’ll then change the class of the category’s thumbnails from .not-current to .current (we had applied the .not-current class to all the thumbnails in the scaleDown() function). Finally, if the selected category is ‘all’, we’ll remove the dynamically added classes (i.e. .current and .not-current) from all the thumbnails.

Note: Since the name of the id (of the <li) and class (of the figures) of each category is the same, we’ll simply refer to the <li as ‘# + category’ and the figures as ‘. + category’.

function show(category) {
	scaleDown();
	$('#' + category).addClass('current-li');
	$('.' + category).removeClass('not-current');
	$('.' + category).addClass('current');
	if (category == "all") {
		$('nav > ul > li').removeClass('current-li');
		$('#all').addClass('current-li');
		$('.work > figure').removeClass('current, not-current');
	}
}

Step 20: JS Detecting Clicks and Implementing the show(category) Function

Finally, through the document.ready method, we’ll add the .current-li class to li#all and detect nav > li clicks. We’ll then get the id of the clicked <li and implement the show(category) function where the ‘category’ will be ‘this.id’ i.e. id of the clicked <li>. So, for example, if the <li> with the id #print is clicked, show(‘print’) will be implemented.

$(document).ready(function(){
	$('#all').addClass('current-li');
	$("nav > ul > li").click(function(){
		show(this.id);
	});
});

This completes our Javascript.


Step 21: The Complete JS

Our completed JS looks like this:

function scaleDown() {
	$('.work > figure').removeClass('current').addClass('not-current');
	$('nav > ul > li').removeClass('current-li');
}
function show(category) {
	scaleDown();
	$('#' + category).addClass('current-li');
	$('.' + category).removeClass('not-current');
	$('.' + category).addClass('current');
	if (category == "all") {
		$('nav > ul > li').removeClass('current-li');
		$('#all').addClass('current-li');
		$('.work > figure').removeClass('current, not-current');
	}
}
$(document).ready(function(){
	$('#all').addClass('current-li');
	$("nav > ul > li").click(function(){
		show(this.id);
	});
});

Now that our site is fully functional, let’s make it responsive.


Step 22: CSS Making it Responsive

Let’s open ‘media-queries.css’ and get going. How you choose to implement your media queries is entirely up to you. You may prefer to have media queries within your main stylesheet, you may even prefer to have them modular and inline with each and every style declaration - it's up to you!

Style changes required for each breakpoint are described here.

965px or less

  • Decrease the width of the container to 840px
  • Decrease the height and width of the thumbnails to 170px each so as to accommodate 4 thumbnails in each row [(170px + 40px) x 4 = 210px x 4 = 840px]
/* Small viewports — Old monitors, netbooks, tablets (landscape), etc. */
@media only screen and (max-width: 965px) {
	.container {
		width: 840px;
	}
	.work figure {
		width: 170px;
		height: 170px;
	}
}

860px or less

  • Decrease the width of the container to 720px
  • Increase the height and width of the thumbnails back to 200px each to accommodate 3 in each row [(200px + 40px) x 3 = 240px x 3 = 720px]
/* Smaller viewports — more tablets, old monitors */
@media only screen and (max-width: 860px) {
	.container {
		width: 720px;
	}
	.work figure {
		width: 200px;
		height: 200px;
	}
}

740px or less

  • Decrease the width of the container to 600px
  • Decrease the opacity of the .not-current to 50% (since we're mainly working for mobile devices now)
  • Decrease the height and width of the thumbnails to 160px each to accommodate 3 in each row [(160px + 40px) x 3 = 200px x 3 = 600px]
/* Even smaller viewports — more tablets, etc. */
@media only screen and (max-width: 740px) {
	.container {
		width: 600px;
	}
	.work figure {
		width: 160px;
		height: 160px;
	}
	.not-current {
		opacity: 0.5;
	}
}

610px or less

  • Decrease the width of the container to 460px
  • Set the top and bottom margin of the thumbnails to 20px and left and right margin to 60px
  • Increase the height and width of the thumbnails back to 200px each to accommodate 1 in each row [(200px + 120px) x 1 = 320px x 1 = 320px]
/* Mobile phones (Landscape) and Tablets (Portrait) */
@media only screen and (max-width: 610px) {
	.container {
		width: 460px;
	}
	header {
		font-size: 400%;
	}
	nav ul li {
	}
	.work figure {
		margin-left: 40px;
		margin-bottom: 60px;
	}
	.work figure dl {
		height: 40px;
		top: 200px;
		bottom: 0px;
	}
}

480px or less

  • Decrease the width of the container to 320px
  • Set the top and bottom margin of the thumbnails to 20px and left and right margin to 60px
  • Increase the height and width of the thumbnails back to 200px each to accommodate 1 in each row [(200px + 120px) x 1 = 320px x 1 = 320px]
/* Mobile phones (Portrait) */
@media only screen and (max-width: 480px) {
	.container {
		width: 320px;
	}
	.work figure {
		width: 200px;
		height: 200px;
		margin: 20px 60px;
	}
}


Browser Compatibility

The basic scaling functionality (CSS transforms) works perfectly in most major browsers, which include:

  • IE 9+ (use http://www.useragentman.com/IETransformsTranslator/ for lower versions)
  • Firefox 14+
  • Chrome 21+
  • Safari 5.1+

The filter effects (sepia and grayscale) work only in Webkit browsers.


jsFiddles

I’ve made three jsFiddles for you to try out and experiment with:

  1. Caption Split Effect (Vertical)
  2. Caption Split Effect (Horizontal)
  3. Diagonal Masked Thumbnail Images

Conclusion

That’s it! We have successfully created a working, filterable, and responsive portfolio. I hope you found this tutorial useful. Thanks for reading!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Ivan

    Great tut! Only thing, is that unfortunately CSS3 transforms and web types rendering on Chrome/Win7 64x is almost unacceptable. Apart from that (which is not your fault), kudos for the accurate article :)

  • Dave Lane

    Nice work. Regarding your js, I would recommend caching your selectors in variables, as it saves you dipping into the DOM multiple times.

    • http://www.snaptin.com Ian Yates

      Good point Dave – JW wrote a nice post on that very topic a while back. I should have picked up on it..

      • Dave Lane

        Np. Thanks for the insight on styling webkit scrollbars btw. I was not aware that it was an option. Great stuff.

    • http://www.creator.p-ano.com Akshat Srivastava
      Author

      Good point. I’ll keep that in mind for future projects. Thanks. :)

  • esom

    Very good post

  • Mark

    Thanks for this excellent tutorial.
    Love the CSS you came up with. Simple, clean and effective!

  • Patrick Whitty-Clarke

    Nice tutorial.

    I think you have a slight mistake in your source files though. In your online version the following CSS is set to:

    .work figure dl {
    /* height: 40px;
    top: 200px; */
    bottom: 0px;
    }

    whereas in the offline version (download source files) it’s still active, as in un-commented and it means that under 480px (at least in Firefox for me) the hover effect doesn’t display correctly.

    Also I tested the page on my Android (2.x) browser and Opera Mini and in both of those mobile browsers the CSS transforms don’t work. In Android they get applied up until the first filterable item, then all further images remain the same size. In Opera mini, none of them change size but they all appear slightly opaque (maybe this css property backface-visibility:).

    Great tutorial though, I just think because of the compatibility issues with those phone browsers I’d be tempted to use isotope instead of the css transforms making the thumbnail larger/smaller.

    • http://www.snaptin.com Ian Yates

      Thanks for your comment Patrick – I commented those rules out after I noticed the glitch in the demo. I forgot to update the source download (sorry about that).. Glad you enjoyed the tutorial :)

  • Pingback: Build a Responsive, Filterable Portfolio, with CSS3 Twists … | Beachdrop

  • http://www.derby-webdesign.co.uk/ Kevin

    Excellent article. I’ll hopefully use that in a new project :) Thanks

    • http://www.creator.p-ano.com Akshat Srivastava
      Author

      Glad you liked it!

  • Desiree Hart

    Akshat,

    I love the clean layout and idea of a searchable portfolio. This is a great tutorial! Very timely too, as I’m in the process of setting up my portfolio.

    Desiree

  • Siyanda

    Hi thanks for sharing this great tut,I’ve been playing with it and the filter effect seems to display strangely on screens smaller than 400 px, I’ve been fiddling with the queries bt I still can’t get it to display properly.

  • FilterJs

    Thanks for the excellent tutorial. I was wondering if it will be difficult to modify how it filters out the portfolio. Maybe make the in-active ones disappear, and then re-arrange others to better layout. If anyone knows please guide me to that direction.

    Thanks alot to the author, and everyone.

    • Wilhelm Wanecek

      Hi!
      I’ve tried to accomplish this aswell, and while you can add a “Display:none” to the .not-current class, I don’t really like the result, since there is no animation for the display property in css.

      Setting “transform: scale(0)” doesn’t do it either since the .not-current still take up place.
      The best thing would be to do transform: scale(0), and when the transformation is done, trigger display:none;

      I do not know how to do this in jQuery without writing too much code, any tips? :)

    • http://www.creator.p-ano.com Akshat Srivastava
      Author

      Hi! Glad you found the tutorial useful!

      After giving some time to it, I figured out two ways of disappearing the inactive ones with transitions:

      1. Adding ‘zoom: 0.001;’ to the .not-current class. The only problem is that the ‘zoom’ property doesn’t validate.

      2. Setting the height, width, and margin of the class to 0 (and important). This doesn’t look that good, but it is a fully valid method.

      • http://www.creator.p-ano.com Akshat Srivastava
        Author

        One thing – ‘zoom’ works only on Chrome, Safari, and IE. IE too messes up the whole thing, so I think you should go with option 2.

    • Chris Spittles

      http://www.chris-spittles.co.uk/codeForForums/csstiles/index.html

      Above is an example with disappearing tiles. Wasn’t easy as the tiles have to be inline-blocks and they become white-space dependent. I’ve reworked the markup so its responsive along with the css and JS and i’ve used keyframe animations in addition to transitions.

      I’ve only used moz and webkit vendor prefixes so it won’t work currently in Opera, but you just need to add the opera prefixes in. Oh and it won’t work in IE as its rubbish. EDIT (it won’t work in Opera either as it doesn’t support keyframes) :(

      I’ve reworked the jQuery to be an object and it now outputs a count, i’ve also added a jump to function which allows a user to press an arrow at the bottom right and it will smooth scroll back to the top.

      I haven’t optimised the tile sizes in lower resolutions as I’ve run out of time. In lower resolutions the navigation gets messed up but it won’t take long to conditionally fix these things.

      I too am currently in the process of updating my site and see the merit in such a design but it wasn’t as flexible as it could perhaps be hence the tweaked version.

      Hope this helps.

  • http://www.thepixelgrid.com Tom

    This is a great tutorial with some fantastic concepts. However, I think a number of the CSS rules for margins, padding, and positioning could have been avoided with a simple HTML/CSS reset. Without it, there is room for error in the display in some browsers and, more importantly, code bloat is a bad thing!

    As someone else pointed out, there are a few omissions in the demo code, too. Still; it’s not a bad read for learning from and the design is pretty tasty.

  • Chris Spittles

    .removeClass(‘current, not-current’);

    I believe (and I’m happy to be corrected) that this is wrong?

    I thought that to remove multiple classes you just seperated them with a space:

    .removeClass(‘current not-current’);

    • Akshat Srivastava
      Author

      Yes, that is wrong. Thanks for pointing out. :)

      • test

        test

  • Patrick

    Weren’t there more comments on here? I was sure Akshat replied to my post but now I can’t see it any more – something to do with the disqus update?

    • http://www.snaptin.com Ian Yates

      Yes there were :) We’re migrating the system over to Disqus (for tons of reasons) and the old comments haven’t all made it over yet.. Sorry for the short-term inconvenience!

  • Patrick

    Out of interest, what is your opinion on the hover state when using an iPad/touch screen. There is effectively no hover, so you don’t get the hover information and tapping will bring you to the page (or bring up the photo via lightbox, whatever you decide the link to do).

    I’m currently undecided between two options, either use display:none/block instead of opacity:0/1 which means you tap the thumbnail once to reveal the hover info, then tap again to initiate the proper click, or just use opacity:0/1 and tap once but sacrifice the hover info. Two taps might get annoying but then it displays the information properly. I guess I’ll just have to ask the client, but I’m interested to see what people here think…

    • Chris Spittles

      I would use a media query or JS to serve up a slightly different look to the iPad. I would forgo the need for the animated hover and display the text permanently.

      First check to see if the device is touch enabled (as you don’t want to single out just the ipad):

      var is_touch_device = ‘ontouchstart’ in document.documentElement;
      if(is_touch_device) jQuery(“html”).addClass(“is-touch-enabled”);

      Use that class to override the defaults and cancel the hover state and positioning.

      • Patrick

        Thanks! That’s a nice technique for recognising touch devices, I’ll give it a go.

  • Pingback: Latino » Blog Archive » Scrolling Web App | Expression Web

  • Pingback: 20 Web Design Tutorial Roundup. | SplendidHub

  • Pingback: 60+ Responsive Web Design Tutorial Roundup – Spoil Your Mobile visitors! | Good Favorites

  • http://twitter.com/mantismamita Kirsten Cassidy

    Great tut! Just thought I’d mention that dl is a definition list and not a description list. I ended up using figcaption then an h2 and a p’s

  • http://www.clippingpathindia.com/image-masking.html Image Masking

    My heart going to learn them so much . Because I am the creative designer and I need it so much . Thank you so much .