(function($) { $.fn.carousel = function (options) { // Replace the default options with user defined options. options = $.extend({}, $.fn.carousel.defaults, options || {}); return $(this).each(function () { /* Do not modify the universal values!*/ var pluginData = { currentCenterNum: options.startingFeature, containerWidth: 0, containerHeight: 0, largeFeatureWidth: 0, largeFeatureHeight: 0, smallFeatureWidth: 0, smallFeatureHeight: 0, totalFeatureCount: $(this).children("div").length, currentlyMoving: false, featuresContainer: $(this), featuresArray: [], containerIDTag: "#"+$(this).attr("id"), timeoutVar: null, rotationsRemaining: 0, itemsToAnimate: 0, borderWidth: 0 }; preload(function () { setupFeatureDimensions(); setupCarousel(); setupFeaturePositions(); setupBlips(); initiateMove(true,1); }); // Function to preload the images. function preload(callback) { // Function to not preload the images. if (options.preload == true) { var $imageElements = pluginData.featuresContainer.find("img"); var loadedImages = 0; var totalImages = $imageElements.length; $imageElements.each(function () { // Attempt to load the images $(this).load(function () { // Add to number of images loaded and see if they are all done yet loadedImages++; if (loadedImages == totalImages) { // All done, perform callback callback(); } }); // The images may already be cached in the browser, in which case they // would have a 'true' complete value and the load callback would never be // fired. This will fire it manually. if (this.complete) { $(this).trigger('load'); } }); } else { // If user doesn't want preloader, then just go right to callback. callback(); } } // Gets the feature container based on the number. function getContainer(featureNum) { return pluginData.featuresArray[featureNum-1]; } function getBySetPos(position) { $.each(pluginData.featuresArray, function () { if ($(this).data().setPosition == position) return $(this); }); } // Get previous feature number. function getPreviousNum(num) { if ((num - 1) == 0) { return pluginData.totalFeatureCount; } else { return num - 1; } } // Get next feature number. function getNextNum(num) { if ((num + 1) > pluginData.totalFeatureCount) { return 1; } else { return num + 1; } } /** * Because there are several options the user can set for the width and height * of the feature images, this function is used to determine which options were set * and to set the appropriate dimensions.*/ function setupFeatureDimensions() { // Set the height and width of the entire carousel container. pluginData.containerWidth = pluginData.featuresContainer.width(); pluginData.containerHeight = pluginData.featuresContainer.height(); // Grab the first image for reference. var $firstFeatureImage = $(pluginData.containerIDTag).find("div img:first"); // Large Feature Width. if (options.largeFeatureWidth > 1) pluginData.largeFeatureWidth = options.largeFeatureWidth; else if (options.largeFeatureWidth > 0 && options.largeFeatureWidth < 1) pluginData.largeFeatureWidth = $firstFeatureImage.width() * options.largeFeatureWidth; else pluginData.largeFeatureWidth = $firstFeatureImage.outerWidth(); // Large Feature Height. if (options.largeFeatureHeight > 1) pluginData.largeFeatureHeight = options.largeFeatureHeight; else if (options.largeFeatureHeight > 0 && options.largeFeatureHeight < 1) pluginData.largeFeatureHeight = $firstFeatureImage.height() * options.largeFeatureHeight; else pluginData.largeFeatureHeight = $firstFeatureImage.outerHeight(); // Small Feature Width. if (options.smallFeatureWidth > 1) pluginData.smallFeatureWidth = options.smallFeatureWidth; else if (options.smallFeatureWidth > 0 && options.smallFeatureWidth < 1) pluginData.smallFeatureWidth = $firstFeatureImage.width() * options.smallFeatureWidth; else pluginData.smallFeatureWidth = $firstFeatureImage.outerWidth() / 2; // Small Feature Height. if (options.smallFeatureHeight > 1) pluginData.smallFeatureHeight = options.smallFeatureHeight; else if (options.smallFeatureHeight > 0 && options.smallFeatureHeight < 1) pluginData.smallFeatureHeight = $firstFeatureImage.height() * options.smallFeatureHeight; else pluginData.smallFeatureHeight = $firstFeatureImage.outerHeight() / 2; } /** * Function to take care of setting up the default positions for the features.*/ function setupCarousel() { // Set the total feature count to the amount the user cutoff. if (options.displayCutoff > 0 && options.displayCutoff < pluginData.totalFeatureCount) { pluginData.totalFeatureCount = options.displayCutoff; } // Fill in the features array. pluginData.featuresContainer.children("div").each(function (index) { if (index < pluginData.totalFeatureCount) { pluginData.featuresArray[index] = $(this); } }); // Determine the total border width around the feature if there is one. if (pluginData.featuresContainer.children("div").first().css("borderLeftWidth") != "medium") { pluginData.borderWidth = parseInt(pluginData.featuresContainer.children("div").first().css("borderLeftWidth"))*2; } // Place all the features in a center hidden position to start off. pluginData.featuresContainer // Have to make the container relative positioning. .children("div").each(function () { // Center all the features in the middle and hide them. $(this).css({ 'left': (pluginData.containerWidth / 2) - (pluginData.smallFeatureWidth / 2) - (pluginData.borderWidth / 2), 'width': pluginData.smallFeatureWidth, 'height': pluginData.smallFeatureHeight, 'top': options.smallFeatureOffset + options.topPadding, 'opacity': 0 }); }) // Set all the images to small feature size. .find("img:first").css({ 'width': pluginData.smallFeatureWidth }); // figure out number of items that will rotate each time. if (pluginData.totalFeatureCount < 4) { pluginData.itemsToAnimate = pluginData.totalFeatureCount; } else { pluginData.itemsToAnimate = 4; } // Hide story info and set the proper positioning. pluginData.featuresContainer.find("div > div") .hide(); } //Here all the position data is set for the features.*/ function setupFeaturePositions() { // Give all features a set number that won't change so they remember their // original order. $.each(pluginData.featuresArray, function (i) { $(this).data('setPosition',i+1); }); // Go back one. var oneBeforeStarting = getPreviousNum(options.startingFeature); pluginData.currentCenterNum = oneBeforeStarting; // Center feature will be position 1. var $centerFeature = getContainer(oneBeforeStarting); $centerFeature.data('position',1); // Everything before the center feature. var $prevFeatures = $centerFeature.prevAll(); $prevFeatures.each(function (i) { $(this).data('position',(pluginData.totalFeatureCount - i)); }); // And everything after the center feature. var $nextFeatures = $centerFeature.nextAll(); $nextFeatures.each(function (i) { if ($(this).data('setPosition') != undefined) { $(this).data('position',(i + 2)); } }); // If the counter style is for including number tags in description. if (options.counterStyle == 3) { $.each(pluginData.featuresArray, function () { var pos = getPreviousNum($(this).data('position')); var $numberTag = $(""); $numberTag.addClass("numberTag"); $numberTag.html("("+ pos + " of " + pluginData.totalFeatureCount + ") "); $(this).find('div p').prepend($numberTag); }); } } // Blips are created. function setupBlips() { // Only setup the blips if the counter style is 1 or 2. if (options.counterStyle == 1 || options.counterStyle == 2) { // construct the blip list. var $list = $(""); $list.addClass("blipsContainer"); for (var i = 0; i < pluginData.totalFeatureCount; i++) { // Counter style 1 has no numbers, while 2 does. var counter; if (options.counterStyle == 2) counter = ""; else counter = i+1; // Build the DOM for the blip list. var $blip = $("
"+counter+"
"); $blip.addClass("blip"); $blip.css("cursor","pointer"); $blip.attr("id","blip_"+(i+1)); var $listEntry = $("
  • "); $listEntry.append($blip); $listEntry.css("float","left"); $listEntry.css("list-style-type","none"); $list.append($listEntry); } // add the blip list and then make sure it's visible. $(pluginData.containerIDTag).append($list); $list.hide().show(); } } // Move the highlighted blip to the currently centered feature. function changeBlip(oldCenter, newCenter) { // get selectors for the two blips. var $blipsContainer = pluginData.featuresContainer.find(".blipsContainer"); var $oldCenter = $blipsContainer.find("#blip_"+oldCenter); var $newCenter = $blipsContainer.find("#blip_"+newCenter); // Change classes. $oldCenter.removeClass("blipSelected"); $newCenter.addClass("blipSelected"); } // Sets the autoPlay for carousel. function autoPlay() { // Clear the timeout var if it exists. if (pluginData.timeoutVar != null) { pluginData.timeoutVar = clearTimeout(pluginData.timeoutVar); } // Set interval for moving if autoplay is set. if (options.autoPlay != 0) { var autoTime = (Math.abs(options.autoPlay) < options.carouselSpeed) ? options.carouselSpeed : Math.abs(options.autoPlay); pluginData.timeoutVar = setTimeout(function () { if (options.autoPlay > 0) initiateMove(true,1); else if (options.autoPlay < 0) initiateMove(false,1); }, autoTime); } } // This is a helper function for the animateFeature function that // will update the positions of all the features based on the direction. function rotatePositions(direction) { $.each(pluginData.featuresArray, function () { var newPos; if (direction == false) { newPos = getNextNum($(this).data().position); } else { newPos = getPreviousNum($(this).data().position); } $(this).data('position',newPos); }); } /** * This function is used to animate the given feature to the given * location. Valid locations are "left", "right", "center", and "hidden". */ function animateFeature($feature, direction) { var new_width, new_height, new_top, new_left, new_zindex, new_padding, new_fade; // Determine the old and new positions of the feature. var oldPosition = $feature.data('position'); var newPosition; if (direction == true) newPosition = getPreviousNum(oldPosition); else newPosition = getNextNum(oldPosition); // Caculate new new css values depending on where the feature will be located. if (newPosition == 1) { new_width = pluginData.largeFeatureWidth; new_height = pluginData.largeFeatureHeight; new_top = options.topPadding; new_zindex = $feature.css("z-index"); new_left = (pluginData.containerWidth / 2) - (pluginData.largeFeatureWidth / 2) - (pluginData.borderWidth / 2); new_fade = 1.0; } else { new_width = pluginData.smallFeatureWidth; new_height = pluginData.smallFeatureHeight; new_top = options.smallFeatureOffset + options.topPadding; new_zindex = 1; new_fade = 0.4; // Left if (newPosition == pluginData.totalFeatureCount) { new_left = options.sidePadding; // Right } else if (newPosition == 2) { new_left = pluginData.containerWidth - pluginData.smallFeatureWidth - options.sidePadding - pluginData.borderWidth; // Hidden } else { new_left = (pluginData.containerWidth / 2) - (pluginData.smallFeatureWidth / 2) - (pluginData.borderWidth / 2); new_fade = 0; } } // This code block takes care of hiding the feature information. if (newPosition != 1) { $feature.find("div") .hide(); } // Animate the feature div to its new location. $feature .animate( { width: new_width, height: new_height, top: new_top, left: new_left, opacity: new_fade }, options.carouselSpeed, options.animationEasing, function () { // Take feature info out of hiding if new position is center. if (newPosition == 1) { // Fade in the feature information. $feature.find("div") .fadeTo("fast",0.85); } // Decrement the animation queue. pluginData.rotationsRemaining = pluginData.rotationsRemaining - 1; // Have to change the z-index after the animation is done. $feature.css("z-index", new_zindex); if (options.counterStyle == 1 || options.counterStyle == 2) { if (newPosition == 1) { var newCenterItemNum = pluginData.featuresContainer.children("div").index($feature) + 1; var oldCenterItemNum; if (direction == false) oldCenterItemNum = getNextNum(newCenterItemNum); else oldCenterItemNum = getPreviousNum(newCenterItemNum); // Change the active blip. changeBlip(oldCenterItemNum, newCenterItemNum); } } var divide = pluginData.rotationsRemaining / pluginData.itemsToAnimate; if (divide % 1 == 0) { pluginData.currentlyMoving = false; rotatePositions(direction); if (pluginData.rotationsRemaining > 0) move(direction); } // Call autoplay again. autoPlay(); } ) .find("img:first") .animate({ width: new_width, height: new_height }, options.carouselSpeed, options.animationEasing) .end(); } // Carousel movement. function move(direction) { // Set the carousel to currently moving. pluginData.currentlyMoving = true; // Obtain the new feature positions based on the direction that the carousel is moving. var $newCenter, $newLeft, $newRight, $newHidden; if (direction == true) { // Shift features to the left. $newCenter = getContainer(getNextNum(pluginData.currentCenterNum)); $newLeft = getContainer(pluginData.currentCenterNum); $newRight = getContainer(getNextNum(getNextNum(pluginData.currentCenterNum))); $newHidden = getContainer(getPreviousNum(pluginData.currentCenterNum)); pluginData.currentCenterNum = getNextNum(pluginData.currentCenterNum); } else { $newCenter = getContainer(getPreviousNum(pluginData.currentCenterNum)); $newLeft = getContainer(getPreviousNum(getPreviousNum(pluginData.currentCenterNum))); $newRight = getContainer(pluginData.currentCenterNum); $newHidden = getContainer(getNextNum(pluginData.currentCenterNum)); pluginData.currentCenterNum = getPreviousNum(pluginData.currentCenterNum); } // The z-index must be set before animations take place for certain movements. if (direction) { $newLeft.css("z-index", 3); } else { $newRight.css("z-index", 3); } $newCenter.css("z-index", 4); // Animate the features into their new positions. animateFeature($newLeft, direction); animateFeature($newCenter, direction); animateFeature($newRight, direction); // Only want to animate the "hidden" feature if there are more than three. if (pluginData.totalFeatureCount > 3) { animateFeature($newHidden, direction); } } // This is used to relegate carousel movement. function initiateMove(direction, rotations) { if (pluginData.currentlyMoving == false) { var queue = rotations * pluginData.itemsToAnimate; pluginData.rotationsRemaining = queue; move(direction); } } /** * This will find the shortest distance to travel the carousel from * one position to another position.*/ function findShortestDistance(from, to) { var goingToLeft = 1, goingToRight = 1, tracker; tracker = from; // See how long it takes to go to the left. while ((tracker = getPreviousNum(tracker)) != to) { goingToLeft++; } tracker = from; // See how long it takes to to to the right. while ((tracker = getNextNum(tracker)) != to) { goingToRight++; } // Whichever is shorter. return (goingToLeft < goingToRight) ? goingToLeft*-1 : goingToRight; } // Move to the left if left button clicked. $(".leftButton").click(function () { initiateMove(false,1); }); // Move to right if right button clicked. $(".rightButton").click(function () { initiateMove(true,1); }); // These are the click and hover events for the features. pluginData.featuresContainer.children("div") .click(function () { var position = $(this).data('position'); if (position == 2) { initiateMove(true,1); } else if (position == pluginData.totalFeatureCount) { initiateMove(false,1); } }) .mouseover(function () { if (pluginData.currentlyMoving == false) { var position = $(this).data('position'); if (position == 2 || position == pluginData.totalFeatureCount) { $(this).css("opacity",0.8); } } }) .mouseout(function () { if (pluginData.currentlyMoving == false) { var position = $(this).data('position'); if (position == 2 || position == pluginData.totalFeatureCount) { $(this).css("opacity",0.4); } } }); // Add event listener to all clicks within the features container. $("a", pluginData.containerIDTag).live("click", function (event) { var $parents = $(this).parentsUntil(pluginData.containerIDTag); $parents.each(function () { var position = $(this).data('position'); if (position != undefined) { if (position != 1) { if (position == pluginData.totalFeatureCount) { initiateMove(false,1); } else if (position == 2) { initiateMove(true,1); } event.preventDefault(); return false; } } }); }); $(".blip").live("click",function () { // Grab the position # that was clicked. var goTo = $(this).attr("id").substring(5); // Find out where that feature # actually is in the carousel right now. var whereIsIt = pluginData.featuresContainer.children("div").eq(goTo-1).data('position'); // Which feature # is currently in the center. var currentlyAt = pluginData.currentCenterNum; // If the blip was clicked for the current center feature, do nothing. if (goTo != currentlyAt) { // Find the shortest distance to move the carousel. var shortest = findShortestDistance(1, whereIsIt); // Initiate a move in that direction with given number of rotations. if (shortest < 0) { initiateMove(false,(shortest*-1)); } else { initiateMove(true,shortest); } } }); }); }; $.fn.carousel.defaults = { largeFeatureWidth : 0, largeFeatureHeight: 0, smallFeatureWidth: .5, smallFeatureHeight: .5, topPadding: 20, sidePadding: 30, smallFeatureOffset: 50, startingFeature: 1, carouselSpeed: 1000, autoPlay: 5000, counterStyle: 1, preload: true, displayCutoff: 0, animationEasing: 'swing' }; })(jQuery);