Recreating the MobileMe Web Gallery Interface: Part 2
Last week, I showed you how to recreate Apple’s MobileMe Gallery interface. Today, we will refine the interface with more features, including a larger view for the grid view, as well as a slideshow with crossfade transitions.
Preface
Last week, we ended up with a gallery that had two different views for our photos: Grid View and Mosaic View. What it was missing, however, as many of you pointed out, was a larger view or lightbox for the grid view. This way, when you clicked on an image in the grid view, a larger image would appear so that you could see it better. We will add that in this week’s tutorial.
Another nice feature to have would be a slideshow view. Sometimes it is nice to just sit back and enjoy the pictures rather than having to click around. Apple’s slideshow view is fairly rudimentary, with no transitions at all (the images jump from one to the next), which is very un Apple-like! We will work on that as well.
Step 1: Read Last Week’s Tutorial!
This is a very important step, even though it is obvious. In order for you to understand this tutorial, you will need to have read last week’s. You will also need to download the source code from last week’s tutorial, because we will use it as a starting point.
Step 2: Add the Needed Files

We will need two images for this tutorial. One of them is used in the large image view, and the other in the slideshow view. You should download this zip file and place the two images that are extracted into a folder called images within the assets folder from last week’s tutorial.
We will also need three files for our slideshow view: an html file, a css file, and a JavaScript file. First, create a file called slideshow.html in the root of last weeks tutorial folder that you downloaded. Next, create slideshow.css inside assets/css, and finally create slideshow.js in assets/js. You should now have all of the necessary files!
Step 3: The Larger View
We will start by creating the large image view for the grid view. First, open javascript.js (in the assets/js folder) with a text editor, and un-comment the two lines that look like this: largeView(this, i) (lines 105, and 138). These lines will call our largeView function, and tell it the current image so that we can display the correct one at full size. But, before this will work, we need to create the largeView function.
function largeView(photo, i) {
current = i;
var item = data[i];
var hovered = false;
$("h1").hide();
$(".button").remove();
$("#controls").hide();
$("#content").css({ bottom: "0px", top: "0px" });
$("#content").attr("class", "").addClass("large_view");
$("#content *").remove();
$('<div class="button">Back to Album</div>').click(function() {
view(); //go back to the current view
}).appendTo("#content");
var large = $('<div id="main">');
$("<img/>").attr("src", item.src).appendTo(large);
$("<strong/>").html(item.title).appendTo(large);
large.appendTo("#content");
var wrapper = $('<div id="hover_view_wrapper">');
var hover = $('<div id="hover_view">').hover(function() {
hovered = true;
}, function() {
hovered = false;
});
$('<div id="previous" title="Previous">').click(function() {
if(!data[current-1]) return;
$(".large_view #hover_view #next").removeClass("disabled");
if(!data[current-2]) $(this).addClass("disabled");
current--;
$(".large_view #main").animate({ opacity: 0 }, "fast", function() {
$(".large_view img").attr("src", data[current].src);
$(".large_view strong").html(data[current].title);
$(this).animate({ opacity: 1 }, "fast");
});
}).appendTo(hover);
$('<div id="next" title="Next">').click(function() {
if(!data[current+1]) return;
$(".large_view #hover_view #previous").removeClass("disabled");
if(!data[current+2]) $(this).addClass("disabled");
current++;
$(".large_view #main").animate({ opacity: 0 }, "fast", function() {
$(".large_view img").attr("src", data[current].src);
$(".large_view strong").html(data[current].title);
$(this).animate({ opacity: 1 }, "fast");
});
}).appendTo(hover);
wrapper.append(hover).appendTo("#content");
if(current == 0) {
$(".large_view #hover_view #previous").addClass("disabled");
}
else if(current == data.length-1) {
$(".large_view #hover_view #next").addClass("disabled");
}
var timer;
var showing = false;
$("#content").mousemove(function(event) {
if(!showing) {
showing = true;
$(".large_view #hover_view").stop().animate({ opacity: 1 });
}
clearTimeout(timer);
timer = setTimeout(function() {
if(hovered) return;
showing = false;
$(".large_view #hover_view").stop().animate({ opacity: 0 });
}, 2000);
});
};
At the top of javascript.js, there is a variable called current, which holds the index of the currently selected image. The first thing that we do in our largeView, is to set current to equal the index that was passed in. Next, we get the data about that index from the data file, and create a variable called hovered, which is set to false. When the mouse is hovered over the browser window, we want some controls to appear. These will allow the user to skip to the next and previous photo in the album. Next, we need to do some resetting. We hide the header, and remove the back button that was there for going back to the home screen, and hide the footer. We also extend the main content area into the empty space left by the hidden footer, add the large_view class to it so that we can style it easily, and remove anything that might have been there from other views.
After adding a back button that brings us back into whatever view we were in before going into large view, we create a div with the id of main. To this we add the selected photo, and it’s title. This is all that is necessary if we only wanted to view this photo larger. But, we would like to have some controls so that we can move between photos. These controls will appear and disappear as we need them, so that they are not always in our way.
Because we want the controls to be centered horizontally on the screen, we create a wrapper for easy styling. To this, we add a div with the id of hover_view. When we hover over this div, we want to set the hovered variable to true, and when we move our mouse out of the hover_view, we want to set it to false. More on this later. The next thing we create are the two buttons responsible for moving forward and backward in our album of photos. Both have similar code, but one has current–, and the other current++, for the previous and next buttons respectively. The first three lines of each button’s click handler are for validation. We want to make sure that the photo that we are moving to actually exists. If not, we will just return. The next two lines are used to disable, and enable the buttons. When we get to the first photo and the last photo in the album, we want to add a disabled look to the previous and next buttons respectively. If the photo back two, or forward two doesn’t exist, we know to disable the button since we are displaying the last photo possible to display in that direction. After adjusting the current image in the correct manner, we will fade out the div holding the image and it’s title, set them to their new values, and fade them back in. This creates a nice smooth effect when moving between images.
Next, we need to set the disabled state for the initial load of the large view. If current is equal to 0, we know that we are displaying the first image already, so let’s disable the previous button. If not, but we are on the last image, we should disable the next button.
Finally, we will make the controls disappear when we don’t need them. Here is what we want to happen. When the user moves their mouse, the controls will appear for them. If the user doesn’t move their mouse for over two seconds, we want the controls to be hidden again. The controls will only disappear after two seconds if the user’s mouse is not over the controls themselves. This is why we created a variable called hovered and added the hover event to the div holding the controls. First, we create two variables: timer, and showing. Next, we register a mousemove handler on the main content area (which takes up the whole browser window at this point. If showing is equal to false, we set it to true, and animate the opacity of the controls to 1. This makes the controls appear smoothly. Next we clear the timer that we set, and re-start it on the next line. The function passed into setTimeout is automatically called by the browser after two seconds. If we are not hovered over the controls, we set showing to false, and then animate the opacity to zero, otherwise we simply return and do nothing.
To complete the large view, we need to add the CSS.
.large_view {
overflow: hidden !important;
min-height: 400px;
padding: 0px !important;
}
.large_view #main {
margin: 50px auto 0 auto;
height: 80%;
width: 80%;
text-align: center;
}
.large_view #main img {
max-width: 100%;
max-height: 100%;
}
.large_view #main strong {
color: rgb(170, 170, 170);
font: bold 13px 'Helvetica Neue', Helvetica, Arial, sans-serif;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
display: block;
}
.large_view #hover_view_wrapper {
position: absolute;
bottom: 20px;
left: 0px;
width: 100%;
}
.large_view #hover_view {
margin: auto;
width: 221px;
height: 58px;
background: rgb(31, 31, 31);
background: rgba(31, 31, 31, 0.9);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
}
.large_view #hover_view #previous {
width: 42px;
height: 48px;
background: url(../images/arrows.png) 0 -320px no-repeat;
cursor: pointer;
margin-left: 50px;
float: left;
}
.large_view #hover_view #previous:hover {
background-position: 0 -257px;
}
.large_view #hover_view #previous.disabled {
background-position: 0 -194px;
}
.large_view #hover_view #next {
width: 41px;
height: 48px;
background: url(../images/arrows.png) 0 -131px no-repeat;
cursor: pointer;
margin-left: 130px;
}
.large_view #hover_view #next:hover {
background-position: 0 -68px;
}
.large_view #hover_view #next.disabled {
background-position: 0 -5px;
}
The CSS will be similar to the mosaic view, since we will be using percentages to make the layout scalable. Because it is scalable, we want the content area to have overflow: hidden set. We also give it a minimum height so that things don’t get too small when the window is resized. The #main div, will have a top margin of 50px, and it’s right and left margin will be set to auto so that the image is centered in the window. The width and height will be set to 80%, so that the image will resize based on the size of the window. To complete this, we set the img tag itself to have a maximum width and height of 100% of the #main div. This, along with the previous information, will make the image scale proportionally up to 80% of the width and height of the window. We also set the text-overflow property on the strong tag (the title of the image) to ellipsis, which causes three dots to appear at the end of the string when it is too long to fit in the space allotted. This won’t work in Firefox, but is a nice touch for browsers that support it.
Next, we get to the controls, or hover view. First, we style the wrapper that we generated with our jQuery so that is always 20px from the bottom of the window, and has a width of 100%. This will allow us to center the controls on the screen. Next, we style the hover_view itself. Setting the margin to auto will center it horizontally on the screen, and using rgba for the background color, will make the background of the controls slightly transparent in browsers that support it. The rest of this CSS is devoted to the next and previous buttons. Since we are using an image sprite for all of the arrows, we only need to set the background-position when the button becomes disabled or is hovered over. We also set the cursor to pointer so that the user knows that they can click on the buttons.
We should now have a large view that looks like this:

Step 4: The Slideshow
The slideshow is the only view that we use an entirely different page for. This is because we want it to open in a new, full screen (or as close as we can get) browser window. Before we start coding this page, let’s set up the view in our main page. Open up index.html, and add the following code after the link to Mosaic view. This is the link to slideshow view in the footer of our main page.
<a href="#" id="slideshow">Slideshow</a>
We need to style that link, so open up styles.css (in assets/css), and add the following lines before the styles for the album_view begin:
#views #slideshow {
background-position: 0px -60px;
}
#views #slideshow:hover {
color: white;
background-position: 0px -140px;
}
Now, open up javascript.js (in assets/js), and add the following the $(document).ready() function.
$("#slideshow").click(function() {
slideshowView();
});
This simply calls the slideshowView function when the link we just added to our html gets clicked. Finally, add the slideshowView function to the bottom of javascript.js.
function slideshowView() {
window.open("slideshow.html#"+album+"/"+current,
"slideshow",
"menubar=no,toolbar=no,location=no,fullscreen=yes,resizable=no,scrollbars=no,status=no,left=0,top=0,width="+screen.width+",height="+screen.height);
};
All this does, is open a new window with slideshow.html in it, and some options set. The # sign after sideshow.html shows that we are setting the hash of the URL. We will use this hash to know what album to display a slideshow for, and the picture we want to start the slideshow with. The options we set for this window, tell it not to have a menubar, toolbar, location bar, and status bar, and that it is not resizable, has no scrollbars and some browsers support the fullscreen attribute. We set the left and top position of the window to 0, and the width and height to the screen’s width and height. All of these options combined, make the window as large as possible on the screen so that we can get as close to true fullscreen as possible.
We need to keep track of the current album for this to work, so we will add a variable to the top of our javascript to do that.
var album = 0;
To keep track of the album, we need to change this variable when the current album changes. Find the line that reads:
var item = $('<div class="item">').click(function() {
(line 59 for me), and add the following line right below it:
album = i;
This should set the current album when the user clicks on an album in album view.
Alright, let’s start our work in slideshow.html. This is the HTML page that will be loaded into our fullscreen window. The html for the slideshow is again very simple, because most of the html will be generated by our jQuery.
<!DOCTYPE html>
<html>
<head>
<title>Slideshow</title>
<link rel="stylesheet" type="text/css" href="assets/css/slideshow.css">
<script type="text/javascript" src="assets/data/data.json"></script>
<script type="text/javascript" src="assets/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="assets/js/jquery-ui-1.7.1.custom.min.js"></script>
<script type="text/javascript" src="assets/js/slideshow.js"></script>
</head>
<body>
<div id="slideshow">
</div>
<div id="controls">
</div>
</body>
</html>
We bring in several files in the head section of our html. These include our slideshow.css file, our data file, jQuery and jQuery UI, and our slideshow.js file. There are two divs in the body section: one holds the slideshow, and one holds the controls. The layout of the slideshow page is very much like that of the large view. There is a main view that automatically resizes to fit the browser window, and controls that automatically appear and disappear.
Slideshow JavaScript
Let’s work on our JavaScript next. Open up slideshow.js (in assets/js), and add the following code to it. Don’t worry, I’ll explain it in a minute!
$(document).ready(function() {
//get the selected album and photo from the hash
var hash = window.location.hash.match(/#(\d+)\/(\d+)/);
var album = parseInt(hash[1]);
var current = parseInt(hash[2]);
//get the photos and title from the selected album, and set the title of the window
var data = albums[album].photos;
var title = albums[album].title;
document.title = gallery + " - "
+ title + " - "
+ data[current].title + " (" + (current + 1) + " of " + data.length + ")";
var fade = 500; //the duration of the slideshow crossfade
//create the two images used for crossfading
var img1 = $('<img id="img1">')
.attr("src", data[current].src)
.appendTo("#slideshow")
.fadeIn(fade)
.wrap("<div class='img_wrapper'></div>");
var img2 = $('<img id="img2">')
.attr("src", data[current+1].src)
.appendTo("#slideshow")
.wrap("<div class='img_wrapper'></div>");
//define the next and previous function used for changing the displayed image
var next = function() {
current++;
if(current >= data.length) current = 0;
var next = (current+1 >= data.length ? 0 : current+1);
document.title = gallery + " - " + title + " - " + data[current].title + " (" + (current + 1) + " of " + data.length + ")";
$("#slideshow img:visible").stop().fadeOut(fade, function() {
$(this).attr("src", data[next].src);
});
$("#slideshow img:hidden").attr("src", data[current].src).stop().fadeIn(fade);
};
var previous = function() {
current--;
if(current < 0) current = data.length-1;
var previous = (current < 0 ? data.length-1 : current);
document.title = gallery + " - "
+ title + " - "
+ data[current].title + " (" + (current + 1) + " of " + data.length + ")";
$("#slideshow img:visible").stop().fadeOut(fade, function() {
$(this).attr("src", data[current].src);
});
$("#slideshow img:hidden").attr("src", data[previous].src).stop().fadeIn(fade);
};
//set the timer to change images every 4 seconds
var interval = setInterval(next, 4000);
var playing = true; //is the slideshow currently playing?
var hovered = false; //are we hovered over the controls?
var wrapper = $('<div id="hover_view_wrapper">');
var hover = $('<div id="hover_view">').hover(function() {
hovered = true;
}, function() {
hovered = false;
});
//create the previous, next and play/pause buttons
$('<div id="previous" title="Previous">').click(function() {
previous();
if(current < 0) current = data.length-1;
if(playing) {
clearInterval(interval);
interval = setInterval(next, 4000);
}
}).appendTo(hover);
$('<div id="playpause" title="Pause">').addClass("pause").click(function() {
if(playing) {
clearInterval(interval);
$(this).removeClass("pause").addClass("play");
$(this).attr("title", "Play");
playing = false;
}
else {
interval = setInterval(next, 4000);
$(this).removeClass("play").addClass("pause");
$(this).attr("title", "Pause");
playing = true;
}
}).appendTo(hover);
$('<div id="next" title="Next">').click(function() {
next();
if(playing) {
clearInterval(interval);
interval = setInterval(next, 4000);
}
}).appendTo(hover);
wrapper.append(hover).appendTo("body");
var timer;
var showing = false;
$("body").mousemove(function(event) {
if(!showing) {
showing = true;
$("#hover_view").stop().animate({ opacity: 1 });
$("body").css("cursor", "default");
}
clearTimeout(timer);
timer = setTimeout(function() {
if(hovered) return;
showing = false;
$("#hover_view").stop().animate({ opacity: 0 });
$("body").css("cursor", "none");
}, 2000);
}).mousemove();
});
Wow, that’s a lot all at once. Let me explain it. The first thing we do, is use a regular expression to find the album and photo that we want to display. This is in the window.location.hash because we added it when we opened the window from our main page. Next, we get the title of the album, and the photos in the album and use this information to set the title of the window. The title will look like this (with the highlighted parts replaced with real values):
Gallery Title – Album Title – Photo Title (current index of number of photos in album)
For this slideshow, we will have a crossfade transition between photos. In order to accomplish this, we will use two images. Both of these images are created, one as the current image, and the other as the next image, and added to the #slideshow div. They are wrapped in a div with the class of img_wrapper so that we can center the image on the screen, and the first one is faded in. The next thing that happens, is that we create two functions: one to go to the next image, and the other to go to the previous image. We want the slideshow to loop, so if we come to the end we go to the beginning, and if we come to the beginning we go to the end. For both previous and next, we update the title of the window so that it reflects the current image. Then, we use two really nice jQuery selectors to find the visible and hidden images. To accomplish the crossfade effect, the visible image needs to be faded out, and the hidden one needs to be faded in. If we do those animations at the same time, we get a nice crossfade effect. The image being faded in needs to be set to the image that we are moving to, and the one being faded out needs to be set to the next image. To get the slideshow going, all we need to do is call setInterval so that the browser calls the next function every 4 seconds.
Next, we need to create the controls. We will have previous and next buttons, along with a play/pause button. The previous and next buttons are very simple. When you click them, they simply call the respective function for their action, and if the slideshow is currently playing, they reset the 4 second photo timer. This makes sure that when you change the current image, it doesn’t change again quickly as it would if the timer was just left running. Next, the play/pause button. If we are playing, we clear the slideshow timer, change the button’s class to play, set the tooltip of the button to “Play”, and set playing to false. If we are paused, we do just the opposite.
Finally, we register a mousemove handler on the body. This is the same code that we used for hiding and showing the controls that we used in the large view, so I won’t go into detail. There is one extra line of code. This sets the cursor of the body when the controls are invisible to none. This way, when you are watching the slideshow, the mouse cursor disappears.
To finish off the slideshow view, let’s add the CSS. Place the following in slideshow.css (in assets/css).
body {
overflow: hidden;
background: black;
margin: 0;
}
#slideshow .img_wrapper {
position: absolute;
top: 20px;
width: 100%;
height: 95%;
text-align: center;
}
#slideshow img {
max-height: 100%;
max-width: 100%;
display: none;
}
#hover_view_wrapper {
position: absolute;
bottom: 40px;
left: 0px;
width: 100%;
}
#hover_view {
margin: auto;
width: 251px;
height: 58px;
background: rgb(31, 31, 31);
background: rgba(31, 31, 31, 0.8);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
}
#hover_view #previous {
width: 42px;
height: 48px;
background: url(../images/arrows.png) 0 -320px no-repeat;
cursor: pointer;
margin-left: 50px;
float: left;
}
#hover_view #previous:hover {
background-position: 0 -257px;
}
#hover_view #previous.disabled {
background-position: 0 -194px;
}
#hover_view #playpause {
width: 52px;
height: 55px;
background: url(../images/slideshow_hud_sprite.png) 0 0 no-repeat;
cursor: pointer;
margin-left: 12px;
float: left;
}
#hover_view #playpause.pause {
background-position: 0 0;
}
#hover_view #playpause.play {
background-position: 0 -85px;
}
#hover_view #playpause.pause:hover {
background-position: 0 -170px;
}
#hover_view #playpause.play:hover {
background-position: 0 -255px;
}
#hover_view #next {
width: 41px;
height: 48px;
background: url(../images/arrows.png) 0 -131px no-repeat;
cursor: pointer;
margin-left: 160px;
}
#hover_view #next:hover {
background-position: 0 -68px;
}
#hover_view #next.disabled {
background-position: 0 -5px;
}
Most of this is very similar to the large view, since the layout is basically the same. The image_wrapper has a width of 100%, and a height of 95% so that it fills up most of the window, and it also has text-align set to center so that the image (which is an inline element) is centered horizontally on the page. The image itself has its maximum width and height set to 100% so that it remains proportional, and display set to none because we will fade it in with our jQuery. The hover_view is more or less exactly the same as in the large view, but there is an extra button (the play button) so we need to style that. All of the buttons use an image sprite to save on network traffic, and to save the need for image caching.
We should now have a finished slideshow view, complete with crossfade transitions, and the ability to play, pause and skip images!

Conclusion
Now that you’ve followed this tutorial, your gallery is more complete. It is still missing a backend, as well as support for Internet Explorer, however, so get to work! I hope you enjoyed the tutorial, and if you have any questions or comments, please leave them here, or send me a message on Twitter (@devongovett).
- Follow us on Twitter, or subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.


