Curious about our portfolio slider? Learn about how we pulled it off with the jQuery framework, which powers all the animations on the site.
Using jQuery to Produce Rich User Interfaces
Since we launched our new company site back in November, we’ve gotten a lot of positive feedback from visitors and well-wishers about the novel approach to our portfolio. And while we expected to hear from other designers and developers, I was somewhat surprised to learn the impact it has on potential clients: often, via email or in person, I hear that seeing the stages of our creative process presented as a zippy polaroid left a lasting impression in their mind. And when a prospective client is evaluating a dozen (or more) vendors to tackle their next big project, this becomes extremely valuable — it helps us stand out! So it’s easy to make a case that rich animations are more than just eye candy; together with our deliberately-whimsical copywriting they become an important part of our brand identity.
All the animations used throughout the company site I’ve written with jQuery, the greatest JavaScript library on the planet (yeah, I went there). jQuery makes client-side scripting not only super-easy but a blast to write. I’ve written about jQuery before and how much I like it (I even recently gave a talk to that effect) but have long wanted to break down how it powers our little portfolio slider. (I won’t go into too much detail about jQuery itself — I leave finding introductory tutorials elsewhere on the intertubes as an exercise to the reader.)
First, A Little Background
We decided early to let visitors in on our creative process. But the question remained how exactly to execute it: we only have so much screen real estate and it’s important to maximize the size of the screenshot (too often these days I’m squinting at portfolio entries — get with it, people!) without cluttering the design with too many extraneous visual elements. Where I ultimately got the idea from was the Fx.Scroll demo for the Mootools JavaScript effects library: at first my plan was to use one giant image composed of all the individual screenshots and, instead of moving up and down, just move back and forth within the “polaroid” viewport image that Jon created. I started working on some demos of it, teaching myself Mootools along the way.
Thing is, I kept running into problems. I’m still not sure why, but I kept getting significantly inconsistent results across browsers and in some cases no results at all. And not just the usual suspects, but also between Safari and Firefox, which is much more difficult to pinpoint (if you think CSS rendering differences between browsers is sticky, wait till you try JavaScript). Moreover, the Mootools syntax I’m not crazy about: I find it kind of awkward and less intuitive than jQuery. Now, much of this I’m sure can be chalked up to my inexperience with it and doubtless Mootools defenders are going to write me an earful, but it was actually frustration that drove me to jQuery as an alternative and I got the results I was looking for with less effort in less time. Make of it what you will, but that was my subjective experience.
I began re-writing the Mootools scripts using jQuery and had much better luck. But even though other parts of the site became easier, I no longer had a plugin already written that did what I was looking for. Fortunately, though — and this is kind of the whole point — I was able to write my own, total n00b that I was (er, still am).
Enough, Dude, Let’s Dive In Already
First, let’s take a look at the portfolio markup. The whole thing is achieved with a single container division
called “portfolio” that contains four child elements: two unordered lists, an image and an anchor (i.e., link). In other words:
-
<div id="portfolio">
-
<ul id="gallery">
-
<li><img src="/{client}/before.jpg" /></li>
-
<li><img src="/{client}/sketch.jpg" /></li>
-
<li><img src="/{client}/wireframe.jpg" /></li>
-
<li><img src="/{client}/after.jpg" /></li>
-
</ul>
-
<img id="frame" width="580" height="415" src="frame.png" />
-
<ul id="sequence">
-
<li id="before"><a href="/{client}/before.jpg"><a></li>
-
<li id="sketch"><a href="/{client}/sketch.jpg"><a></li>
-
<li id="wireframe"><a href="/{client}/wireframe.jpg"><a></li>
-
<li id="after"><a href="/{client}/after.jpg"><a></li>
-
</ul>
-
<a id="launch" rel="external" href="{url}">
-
<img src="launch.png" alt="Launch this site" />
-
</a>
-
</div>
The first list (“gallery”) contains four items: these are the actual screenshots that go zooming by. The image that follows on line 8, called “frame”, looks like this: it’s a 580px wide PNG illustration drawn by Jon and it contains a gigantic, transparent center portion. It sits in front of the previous list, following it in the flow of the markup and using a z-index
to ensure it stays that way. The screenshot images of the list behind it show through the center window but are obscured when outside it.
Next comes the second list (“sequence”) beginning on line 9, which accounts for the four navigation items at the bottom that you rollover with your mouse to fire the script. Finally, there’s a transparent image (launch.png
) inside an anchor called “launch” that sits on top of the image “frame” and takes you to the website in question. (Interestingly, the button originally said “Launch the site”, but after some user testing we determined that the language was too obscure for the average visitor and I had Jon re-export it saying “View the site” instead. The image name and markup, though, remained unchanged, since it’s easier just to upload an image with the same name. Yes, I’m a little lazy.)
At the heart of it, all you need to focus on is the initial unordered list with id="gallery"
: that’s where the action occurs. I mentioned before that the image “frame” sits in front of the list. What happens is, when you hover over one of the nav items, the entire unordered “gallery” list shifts to the side behind the polaroid border image, leaving the screenshot of interest (sketch, wireframe, etc.) showing through the transparent center frame. Here’s a simplified visual representation:
And here’s what it looks like in the context of the actual page. (Note that the <ul>
extends well past the right column.)
I Thought He’d Never Get to the jQuery
Easy, tiger. So, what makes it move? It all comes down to one simple, flexible function jQuery makes available called animate()
.animate()
lets you take any element and change things about it over time: that’s all an animation is, really, just a regularly occurring change over time.
Let’s say, for example, that you had a box and that box is located 50px from the left side of your browser window because you assigned it left: 50px
. When you grab that box with animate()
, you can pass a value to animate()
that you want that left
numberto arrive at over time. So if you told it to animate({left: "100px"});
, it would increase that value of the box to 100px over 4 seconds (that’s the default), and you’d see it slide across your screen. But you can give it any duration you want: care to speed it up? Try animate({left: "100px"}, "fast")
. Instead of “slow”, “normal”, and “fast”, you can specify any number you want, in milliseconds. We could write animate({left: "100px"}, 2000);
if we wanted that left
value to change from 50px to 100px in 2 seconds.
That’s all I’m doing with the portfolio slider: when you hover over one of the nav items, it triggers jQuery to reassign the left
value of the<ul id="gallery">
to some new number, over time, and this is how it moves.
Got all that? Let’s have a look at the jQuery used to trigger the behavior (wrapped lines marked with ↩):
-
$(window).bind("load", function() {
-
$("div#portfolio").prepend('<img src="loading.gif" ↩
-
alt="loading..." />');
-
$("ul#gallery").hide().portfolio();
-
});
I call this in the <head>
of every portfolio page. The first line assigns or “binds” the behavior that follows to the completed loading of the browser window, which is functionally equivalent to writing <body onload="function();">
with classic JavaScript. In line 2, by grabbing the division with an id
of “portfolio” using the $()
function, we “wrap” a jQuery object around it, and this is what lets us apply cool things like animate()
to it and is what I mean when I talk about “grabbing” DOM elements. In this case, we want to prepend an image using markup that we’ve created on the fly intodiv#portfolio
.
Why do this? Because if a portfolio screenshot image hasn’t finished loading but the rest of the page has been displayed in the browser, this image will stand in its place until it has. It’s not actually calculating anything, it’s just an endless animated GIF that gives the illusion of a progress bar. (You can also make your own, it’s easy!) Beginning with line 3, first we grab the unordered list of interest (“gallery”), hide it with jQuery (this is a shortcut to changing its CSS to "display: none"
) and then fire the portfolio script — in that order. jQuery lets you stack or “chain” multiple commands together in a row.
(As an aside, if you’re wondering why I chose the less common$(window).bind("load")
syntax instead of the more common$(document).ready()
to fire the script on page load, it’s for an important reason: in the latter example, jQuery waits until the DOM is loaded before executing the script, which includes all text butexcludes images. In this example it’s crucial to wait until all the screenshot images have been downloaded to the browser because we have to manipulate them later in the script. The former example, the one I used here, does that: it waits not only until the DOM has finished loading but also until all the images have loaded, and that’s the difference. Other than that, they’re functionally equivalent; but if you’re having trouble with your script as I was, this may be the reason why and took me ages to figure out, since it’s not well-documented.)
That’s all well and good, but what does the .portfolio()
bit do? And where does that function come from? To find out, we turn to an external script. Let’s look at what we’re triggering when we call it:
(Note: wrapped lines marked with ↩):
- jQuery.fn.portfolio = function() {
-
var $polaroid = $(this);
-
$polaroid.parent().find("img.loading").remove();
-
$polaroid.show();
-
var imgWidth = $polaroid.children("li").width();
-
var imgNum = $polaroid.children("li").length;
-
var galleryWidth = imgWidth * imgNum;
-
$polaroid.css("width", galleryWidth);
-
$polaroid.children('li').css("width", imgWidth);
-
$("ul#sequence a").each(function(i) {
-
$(this).bind("mouseover", function(){
-
var margin = - (imgWidth * i);
-
$(this).addClass("current").parent().parent() ↩
-
.find("a:not($(this))").removeClass("current");
-
$polaroid.animate({left: margin}, 500, "easeInOutExpo");
-
console.log("i = "+i+" and margin = "+margin+"px");
-
});
-
$(this).click(function() {return false});
-
});
- };
(You can also view it for real here, with inline comments.) Got all that? If not, how about we …
Tackle That in Stages
Lines 1–4
- jQuery.fn.portfolio = function() {
-
var $polaroid = $(this);
-
$polaroid.parent().find("img.loading").remove();
-
$polaroid.show();
The first line you can safely ignore for now: you need that whenever you’re writing a jQuery plug-in, which is how I approached this example, but explaining it is beyond the scope of this piece. Suffice it to say, writing it like this lets me call that .portfolio()
function on the gallery list that we say earlier, which executes all the code that follows, even though I made that up and it isn’t a core jQuery function.
In line 2, we create a new variable called $polaroid
and we assign it the value of whatever element we passed to the anonymous function declared in line 2 — in this case, the same <ul>
with id="gallery"
we saw earlier. This lets us use $portfolio
as a reference or alias to that list… I usually like to use dollar signs in front of jQuery instance variables because it helps me tell them apart from pure JavaScript variables (i.e., things we haven’t “grabbed” with jQuery). Line 3 then removes that “loading” GIF we saw earler. We do this first by using$polaroid.parent()
, which gives us a reference to <div id="portfolio">
, the parent of <ul id="gallery">
. We then look for any image(s) with a class of “loading” anywhere within the division and scoop it out of the page. Finally, we “show” the unordered list, which we’d hidden previously.
Lines 5–9
-
var imgWidth = $polaroid.children("li").width();
-
var imgNum = $polaroid.children("li").length;
-
var galleryWidth = imgWidth * imgNum;
-
$polaroid.css("width", galleryWidth);
-
$polaroid.children('li').css("width", imgWidth);
Next, we need to find out how wide each of the screenshot images is. So line 5 sets a variable “imgWidth” equal to the width of the list items within the “gallery” list. (In this example, they’re always going to be 580px and that’s not going to change, so I could have simply used that. But I didn’t know that at the time, since we hadn’t finalized the design, and besides it’s good coding practice in general to avoid hard-coding a value in case you want to use it for something else later.) Line 6 calls a property of the jQuery object $polaroid
(“length”) that returns the number of all the items in it (like a JavaScript array)… four, in this case. (Usually .length
is faster than the equivalent jQuery .size()
, so use it unless you have a compelling reason otherwise. UPDATE: It appears that .size()
has been deprecated, so stick with .length
) Line 7 sets a variable “galleryWidth” to the product of these numbers (2320px) that we then use to set the width of the <ul>
“gallery” as well as the individual list items contained inside.
Lines 10–12
-
$("ul#sequence a").each(function(i) {
-
$(this).bind("mouseover", function(){
-
var margin = - (imgWidth * i);
In line 10 we open a code block that iterates over each of the links within the <ul>
“sequence”. In it, we first assign a mouseover
event handler to each one and, upon mouseover, set a variable equal tominus 580px multiplied by our loop counter (0
through 3
), which gives us 0
, –580
, –1160
and –1740
, respectively. So depending on which link you hover over, you assign the “margin” variable to one of those four numbers. This is one of the crucial bits, since that number gets passed to another function later, whereby the <ul>
that moves knows by how much to move.
Lines 13–20
-
$(this).addClass("current").parent().parent() ↩
-
.find("a:not($(this))").removeClass("current");
-
$polaroid.animate({left: margin}, 500, "easeInOutExpo");
-
console.log("i = "+i+" and margin = "+margin+"px");
-
});
-
$(this).click(function() {return false});
-
});
- };
You’ll notice that one of the links in the “sequence” list always has an underline to indicate current status: before, sketch, etc. This is accomplished by assigning a class called “current” to the link within the list, which re-assigns the background image on mouseover (actually, it uses the same background image but moves it, using a technique called CSS Sprites). But when you hover over another link, we need to remove whichever link has the class “current” and add it to whichever one you’re currently hovering over. Lines 13 and 14 take care of this.
First, we start with $(this)
which, in jQuery terms, refers to the link we’ve hovered over. We then add the class “current” to it and traverse the DOM two levels up (first to the parent <li>
, then to itsparent <ul>
). From there, we scan through all of the DOM elements within the list “sequence” and identify any links that have the class “current” and remove the class excepting the one we’re currently hovered over and just added the new class to. It most certainly would not do to remove that one! This is a great example of jQuery showing its magic, via the expression .find("a:not($(this))")
, which employs the :not()
pseudo-selector. Simply. Marvelous.
Lastly, after all this, we finally get to the bit that actually makes the movement happen, in line 15. All we need to do is assign the value of the variable “margin” to the left
value of the “gallery” <ul>
— recall that “margin” changes depending on which link we’ve moused over. In the case of the “Before” link, our loop counter “i” is 0
and therefore “margin” equals 0
, so the list gallery will get its left
value set to 0
and the whole slider lines up along the left side. If we hover over “Wireframe” instead, our loop counter is 2
and “margin” equates to –1160
.
As you’ll recall, a negative margin in CSS has the reverse effect as a positive one: instead of increasing the distance between the left side of the image frame and the “gallery” element, it decreases it, and the net result is to shift the screenshot to the left. We use the.animate()
method here as described previously and pass it two other arguments: 500 is the speed (in ms, so half a second), while “easeInOutExpo” is a kind of mathematical transition curve that makes the screenshot move more slowly at the beginning and at the end of the slide. (The curious reader can learn more about it at its creator’s website.)
Line 16 you may not recognize, which is a custom function that Firebug provides that lets you output info to the console. It’s a handy way of seeing what’s going on, without using a goofy “alert()
” call to find out the value of a variable. If you have Firebug installed, go to our portfolio page and open up the console, then try out the mouseovers in the little polaroid and you’ll see what I mean. The final bit, return false
, disables the default behavior of a link, which is to load another document in your browser. Since these links function only to be targets for mouseover, there’s no point in keeping that.
So That’s It in a Nutshell
When you really boil it down, the whole thing doesn’t seem like much, but it sure was a hassle at the time to get working right… as you can imagine, IE6 gave me no small amount of grief getting all the transparent PNG’s to render properly. (Thanks, Twin Helix folks!) Surprisingly, I had less trouble with the JavaScript problems cross-browser than I thought I would, since jQuery automatically compensates for the differences when executing scipts between browser vendors: the core library abstracts all the problems away. What that means is, when you write code in jQuery, it will work everywhere, which is a huge advantage over classic DOM scripting, for which many CSS-like JavaScript hacks and workarounds exist.
Could you have written this in Mootools or Prototype? Undoubtedly. But this easily and by someone totally new to it? That I’m less certain about, and speaks volumes about how simple and intuitive I find jQuery. Your mileage may vary, of course, and this article is not meant as a knock on the great work these other JavaScript framework developers are doing. I just like jQuery a whole lot.
(Mad props also go to Gian Carlo Mingati, whom I discovered later arrived at a similar solution, and whose idea to use the “preloading” image I totally ganked.)
Latest posts by (see all)
- Outperform Your Competitor: 3 Solid Strategies For Your Website - March 11, 2020
- How To Drive Conversions With Content - February 18, 2020
- Top 8 Web Design Trends to Nail It in 2020 - January 20, 2020
Leave a Reply