programming4us
programming4us
MULTIMEDIA

jQuery 1.3 : An image carousel

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
10/9/2010 4:57:35 PM
As another example of shuffling around page content, we'll implement an image gallery for the front page of the bookstore site. The gallery will present a few featured books for sale, with links to larger cover art for each. Unlike the previous example, where the headlines in our news ticker moved on a set schedule, here we'll use jQuery to slide the images across the screen in response to user interaction.

An alternative mechanism for scrolling through a set of images is implemented by the jCarousel plugin for jQuery. Additionally, the highly flexible SerialScroll plugin allows for scrolling any type of content. While not identical to the result we'll achieve here, these plugins can produce high-quality shuffling effects with very little code. More information on using plugins can be found in Chapter 10.


Setting up the page

As always, we begin by crafting the HTML and CSS so that users without JavaScript available receive an appealing and functional representation of the information:

<div id="featured-books">
<div class="covers">
<a href="images/covers/large/1847190871.jpg"
title="Community Server Quickly">
<img src="images/covers/medium/1847190871.jpg"
width="120" height="148"
alt="Community Server Quickly" />
<span class="price">$35.99</span>
</a>
<a href="images/covers/large/1847190901.jpg"
title="Deep Inside osCommerce: The Cookbook">
<img src="images/covers/medium/1847190901.jpg"
width="120" height="148"
alt="Deep Inside osCommerce: The Cookbook" />
<span class="price">$44.99</span>
</a>
<a href="images/covers/large/1847190979.jpg"
title="Learn OpenOffice.org Spreadsheet Macro
Programming: OOoBasic and Calc automation">
<img src="images/covers/medium/1847190979.jpg"
width="120" height="148"
alt="Learn OpenOffice.org Spreadsheet Macro
Programming: OOoBasic and Calc automation" />
<span class="price">$35.99</span>
</a>
<a href="images/covers/large/1847190987.jpg"
title="Microsoft AJAX C# Essentials: Building
Responsive ASP.NET 2.0 Applications">
<img src="images/covers/medium/1847190987.jpg"
width="120" height="148"
alt="Microsoft AJAX C# Essentials: Building
Responsive ASP.NET 2.0 Applications" />
<span class="price">$31.99</span>
</a>
<a href="images/covers/large/1847191002.jpg"
title="Google Web Toolkit GWT Java AJAX Programming">
<img src="images/covers/medium/1847191002.jpg"
width="120" height="148"
alt="Google Web Toolkit GWT Java AJAX Programming" />
<span class="price">$40.49</span>
</a>
<a href="images/covers/large/1847192386.jpg"
title="Building Websites with Joomla! 1.5 Beta 1">
<img src="images/covers/medium/1847192386.jpg"
width="120" height="148"
alt="Building Websites with Joomla! 1.5 Beta 1" />
<span class="price">$40.49</span>
</a>
</div>
</div>



Each image is contained within an anchor tag, pointing to the larger version of the cover. We also have prices given for each cover; these will be hidden for now, and we'll use JavaScript to display them later at an appropriate time.

To save space on the front page, we want to show only three covers at a time. Without JavaScript, we can accomplish this by setting the overflow property of the container to scroll, and adjusting the width appropriately:

#featured-books {
position: relative;
background: #ddd;
width: 440px;
height: 186px;
overflow: scroll;
margin: 1em auto;
padding: 0;
text-align: center;
z-index: 2;
}
#featured-books .covers {
position: relative;
width: 840px;
z-index: 1;
}
#featured-books a {
float: left;
margin: 10px;
height: 146px;
}
#featured-books .price {
display: none;
}



These styles bear a bit of discussion. The outermost element needs to have a larger z-index property than the one inside it; this allows Internet Explorer to hide the part of the inner element that stretches beyond its container. We set the width of the outer element to 440px, which accommodates three images, the 10px margin around each, and an extra 20px for the scroll bar.

With these styles in place, the images can be browsed using a standard system scroll bar:

Revising the styles with JavaScript

Now that we have gone to the work of making the image gallery usable without JavaScript, we need to undo some of the niceties. The scroll bar will be redundant when we implement our own scrolling mechanism, and the automatic layout of the covers using the float property will get in the way of the positioning we need to do to animate the covers. So our first order of business will be overriding some styles:

$(document).ready(function() {
var spacing = 140;
$('#featured-books').css({
'width': spacing * 3,
'height': '166px',
'overflow': 'hidden'
}).find('.covers a').css({
'float': 'none',
'position': 'absolute',
'left': 1000
});
var $covers = $('#featured-books .covers a');
$covers.eq(0).css('left', 0);
$covers.eq(1).css('left', spacing);
$covers.eq(2).css('left', spacing * 2);
});

The spacing variable is going to come in handy throughout many of our calculations. It represents the width of one of the cover images, plus the padding on either side of it. The width of the containing element can now be set to exactly what is necessary to contain three of the cover images, since we don't need space for the scroll bar anymore. Indeed, we change the overflow property to hidden, and bye-bye scroll bar.

The cover images all get positioned absolutely, and start with a left coordinate of 1000. This places them out of the visible area. Then we move the first three covers into position, one at a time. The $covers variable holding all of the anchor elements will also come in handy later.

Now the first three covers are visible, with no scrolling mechanism available:

Shuffling images when clicked

Now, we need to add code to respond to a click on either of the end images, and reorder the covers as necessary. When the left cover is clicked, this means the user wants to see more images to the left, which in turn means we need to shift the covers to the right. Similarly, when the right cover is clicked we will have to shift the covers to the left. We want the carousel to wrap around, so when images fall off the left side, they get appended to the right. To begin, we will just change the image positions without animation.

$(document).ready(function() {
images, image carouselwrap aroundvar spacing = 140;
$('#featured-books').css({
'width': spacing * 3,
'height': '166px',
'overflow': 'hidden'
}).find('.covers a').css({
'float': 'none',
'position': 'absolute',
'left': 1000
});
var setUpCovers = function() {
var $covers = $('#featured-books .covers a');
$covers.unbind('click');
// Left image; scroll right (to view images on left).
$covers.eq(0)
.css('left', 0)
.click(function(event) {
$covers.eq(2).css('left', 1000);
$covers.eq($covers.length - 1)
.prependTo('#featured-books .covers');
setUpCovers();
event.preventDefault();
});
// Right image; scroll left (to view images on right).
$covers.eq(2)
.css('left', spacing * 2)
.click(function(event) {
$covers.eq(0).css('left', 1000);
$covers.eq(0)
.appendTo('#featured-books .covers');
setUpCovers();
event.preventDefault();
});
// Center image.
$covers.eq(1)
.css('left', spacing);
};
setUpCovers();
});



The new setUpCovers() function incorporates the image positioning code that we wrote earlier. By encapsulating this in a function, we can repeat the image positioning after the elements have been reordered; this will be important, as we shall soon see.

In our example, there are six images in total (which JavaScript will reference with the numbers 0 through 5), and numbers 0, 1, and 2 are visible. When image #0 is clicked, we want to shift all the images to the right by one position. We first move image #2 out of the viewable area (with .css('left', 1000)), since we don't want it to be visible after the shift. Then, we move the image at the end of the line (#5) to the front of the queue (using .prependTo()). This reorders all of the images so when setUpCovers() is called again, the former #5 is now #0, #0 has become #1, and #1 has become #2. The existing positioning code in this function is therefore sufficient to move the covers to their new locations.

Clicking on image #2 performs the process in reverse. This time, it is #0 that gets hidden from view, and then moved to the end of the queue. This shifts #1 to the #0 spot, #2 to #1, and #3 to #2.

There are a couple of details that we have to take care of to avoid user interaction anomalies:

  1. 1. We need to call .preventDefault() within our click handler, since we have made the covers into links to the large version. Without this call, the link will be followed and we would never see our shuffle effect.

  2. 2. We need to unbind all of the click handlers at the beginning of the setUpCovers() function, or we could end up with multiple handlers bound to the same image as the carousel rotates.

Adding sliding animation

It can be difficult to understand what just happened when an image is clicked; since the covers move instantaneously, they can appear to have just changed rather than moved. To mitigate this issue, we can add an animation that causes the covers to slide into place rather than just appearing in their new positions. This requires a revision of the setUpCovers() function:

var setUpCovers = function() {
images, image carouselsliding animation, addingvar $covers = $('#featured-books .covers a');
$covers.unbind('click');
// Left image; scroll right (to view images on left).
$covers.eq(0)
.css('left', 0)
.click(function(event) {
$covers.eq(0).animate({'left': spacing}, 'fast');
$covers.eq(1).animate({'left': spacing * 2}, 'fast');
$covers.eq(2).animate({'left': spacing * 3}, 'fast');
$covers.eq($covers.length - 1)
.css('left', -spacing)
.animate({'left': 0}, 'fast', function() {
$(this).prependTo('#featured-books .covers');
setUpCovers();
});
event.preventDefault();
});
// Right image; scroll left (to view images on right).
$covers.eq(2)
.css('left', spacing * 2)
.click(function(event) {
$covers.eq(0)
.animate({'left': -spacing}, 'fast', function() {
$(this).appendTo('#featured-books .covers');
setUpCovers();
});
$covers.eq(1).animate({'left': 0}, 'fast');
$covers.eq(2).animate({'left': spacing}, 'fast');
$covers.eq(3)
.css('left', spacing * 3)
.animate({'left': spacing * 2}, 'fast');
event.preventDefault();
});
// Center image.
$covers.eq(1)
.css('left', spacing);
};



When the left image is clicked, we can move all three visible images to the right by one image width (reusing the spacing variable we defined earlier). This part is straightforward, but we also have to make the new image slide into view. To do this, we grab the image from the end of the queue, and first set its screen position to be just off-screen on the left side (-spacing). Then, we slide it into view along with the other items.

Even though the animation takes care of the initial move, we still need to change the cover order by calling setUpCovers() again. If we don't, the next click won't work correctly. Since setUpCovers() changes the cover positions, we must defer the call until after the animation completes, so we place the call in the animation's callback.

A click on the rightmost image performs a similar set of animations, but in reverse. This time, it's the leftmost image that moves out of view, and must be moved to

the end of the queue before we trigger setUpCovers() when the animation is complete. The new, rightmost image, on the other hand, must be moved into position (spacing * 3) before its animation can begin.

Displaying action icons

Our image carousel now rotates smoothly, but we haven't provided any hint to the user that clicking on the covers will cause them to scroll. We can assist the user by displaying appropriate icons when the mouse hovers over the images.

In this case, we'll place the icons on top of the existing images. By using the opacity property, we can continue to see the cover underneath when the icon is displayed. We'll use simple monochrome icons so that the cover is not too obscured:

We'll need three icons, one each for the left and right covers, which the user will choose to scroll, and one for the middle cover, which the user can click for an enlarged version. We can create HTML elements that reference the icons and store them in variables for later use:

var $leftRollover = $('<img/>')
images, image carouselaction icons, displaying.attr('src', 'images/left.gif')
.addClass('control')
.css('opacity', 0.6)
.css('display', 'none');
var $rightRollover = $('<img/>')
.attr('src', 'images/right.gif')
.addClass('control')
.css('opacity', 0.6)
.css('display', 'none');
var $enlargeRollover = $('<img/>')
.attr('src', 'images/enlarge.gif')
.addClass('control')
.css('opacity', 0.6)
.css('display', 'none');



You may notice that we've got a fair amount of repetition here. To minimize this extra code, we can pull this work out into a function that we call for each icon that needs to be created:

function createControl(src) {
return $('<img/>')
.attr('src', src)
.addClass('control')
.css('opacity', 0.6)
.css('display', 'none');
}
var $leftRollover = createControl('images/left.gif');
var $rightRollover = createControl('images/right.gif');
var $enlargeRollover = createControl('images/enlarge.gif');

In the CSS for the page, we set the z-index of these controls to be higher than the images', and then position them absolutely so that they can overlap the covers:

#featured-books .control {
position: absolute;
z-index: 3;
left: 0;
top: 0;
}

The rollover icons all share the same control class, so one might be tempted to place the opacity style in the CSS stylesheet. However, element opacity is not handled consistently between browsers; in Internet Explorer, the syntax for 60% opacity is filter: alpha(opacity=60). Rather than wrestle with these distinctions, we set the opacity style using jQuery's .css() method, which abstracts away these browser inconsistencies.

Now, all we have to do in our hover handlers is to place the images in the right DOM location and show them.

var setUpCovers = function() {
var $covers = $('#featured-books .covers a');
$covers.unbind('click mouseenter mouseleave');

// Left image; scroll right (to view images on left).
$covers.eq(0)
.css('left', 0)
.click(function(event) {
$covers.eq(0).animate({'left': spacing}, 'fast');
$covers.eq(1).animate({'left': spacing * 2}, 'fast');
$covers.eq(2).animate({'left': spacing * 3}, 'fast');
$covers.eq($covers.length - 1)
.css('left', -spacing)
.animate({'left': 0}, 'fast', function() {
$(this).prependTo('#featured-books .covers');
setUpCovers();
});
event.preventDefault();
}).hover(function() {
$leftRollover.appendTo(this).show();
}, function() {
$leftRollover.hide();
});

// Right image; scroll left (to view images on right).
$covers.eq(2)
.css('left', spacing * 2)
.click(function(event) {
$covers.eq(0)
.animate({'left': -spacing}, 'fast', function() {
$(this).appendTo('#featured-books .covers');
setUpCovers();
});
$covers.eq(1).animate({'left': 0}, 'fast');
$covers.eq(2).animate({'left': spacing}, 'fast');
$covers.eq(3)
.css('left', spacing * 3)
.animate({'left': spacing * 2}, 'fast');
event.preventDefault();
}).hover(function() {
$rightRollover.appendTo(this).show();
}, function() {
$rightRollover.hide();
});

// Center image.
$covers.eq(1)
.css('left', spacing)
.hover(function() {
$enlargeRollover.appendTo(this).show();
}, function() {
$enlargeRollover.hide();
});

};



Just as we did earlier with click, we unbind mouseenter and mouseleave handlers at the beginning of setUpCovers() so that the hover behaviors do not accumulate. Here, we use another feature of the .unbind() method: handlers for multiple event types can be unbound at once by separating the event type names with spaces.

Why mouseenter and mouseleave? When we call the .hover() method, internally jQuery translates this into two separate event bindings. The first function we supply is bound as a handler for the mouseenter event, and the second is bound to mouseleave. So, to remove the handlers bound using .hover(), we need to unbind and mouseleave. mouseenter

Now when the mouse cursor is over a cover, the appropriate rollover image is overlaid on top of the cover:

Image enlargement

Now, our image gallery is fully functional, with a carousel that allows the user to navigate to a desired image. A click on the center image leads to an enlarged view of the cover in question. But, there is more we can do with this image enlargement functionality.

Rather than lead the user to a separate URL when the center image is clicked, we can overlay the enlarged book cover on the page itself.

A number of variations on the theme of displaying information overlaid on the page are available as jQuery plugins. A few of the more popular ones include FancyBox, ShadowBox, Thickbox, SimpleModal, and jqModal. More information on using plugins can be found in Chapter 10.


This larger cover image will require a new image element, which we can create at the same time that the hover images are instantiated:

var $enlargedCover = $('<img/>')
.addClass('enlarged')
.hide()
.appendTo('body');

We will apply a set of style rules to this new class that are similar to the ones we have seen before:

img.enlarged {
position: absolute;
z-index: 5;
cursor: pointer;
}

This absolute positioning will allow the cover to float above the other images we have positioned, because the z-index is higher than the ones we have already used. Now we need to actually position the enlarged image when the center image in the carousel is clicked:

// Center image; enlarge cover.
$covers.eq(1)
.css('left', spacing)
.click(function(event) {
$enlargedCover.attr('src', $(this).attr('href'))
.css({
'left': ($('body').width() - 360) / 2,
'top' : 100,
'width': 360,
'height': 444
}).show();
event.preventDefault();
})
.hover(function() {
$enlargeRollover.appendTo(this).show();
}, function() {
$enlargeRollover.hide();
});

We can take advantage of the links already present in the HTML source to know where the larger cover's image file resides on the server. We pluck this from the href src attribute of the enlarged cover image. attribute of the link, and set it as the

Now, we must position the image. The top, width, and height are hard-coded for now, but the left requires a little calculation. We want the enlarged image to be centered on the page, but we can't know in advance what the appropriate coordinate is to achieve this positioning. We can find the halfway mark across the page by measuring the width of the<body> element and dividing this by two. Half of our enlarged image will be on either side of this point, so the left coordinate of the image will be ($('body').width() - 360) / 2, since 360 is the width of the enlarged cover. The cover is now positioned appropriately, centered horizontally across the page:

Hiding the enlarged cover

We need a mechanism for dismissing the cover once it has been enlarged. The simplest way to do this is by making a click event on the cover fade it out:

// Center image; enlarge cover.
image enlargement, image carouselenlarged cover, dismissing$covers.eq(1)
.css('left', spacing)
.click(function(event) {
$enlargedCover.attr('src', $(this).attr('href'))
.css({
'left': ($('body').width() - 360) / 2,
'top' : 100,
'width': 360,
'height': 444
})
.show()
.one('click', function() {
$enlargedCover.fadeOut();
});

event.preventDefault();
})
.hover(function() {
$enlargeRollover.appendTo(this).show();
}, function() {
$enlargeRollover.hide();
});


We use the .one() method to bind this click handler, which sidesteps a couple of potential problems. With a regular .bind() of the handler, the user could click on the image again as it was fading out. This would cause the handler to fire again. Also, since we are reusing the same image element every time the cover is enlarged, the binding will occur again for each enlargement. If we do nothing to unbind the handler, they will stack up over time. Using .one() ensures that the handlers are removed once used.

Displaying a close button

This behavior is sufficient for removing the large cover, but we've given no indication to the user that clicking the cover will make it go away. We can provide this assistance by badging the enlarged image with a Close button. Creating the button is similar to defining the other singleton elements we've used&mdash;the items that are guaranteed to appear only once&mdash;and we can call the utility function that we created earlier:

var $closeButton = createControl('images/close.gif')
image enlargement, image carouselenlarged image, badging.addClass('enlarged-control')
.appendTo('body');


When the center cover is clicked, and the enlarged cover is displayed, we need to position and show the button:
$closeButton.css({
'left': ($('body').width() - 360) / 2,
'top' : 100
}).show();

The coordinates of the Close button are identical to the enlarged cover, so their top-left corners are aligned:

We already have a behavior bound to the image that hides it when the image is clicked. Typically in this situation we could rely on event bubbling to cause a click on the Close button to cause the same effect. In this case, however, the Close button is not a descendant element of the cover, despite appearances. We've absolutely positioned the Close button on top of the cover, which means that clicks on the button do not get passed to the enlarged image. Instead, we must handle clicks on the Close button ourselves:

// Center image; enlarge cover.
image enlargement, image carouselclose button used$covers.eq(1)
.css('left', spacing)
.click(function(event) {
$enlargedCover.attr('src', $(this).attr('href'))
.css({
'left': ($('body').width() - 360) / 2,
'top' : 100,
'width': 360,
'height': 444
})
.show()
.one('click', function() {
$closeButton.unbind('click').hide();

$enlargedCover.fadeOut();
});
$closeButton
.css({
'left': ($('body').width() - 360) / 2,
'top' : 100
})
.show()
.click(function() {
$enlargedCover.click();
});

event.preventDefault();
})
.hover(function() {
$enlargeRollover.appendTo(this).show();
}, function() {
$enlargeRollover.hide();
});



When we show the Close button, we bind a click event handler for it. All this handler needs to do, though, is to trigger the click handler we've already bound to the enlarged cover. We do need to modify that handler, though, and hide the Close button there. While we're at it, we unbind the click handler to prevent handlers from accumulating over time.

More fun with badging

Since we have the prices for the books available to us in the HTML source, we can display this as additional information when the book cover is enlarged. This time we'll apply the technique we just developed for the Close button to textual content rather than an image.

Once again, we create a singleton element at the beginning of our JavaScript code:

var $priceBadge = $('<div/>')
image enlargement, image carouselsingleton element, creating.addClass('enlarged-price')
.css('opacity', 0.6)
.css('display', 'none')
.appendTo('body');



Since the price will be partially transparent, a high contrast between font color and background will work best:

.enlarged-price {
background-color: #373c40;
color: #fff;
width: 80px;
padding: 5px;
font-size: 18px;
font-weight: bold;
text-align: right;
position: absolute;
z-index: 6;
}

Before we can display the price badge, we need to populate it with the actual price information from the HTML. Inside the center cover's click handler, the keyword refers to the link element. Since the price is in a<span> element within the link, obtaining the text is straightforward: this

var price = $(this).find('.price').text();

Now we can display the badge when the cover is enlarged:

$priceBadge.css({
'right': ($('body').width() - 360) / 2,
'top' : 100
}).text(price).show();

This will fix the price at the top-right corner of the enlarged image:

Once we place a $priceBadge.hide(); within the cover's click handler to clean up after ourselves, we're done.

Animating the cover enlargement

When the user clicks on the center cover, the enlarged version currently appears in the center of the page with no flair. To improve on this, we can use the built-in animation capabilities of jQuery to smoothly transition between the thumbnail view of the cover and the full-size version.

To do this, we need to know the starting coordinates of the animation; i.e. the position of the center cover on the page. Calculating this position requires some clever DOM traversal using plain JavaScript, but jQuery gives us a shortcut. The .offset() method returns an object containing the left and top coordinates of

an element relative to the page. We can then insert the width and height of the image into this object, and have the position information contained in a tidy package.

var startPos = $(this).offset();
startPos.width = $(this).width();
startPos.height = $(this).height();

Our destination coordinates can now be calculated from these quite easily. We'll collect them in a similar object.

var endPos = {};
endPos.width = startPos.width * 3;
endPos.height = startPos.height * 3;
endPos.top = 100;
endPos.left = ($('body').width() - endPos.width) / 2;

We can now use these two objects as maps of CSS attributes, which can be passed to methods such as .css() and .animate().

$enlargedCover.attr('src', $(this).attr('href'))
.css(startPos)

.show()
.animate(endPos, 'normal', function() {

$enlargedCover
.one('click', function() {
$closeButton.unbind('click').hide();
$priceBadge.hide();
$enlargedCover.fadeOut();
});
$closeButton
.css({
'left': endPos.left,
'top' : endPos.top

})
.show()
.click(function() {
$enlargedCover.click();
});
$priceBadge
.css({
'right': endPos.left,
'top' : endPos.top

})
.text(price)
.show();
});



Note that the Close button and price badge can't be placed until the animation completes, so we have moved their code into the callback of the .animate() method. Also, we've taken this opportunity to simplify the .css() calls for both of these elements by reusing the positioning information we calculated for the enlarged cover.

Now we have a smooth transition from small to large cover:

Deferring animations until image loads

Our animation is smooth, but depends on a fast connection to the site. If the enlarged cover takes some time to download, then the first moments of the animation might display the red X indicating a broken image, or still display the previous image. We can make the transition a bit more elegant by waiting until the image has fully loaded before starting the animation:

$enlargedCover.attr('src', $(this).attr('href'))
.css(startPos)
.show();
var performAnimation = function() {

$enlargedCover.animate(endPos, 'normal', function() {
$enlargedCover.one('click', function() {
$closeButton.unbind('click').hide();
$priceBadge.hide();
$enlargedCover.fadeOut();
});
$closeButton
.css({
'left': endPos.left,
'top' : endPos.top
})
.show()
.click(function() {
$enlargedCover.click();
});
$priceBadge
.css({
'right': endPos.left,
'top' : endPos.top
})
.text(price)
.show();
});
};
if ($enlargedCover[0].complete) {
performAnimation();
}
else {
$enlargedCover.bind('load', performAnimation);
}



There are two cases we have to consider: either the image is available nearly instantly (perhaps due to caching), or it needs time to load. In the first situation, the image's complete attribute will be true, so we can call our new performAnimation() function immediately. In the second case, we need to wait for the image load to complete before we call performAnimation(). This is a rare instance in which the standard DOM load event is more useful to us than jQuery's custom ready event. Since load is triggered on a window, image, or frame when all of its contents have fully loaded, we can observe the event to make sure that the image is being properly displayed. Only then is the handler executed, and the animation is performed.

We're using the .bind('load') syntax rather than the shorthand .load() method here for clarity since .load() is also an AJAX method; the two syntaxes are interchangeable.


Internet Explorer and Firefox have different interpretations of what to do if the image is already in the browser cache. In this case, Firefox will immediately send the event to JavaScript, but Internet Explorer will never send the event because no load actually occurred. Our testing of the complete attribute compensates for this variance in implementations. load

Adding a loading indicator

But now, we can have an awkward situation on slow network connections when an image takes a few moments to load. Our page appears to do nothing while this download is in progress. As we did when loading the news headlines, we should provide an indication to the user that some activity is occurring by displaying a loading indicator in the meantime.

The indicator will be another singleton image that will be displayed when appropriate:

var $waitThrobber = $('<img/>')
.attr('src', 'images/wait.gif')
.addClass('control')
.css('z-index', 4)
.hide();

For this image, we're actually using an animated GIF, because the motion will reinforce to the user that the activity is taking place:

It will just take two lines to put our loading indicator in place, now that we have the element defined. At the very beginning of our click handler for the center image, before we start doing any work, we need to display the indicator:

$waitThrobber.appendTo(this).show();

And at the beginning of the performAnimation() function, when we know the image has been loaded, we remove the indicator from view:

$waitThrobber.hide();

This is all it takes to badge the cover being enlarged with the loading indicator. The animation appears overlaying the top left corner of the cover:

The finished code

This chapter represents just a small fraction of what can be done on the Web with animated image and text rotators. Taken all together, the code for the image carousel looks like this:

$(document).ready(function() {
image carouselcodevar spacing = 140;
function createControl(src) {
return $('<img/>')
.attr('src', src)
.addClass('control')
.css('opacity', 0.6)
.css('display', 'none');
}
var $leftRollover = createControl('images/left.gif');
var $rightRollover = createControl('images/right.gif');
var $enlargeRollover = createControl('images/enlarge.gif');
var $enlargedCover = $('<img/>')
.addClass('enlarged')
.hide()
.appendTo('body');
var $closeButton = createControl('images/close.gif')
.addClass('enlarged-control')
.appendTo('body');
var $priceBadge = $('<div/>')
.addClass('enlarged-price')
.css('opacity', 0.6)
.css('display', 'none')
.appendTo('body');
var $waitThrobber = $('<img/>')
.attr('src', 'images/wait.gif')
.addClass('control')
.css('z-index', 4)
.hide();
$('#featured-books').css({
'width': spacing * 3,
'height': '166px',
'overflow': 'hidden'
}).find('.covers a').css({
'float': 'none',
'position': 'absolute',
'left': 1000
});
var setUpCovers = function() {
var $covers = $('#featured-books .covers a');
$covers.unbind('click mouseenter mouseleave');
// Left image; scroll right (to view images on left).
$covers.eq(0)
.css('left', 0)
.click(function(event) {
$covers.eq(0).animate({'left': spacing}, 'fast');
$covers.eq(1).animate({'left': spacing * 2}, 'fast');
$covers.eq(2).animate({'left': spacing * 3}, 'fast');
$covers.eq($covers.length - 1)
.css('left', -spacing)
.animate({'left': 0}, 'fast', function() {
$(this).prependTo('#featured-books .covers');
setUpCovers();
});
event.preventDefault();
}).hover(function() {
$leftRollover.appendTo(this).show();
}, function() {
$leftRollover.hide();
});
// Right image; scroll left (to view images on right).
image carouselcode$covers.eq(2)
.css('left', spacing * 2)
.click(function(event) {
$covers.eq(0)
.animate({'left': -spacing}, 'fast', function() {
$(this).appendTo('#featured-books .covers');
setUpCovers();
});
$covers.eq(1).animate({'left': 0}, 'fast');
$covers.eq(2).animate({'left': spacing}, 'fast');
$covers.eq(3)
.css('left', spacing * 3)
.animate({'left': spacing * 2}, 'fast');
event.preventDefault();
}).hover(function() {
$rightRollover.appendTo(this).show();
}, function() {
$rightRollover.hide();
});
// Center image; enlarge cover.
$covers.eq(1)
.css('left', spacing)
.click(function(event) {
$waitThrobber.appendTo(this).show();
var price = $(this).find('.price').text();
var startPos = $(this).offset();
startPos.width = $(this).width();
startPos.height = $(this).height();
var endPos = {};
endPos.width = startPos.width * 3;
endPos.height = startPos.height * 3;
endPos.top = 100;
endPos.left = ($('body').width() - endPos.width) / 2;
$enlargedCover.attr('src', $(this).attr('href'))
.css(startPos)
.show();
var performAnimation = function() {
$waitThrobber.hide();
$enlargedCover.animate(endPos, 'normal',
function() {
$enlargedCover.one('click', function() {
$closeButton.unbind('click').hide();
$priceBadge.hide();
$enlargedCover.fadeOut();
});
$closeButton
.css({
'left': endPos.left,
'top' : endPos.top
image carouselcode})
.show()
.click(function() {
$enlargedCover.click();
});
$priceBadge
.css({
'right': endPos.left,
'top' : endPos.top
})
.text(price)
.show();
});
};
if ($enlargedCover[0].complete) {
performAnimation();
}
else {
$enlargedCover.bind('load', performAnimation);
}
event.preventDefault();
})
.hover(function() {
$enlargeRollover.appendTo(this).show();
}, function() {
$enlargeRollover.hide();
});
};
setUpCovers();
image carouselcode});



Other  
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
programming4us programming4us
programming4us
 
 
programming4us