/*! photobox v1.9.2 (c) 2012 Yair Even Or LICENSE: GNU AGPLv3 */ ;(function($, doc, win){ "use strict"; var Photobox, photobox, options, images=[], imageLinks, activeImage = -1, activeURL, lastActive, activeType, prevImage, nextImage, thumbsStripe, docElm, APControl, changeImage, $doc = $(doc), $win = $(win), isOldIE = !('placeholder' in doc.createElement('input')), noPointerEvents = (function(){ var el = $('

')[0]; el.style.cssText = 'pointer-events:auto'; return !el.style.pointerEvents})(), isTouchDevice = false, // assume "false" unless there's a touch thumbsContainerWidth, thumbsTotalWidth, activeThumb = $(), blankImg = "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", transformOrigin = getPrefixed('transformOrigin'), transition = getPrefixed('transition'), transitionend = "transitionend webkitTransitionEnd oTransitionEnd otransitionend", // Normalize rAF raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function(cb) { return window.setTimeout(cb, 1000 / 60); }, // Preload images preload = {}, preloadPrev = new Image(), preloadNext = new Image(), // DOM elements closeBtn, image, video, prevBtn, nextBtn, thumbsToggler, caption, captionText, pbLoader, autoplayBtn, thumbs, wrapper, defaults = { single : false, // if "true" - gallery will only show a single image, with no way to navigate beforeShow : null, // Callback before showing an image afterClose : null, // Callback after closing the gallery loop : true, // Allows to navigate between first and last images thumb : null, // A relative path from the link to the thumbnail (if it's not inside the link) thumbs : true, // Show gallery thumbnails below the presented photo thumbAttr : 'data-src', // Attribute to get the image for the thumbnail from captionTmpl : '

{title}
({currentImageIdx}/{totalImagesCount})
', autoplay : false, // should autoplay on first time or not time : 3000, // autoplay interval, in miliseconds (less than 1000 will hide the autoplay button) history : false, // should use history hashing if possible (HTML5 API) hideFlash : true, // Hides flash elements on the page when photobox is activated. NOTE: flash elements must have wmode parameter set to "opaque" or "transparent" if this is set to false zoomable : true, // disable/enable mousewheel image zooming rotatable : true, // allow rotation of the image wheelNextPrev : true, // change image using mousewheel left/right keys : { close : [27, 88, 67], // keycodes to close photobox, default: esc (27), 'x' (88), 'c' (67) prev : [37, 80], // keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80) next : [39, 78] // keycodes to navigate to the next image, default: Right arrow (39), 'n' (78) } }, // DOM structure overlay = $('
').append( thumbsToggler = $(''), pbLoader = $('
'), prevBtn = $('
').on('click', next_prev), nextBtn = $('
').on('click', next_prev), wrapper = $('
').append( // gives Perspective image = $(''), video = $('
') ), closeBtn = $('
').on('click', close)[0], autoplayBtn = $('
').append( $('
') ), caption = $('
').append( '', '', captionText = $('
'), thumbs = $('
').addClass('pbThumbs') ) ); /////////////////////////////////////////////// // Should remove this and use underscore/lodash if possible function throttle(callback, duration){ var wait = false; return function(){ if( !wait ){ callback.call(); wait = true; setTimeout(function(){wait = false; }, duration); } } } /////////////////////////////////////////////// // Initialization (on DOM ready) function prepareDOM( force ){ // do not procceed if already called, unless forced to if( document.body.contains(overlay[0]) && !force ) return; noPointerEvents && overlay.hide(); $doc.on('touchstart.testMouse', function(){ $doc.off('touchstart.testMouse'); isTouchDevice = true; overlay.addClass('mobile'); }); autoplayBtn.off().on('click', APControl.toggle); // attach a delegated event on the thumbs container thumbs.off().on('click', 'a', thumbsStripe.click); // if useragent is IE < 10 (user deserves a slap on the face, but I gotta support them still...) isOldIE && overlay.addClass('msie'); // cancel prorogation up to the overlay container so it won't close overlay.off().on('click', 'img', function(e){ e.stopPropagation(); }); $(doc.body).append(overlay); // need this for later: docElm = doc.documentElement; } // @param [List of elements to work on, Custom settings, Callback after image is loaded] $.fn.photobox = function(target, settings, callback){ prepareDOM(); return this.each(function(){ var PB_data = $(this).data('_photobox'); if( PB_data ){ // don't initiate the plugin more than once on the same element if( target === 'destroy') PB_data.destroy(); return this; } if( typeof target != 'string' ) target = 'a'; if( target === 'prepareDOM' ){ prepareDOM( true ); return this; } // merge the user settings with the default settings object settings = $.extend({}, defaults, settings || {}); // create an instance og Photobox photobox = new Photobox(settings, this, target); // Saves the insance on the gallery's target element $(this).data('_photobox', photobox); // add a callback to the specific gallery photobox.callback = callback; }); } Photobox = function(_options, object, target){ this.options = $.extend({}, _options); this.target = target; this.selector = $(object || doc); this.thumbsList = null; // filter the links which actually HAS an image as a child var filtered = this.imageLinksFilter( this.selector.find(target) ); this.imageLinks = filtered[0]; // Array of jQuery links this.images = filtered[1]; // 2D Array of image URL & title this.init(); }; Photobox.prototype = { init : function(){ // Cache DOM elements for this instance this.DOM = this.DOM(); this.DOM.rotateBtn.toggleClass('show', this.options.rotatable); // if any node was added or removed from the Selector of the gallery this.observerTimeout = null; this.events.binding.call(this); }, // happens only once DOM : function(){ var DOM = {} DOM.scope = overlay; DOM.rotateBtn = DOM.scope.find('.rotateBtn'); return DOM; }, //check if DOM nodes were added or removed, to re-build the imageLinks and thumbnails observeDOM : (function(){ var MutationObserver = win.MutationObserver || win.WebKitMutationObserver, eventListenerSupported = win.addEventListener; return function(obj, callback){ if( MutationObserver ){ var that = this, // define a new observer obs = new MutationObserver(function(mutations, observer){ if( mutations[0].addedNodes.length || mutations[0].removedNodes.length ) callback(that); }); // have the observer observe for changes in children obs.observe( obj, { childList:true, subtree:true }); } else if( eventListenerSupported ){ obj.addEventListener('DOMNodeInserted', $.proxy( callback, that ), false); obj.addEventListener('DOMNodeRemoved', $.proxy( callback, that ), false); } } })(), open : function(link){ var startImage = $.inArray(link, this.imageLinks); // if image link does not exist in the imageLinks array (probably means it's not a valid part of the gallery) if( startImage == -1 ) return false; // load the right gallery selector... options = this.options; images = this.images; imageLinks = this.imageLinks; photobox = this; this.setup(1); overlay.on(transitionend, function(){ overlay.off(transitionend).addClass('on'); // class 'on' is set when the initial fade-in of the overlay is done changeImage(startImage, true); }).addClass('show'); if( isOldIE ) overlay.trigger('MSTransitionEnd'); return false; }, imageLinksFilter : function(linksObj){ var that = this, images = [], caption = {}, captionlink; function linksObjFiler(i){ // search for the thumb inside the link, if not found then see if there's a 'that.settings.thumb' pointer to the thumbnail var link = $(this), thumbImg, thumbSrc = ''; caption.content = link[0].getAttribute('title') || ''; if( that.options.thumb ) thumbImg = link.find(that.options.thumb)[0]; // try a direct child lookup if( !that.options.thumb || !thumbImg ) thumbImg = link.find('img')[0]; // if no img child found in the link if( thumbImg ){ captionlink = thumbImg.getAttribute('data-pb-captionlink'); thumbSrc = thumbImg.getAttribute(that.options.thumbAttr) || thumbImg.getAttribute('src'); caption.content = ( thumbImg.getAttribute('alt') || thumbImg.getAttribute('title') || ''); } // if there is a caption link to be added: if( captionlink ){ captionlink = captionlink.split('['); // parse complex links: text[www.site.com] if( captionlink.length == 2 ){ caption.linkText = captionlink[0]; caption.linkHref = captionlink[1].slice(0,-1); } else{ caption.linkText = captionlink; caption.linkHref = captionlink; } caption.content += ' ' + caption.linkText + ''; } images.push( [link[0].href, caption.content, thumbSrc] ); return true; } return [linksObj.filter(linksObjFiler), images]; }, // things that should happen every time the gallery opens or closes (some messed up code below..) setup : function (open){ var fn = open ? "on" : "off"; // thumbs stuff if( options.thumbs ){ if( !isTouchDevice ){ thumbs[fn]('mouseenter.photobox', thumbsStripe.calc) [fn]('mousemove.photobox', thumbsStripe.move); } } if( open ){ image.css({'transition':'0s'}).removeAttr('style'); // reset any transition that might be on the element (yes it's ugly) overlay.show(); // Clean up if another gallery was viewed before, which had a thumbsList thumbs .html( this.thumbsList ) .trigger('mouseenter.photobox'); if( options.thumbs ){ overlay.addClass('thumbs'); } else{ thumbsToggler.prop('checked', false); overlay.removeClass('thumbs'); } // things to hide if there are less than 2 images if( this.images.length < 2 || options.single ) overlay.removeClass('thumbs hasArrows hasCounter hasAutoplay'); else{ overlay.addClass('hasArrows hasCounter') // check is the autoplay button should be visible (per gallery) and if so, should it autoplay or not. if( options.time > 1000 ){ overlay.addClass('hasAutoplay'); if( options.autoplay ) APControl.progress.start(); else APControl.pause(); } else overlay.removeClass('hasAutoplay'); } options.hideFlash && $('iframe, object, embed').css('visibility', 'hidden'); } else { $win.off('resize.photobox'); } $doc.off("keydown.photobox")[fn]({ "keydown.photobox": keyDown }); if( isTouchDevice ){ overlay.removeClass('hasArrows'); // no need for Arrows on touch-enabled wrapper[fn]('swipe', onSwipe); } if( options.zoomable ){ overlay[fn]({"mousewheel.photobox": $.proxy(this.events.callbacks.onScrollZoom, this) }); if( !isOldIE) thumbs[fn]({"mousewheel.photobox": thumbsResize }); } if( !options.single && options.wheelNextPrev ){ overlay[fn]({"mousewheel.photobox": throttle(wheelNextPrev,1000) }); } }, destroy : function(){ options = this.options; this.selector .off('click.photobox', this.target) .removeData('_photobox'); close(); }, events : { binding : function(){ var that = this; // only generates the thumbStripe once, and listen for any DOM changes on the selector element, if so, re-generate // This is done on "mouseenter" so images will not get called unless it's liekly that they would be needed this.selector.one('mouseenter.photobox', this.target, function(e){ that.thumbsList = thumbsStripe.generate.apply(that); }); this.selector.on('click.photobox', this.target, function(e){ e.preventDefault(); that.open(this); }); if( !isOldIE && this.selector[0].nodeType == 1 ) // observe normal nodes this.observeDOM( this.selector[0], $.proxy( this.events.callbacks.onDOMchanges, this )); this.DOM.rotateBtn.on('click', this.events.callbacks.onRotateBtnClick); }, callbacks : { onDOMchanges : function(){ var that = this; // use a timeout to prevent more than one DOM change event firing at once, and also to overcome the fact that IE's DOMNodeRemoved is fired BEFORE elements were actually removed clearTimeout(this.observerTimeout); that.observerTimeout = setTimeout( function(){ var filtered = that.imageLinksFilter( that.selector.find(that.target) ), activeIndex = 0, isActiveUrl = false, i; // Make sure that ONLY DOM changes in the photobox number of items will trigger a change if(that.imageLinks.length == filtered[0].length) return; that.imageLinks = filtered[0]; that.images = filtered[1]; // if photobox is opened if( photobox ){ // if gallery which was changed is the currently viewed one: if( that.selector == photobox.selector ){ images = that.images; imageLinks = that.imageLinks; // check if the currently VIEWED photo has been detached from a photobox set // if so, remove navigation arrows // TODO: fix the "images" to be an object and not an array. for( i = images.length; i--; ){ if( images[i][0] == activeURL ) isActiveUrl = true; // if not exits any more } // if( isActiveUrl ){ // overlay.removeClass('hasArrows'); // } } } // if this gallery has thumbs //if( that.options.thumbs ){ that.thumbsList = thumbsStripe.generate.apply(that); thumbs.html( that.thumbsList ); //} if( that.images.length && activeURL && that.options.thumbs ){ activeIndex = that.thumbsList.find('a[href="'+activeURL+'"]').eq(0).parent().index(); if( activeIndex == -1 ) activeIndex = 0; // updateIndexes(activeIndex); thumbsStripe.changeActive(activeIndex, 0); } }, 50); }, onRotateBtnClick : function(){ var rotation = image.data('rotation') || 0, // in "deg" imgScale = image.data('zoom') || 1 rotation += 90; image.removeClass('zoomable').addClass('rotating'); image.css('transform', 'rotate('+ rotation +'deg) scale('+ imgScale + ')') .data('rotation', rotation) .on(transitionend, function(){ image.addClass('zoomable').removeClass('rotating'); }); }, onScrollZoom : function(e, deltaY, deltaX){ if( deltaX ) return false; var that = this; if( activeType == 'video' ){ var zoomLevel = video.data('zoom') || 1; zoomLevel += (deltaY / 10); if( zoomLevel < 0.5 ) return false; video.data('zoom', zoomLevel).css({width:624*zoomLevel, height:351*zoomLevel}); } else{ raf(function() { var zoomLevel = image.data('zoom') || 1, rotation = image.data('rotation') || 0, position = image.data('position') || '50% 50%', boundingClientRect = image[0].getBoundingClientRect(), value; zoomLevel += (deltaY / 10); if( zoomLevel < 0.1 ) zoomLevel = 0.1; image.data('zoom', zoomLevel); value = 'scale('+ zoomLevel +') rotate('+ rotation +'deg)'; // if the image was zoomed and now is larger than the window size, allow mouse movemenet reposition if( boundingClientRect.height > docElm.clientHeight || boundingClientRect.width > docElm.clientWidth ){ $doc.on('mousemove.photobox', that.events.callbacks.onMouseMoveimageReposition); value += ' translate('+ position +')'; } else{ $doc.off('mousemove.photobox'); // image[0].style[transformOrigin] = '50% 50%'; } image.css({ 'transform':value }); }); } return false; }, // moves the image around during zoom mode on mousemove event onMouseMoveimageReposition : function(e){ raf(function() { var //y = (e.clientY / docElm.clientHeight) * (docElm.clientHeight + 200) - 100, // extend the range of the Y axis by 100 each side sensitivity = 1.5, // 1 = same as mouse more, and higher value is less sensitive to mouse move yDelta = (e.clientY / docElm.clientHeight * 100 - 50) / sensitivity, // subtract 50 because the real center is at "0%" xDelta = (e.clientX / docElm.clientWidth * 100 - 50) / sensitivity, // subtract 50 because the real center is at "0%" position, rotationAngel = image.data('rotation') || 0, rotation = (rotationAngel/90)%4 || 0, imgScale = image.data('zoom') || 1; if( rotation == 1 || rotation == 3 ) position = yDelta.toFixed(2)+'%, ' + -xDelta.toFixed(2) +'%'; else position = xDelta.toFixed(2)+'%, ' + yDelta.toFixed(2) +'%'; image.data('position', position); // image[0].style[transformOrigin] = origin; image[0].style.transform = 'rotate('+ rotationAngel +'deg) scale('+ imgScale + ') translate(' + position + ')'; }); } } } } // on touch-devices only function onSwipe(e, Dx, Dy){ var changeToImage, styles = { transform : 'translateX(25%)', transition : '.2s', opacity : 0 } // X-axis swipe if( Dx == 1 ){ changeToImage = prevImage; } else if( Dx == -1 ){ styles.transform = 'translateX(-25%)'; changeToImage = nextImage; } if( changeToImage ){ image.css(styles); setTimeout(function(){ changeImage(changeToImage) }, 200); } // Y-axis swipe thumbsToggler.prop('checked', Dy == 1 ); } // manage the (bottom) thumbs strip thumbsStripe = (function(){ var containerWidth = 0, scrollWidth = 0, posFromLeft = 0, // Stripe position from the left of the screen stripePos = 0, // When relative mouse position inside the thumbs stripe animated = null, padding, // in percentage to the containerWidth el, $el, ratio, scrollPos, pos; return{ // returns a