/*! * jQuery flowgallery plugin: Cover Flow Image Gallery * Examples and documentation at: http://flowgallery.org * Version: 0.7.0 (08-JAN-2013) * Author: Boris Searles (boris@lucidgardens.com) * Requires jQuery v1.4 or later * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php */ ;(function( $, window, document, undefined ){ "use strict"; var FlowGallery = function(list, config) { var self = this, list, $list, // reference to list as jquery object options = {}, // merged config options with defaults centerX = 0, // horizontal center within list centerY = 0, // vertical center within list $container = false, // parent element of list $caption = false, // caption element activeItem = false, // reference to active FlowItem activeIndex = 0, listWidth = 0, // list width (default: screen width) listHeight = 0, // list height (height of highest image) flowItems = [], // array of FlowItems elCounter = 0, // number of list items resizeWhileDisabled = false; // initialize gallery function init() { var metadata; $list = $(list); // This next line takes advantage of HTML5 data attributes // to support customization of the plugin on a per-element // basis. For example, // metadata = $list.data( 'plugin-options' ); options = $.extend({}, FlowGallery.defaults, config, metadata); $container = $list.parent(); self.length = $list.children().length; self.options = options; initDom(); initFlowItems(); $list.css('visibility', 'visible'); $(window).resize( $.proxy(windowResizeHandler, self) ); if(options.enableKeyNavigation===true) { $(document).keydown(handleKeyEvents); } updateFlow(false); }; // initialize dom setup and styling function initDom() { // set required styling on list element $list.css({ listStyle: 'none', overflow: 'hidden', marginLeft: '0', paddingLeft: '0', position: 'relative', width: '100%' }); initCaption(); initWrapper(); }; // initialize caption - a single instance is shared by all images function initCaption() { var captionElem = document.createElement('p'); $caption = $(captionElem).addClass('fg-caption').css({ backgroundColor: options.backgroundColor, display: 'none', marginTop: '0', padding: '8px ' + (options.imagePadding+10) + 'px 15px', position: 'absolute' }); }; // initialize wrapper around gallery - used for positioning caption function initWrapper() { var wrapperElem = document.createElement('div'); $(wrapperElem) .addClass('fg-wrapper').css({ position: 'relative' }) .append( $list.get(0) ) .append( $caption.get(0) ); $container.append(wrapperElem); }; function initFlowItems() { self.enabled = false; // loop through list items to extract image data and determine list height $list.children().each(function(index) { var flowItem = new FlowItem(this, index, options); if(!flowItem.$el) { return; } flowItem.$el.on('loaded', onItemLoaded); flowItem.$el.on('click', onItemClicked); if(!activeItem && options.activeIndex===index) { flowItem.$el.addClass('active'); activeItem = flowItem; activeIndex = index; } flowItems.push(flowItem); }); self.enabled = true; listWidth = $container.width(); centerX = listWidth*0.5; }; function onItemLoaded() { var item = $(this).data('flowItem'); updateListHeight(item.h); if(item.index===options.activeIndex) { self.activeLoaded = true; updateFlow(true); } else if(self.activeLoaded && (item.th !== options.loadingHeight || item.tw !== options.loadingWidth)) { updateFlow(true); } }; function onItemClicked(e) { var item = $(this).data('flowItem'); if(item !== activeItem) { var oldIndex = activeIndex; flowInDir(item.index-oldIndex); } else if(options.forwardOnActiveClick===true) { flowInDir(1); } }; // only applied after animation of 'active' element function afterFlowHandler(){ showCaption(); // adjust if width changed (i.e. if scrollbars get displayed) if($container.width() !== listWidth) { listWidth = $container.width(); centerX = listWidth*0.5; updateFlow(); showCaption(); } }; // special afterFlow handler for previously active item function getOldActiveAfterFlowHandler(itemIndex) { return function() { var item = flowItems[itemIndex]; item.oldActive = false; }; }; function updateFlow(animate) { if(!self.isEnabled()) { return false; } var config = {}, isBefore = true, // in loop, are we before 'active' item completeFn = null, // callback method to call after animation (for 'active' item) currentItem = false, $listItem = false, itemsLength = flowItems.length, i; // update centerY based on active image centerY = options.thumbTopOffset==='auto' ? activeItem.h*0.5 : options.thumbTopOffset; for(i=0; i flowItems.length-1 || newIndex < 0) { return false; } activeItem.oldActive = true; // mark old active item if(dir<0 && activeIndex > 0) { flowItems[activeIndex].oldActive = true; activeIndex += dir; } else if(dir>0 && activeIndex < flowItems.length-1) { activeIndex += dir; } else { return false; } activeItem = flowItems[activeIndex]; prepareFlow(); updateFlow(animate); }; function prepareFlow() { //if (_options.circular) { // _circularFlow(currentIndex); //} $caption.hide(); $list.find('.active').removeClass('active'); $(activeItem.$el).addClass('active'); // update width (changes if scrollbars disappear) listWidth = $container.width(); centerX = listWidth*0.5; }; function calculateLeftPosition(current, isBefore) { var left = centerX, i = 0; if (isBefore) { left -= flowItems[activeIndex].w*0.5; left -= options.imagePadding; left -= (activeIndex - current) * 10; left -= (activeIndex - current) * 2 * options.thumbPadding; for (i = current; i < activeIndex; i++) { left -= flowItems[i].tw; } } else { left += flowItems[activeIndex].w*0.5; left += options.imagePadding; left += (current - activeIndex) * 10; left += (current - activeIndex) * 2 * options.thumbPadding; for (i = activeIndex + 1; i < current; i++) { left += flowItems[i].tw; } } return left + 'px'; }; // window resize handler - update gallery when window is resized function windowResizeHandler() { if(!self.isEnabled()) { resizeWhileDisabled = true; return false; } listWidth = $container.width(); centerX = listWidth*0.5; updateFlow(); showCaption(); }; // show caption under active image function showCaption() { if(!activeItem.isLoaded) { return false; } var fullHeight, captionText = activeItem.captionText; if(captionText && captionText.length > 0){ $caption.text(captionText); $caption.css({ left: centerX - options.imagePadding - activeItem.w * 0.5, top: activeItem.h + options.imagePadding*2, width: activeItem.w - 20 }); // set height of caption as bottom margin for list fullHeight = activeItem.h + $caption.height() + 40; //this.$list.height(fullHeight); $caption.fadeIn('fast'); } else { // TODO: what happens here? fullHeight = activeItem.h + 40; //this.$list.height(fullHeight); } }; // set list height to height of tallest image (needed for overflow:hidden) function updateListHeight(height) { if(height > listHeight) { listHeight = height; listHeight += options.imagePadding*2; $list.height(listHeight); } }; // handle key events function handleKeyEvents(e) { if(e.keyCode===37) { // right arrow key flowInDir(-1); } else if(e.keyCode===39) { // left arrow key flowInDir(1); } }; // Public API ================================================== this.length = 0; // number of images in gallery this.enabled = true; // is gallery currently enabled this.options = {}; this.getActiveIndex = function() { return activeIndex; }; this.next = function(animate) { flowInDir(1, animate); return this; }; this.prev = function(animate) { flowInDir(-1, animate); return this; }; this.jump = function(index, animate) { flowInDir(index - activeIndex, animate); return this; }; this.isEnabled = function() { return this.enabled===true; }; this.disable = function() { this.enabled = false; return this; }; this.enable = function() { this.enabled = true; // if resize event was fired while disabled, then adjust after // being enabled again if(resizeWhileDisabled) { $.proxy(this.windowResizeHandler, this)(); resizeWhileDisabled = false; } return this; }; // initialize init(); }; // default option values FlowGallery.defaults = { activeIndex: 0, // index of image that is initially active animate: true, backgroundColor: 'black', circular: false, duration: 900, // animation duration (higher value = slower speed) easing: 'linear', enableKeyNavigation: true, // enables forward/backward arrow keys for next/previous navigation forceHeight: false, forceWidth: false, forwardOnActiveClick: true, // should clicking on active image, show next image? imagePadding: 0, // border of active image loadingClass: "loading", // css class applied to
  • elements of loading images loadingHeight: 60, // loading height to use if cannot be determined loadingWidth: 100, // loading width to use if cannot be determined thumbHeight: 'auto', thumbPadding: 0, // border of thumbnails thumbTopOffset: 'auto', // top offset in pixels or 'auto' for centering images within list height thumbWidth: 'auto' }; var FlowItem = function(elem, index, options) { var self = this, $img = false; this.$el = $(elem); this.index = index; this.h = 0; // image height this.th = 0; // thumb height this.w = 0; // image width this.tw = 0; // thumb width this.active = false; // is active image? this.isLoaded = false; // is image fully loaded? this.captionText = false; // text specified within 'title' attribute of img this.oldActive = false; // is this image being animated away from active position? function init() { self.h = options.loadingHeight; self.th = options.loadingHeight; self.w = options.loadingWidth; self.tw = options.loadingWidth; $img = self.$el.find('img'); if($img.length===0) { // mark as invalid if no image found self.$el = false; return; } self.$el.data('flowItem', self); self.captionText = $img.attr('title'); if(options.forceWidth) { $img.width(options.forceWidth); } // remove image and add 'loading' class $img.hide().parent().addClass(options.loadingClass).css({ height: self.th, width: self.tw }); addLoadHandler(); self.$el.css({ backgroundColor: options.backgroundColor, padding: options.thumbPadding, position: 'absolute', textAlign: 'center' }); $img.css({ cursor: 'pointer', height: '100%', imageRendering: 'optimizeQuality', // firefox property //-ms-interpolation-mode: 'bicubic', // IE property width: '100%' }); }; // add handler to images to call after finished loading function addLoadHandler() { var img = $img.get(0); if(img.complete) { initListItem(); } else { $img.bind('load readystatechange', imageLoadHandler) .bind('error', function () { $img.css('visibility', 'visible').parent().removeClass(options.loadingClass); }); } }; // event handler for image loading function imageLoadHandler(e) { var img = $img.get(0); if (img.complete || (img.readyState === 'complete' && e.type === 'readystatechange')) { initListItem(); } }; // load handler for images function initListItem(){ $img.css('visibility', 'visible').parent().removeClass(options.loadingClass); $img.fadeIn(); self.isLoaded = true; initImageDimensions(); self.$el.trigger('loaded', self); }; // determine image dimensions function initImageDimensions() { var img = $img.get(0); // update full image dimensions if(typeof img.naturalWidth !== 'undefined') { self.w = options.forceWidth || img.naturalWidth || img.width; self.h = options.forceHeight || img.naturalHeight || img.height; } else { var tmpImg = new Image(); tmpImg.src = $img.attr('src'); self.w = tmpImg.width; self.h = tmpImg.height; } // update thumbnail dimensions if(options.thumbWidth === 'auto' && options.thumbHeight === 'auto') { self.tw = options.loadingWidth; self.th = Math.round(self.h * options.loadingWidth / self.w); } else if (options.thumbHeight === 'auto') { self.tw = options.thumbWidth; self.th = Math.round(self.h * Number(options.thumbWidth) / self.w); } else if (options.thumbWidth === 'auto') { self.tw = Math.round(self.w * Number(options.thumbHeight) / self.h); self.th = options.thumbHeight; } else { self.tw = options.thumbWidth; self.th = options.thumbHeight; } }; // Public API ================================================== init(); }; /** Basic jQuery plugin setup */ $.fn.flowgallery = function(options) { return this.each(function() { var element = $(this); // Return early if this element already has a plugin instance if (element.data('flowgallery')) { return; } // pass options to plugin constructor var flowgallery = new FlowGallery(this, options); // Store plugin object in this element's data element.data('flowgallery', flowgallery); }); }; // default options $.fn.flowgallery.defaults = FlowGallery.defaults; })( jQuery, window , document );