/*! photobox v1.9.2 (c) 2012 yair even or do not sell this software or use it as part of a package which is sold */ ;(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){ if( dx == 1 ){ image.css({transform:'translatex(25%)', transition:'.2s', opacity:0}); settimeout(function(){ changeimage(previmage) }, 200); } else if( dx == -1 ){ image.css({transform:'translatex(-25%)', transition:'.2s', opacity:0}); settimeout(function(){ changeimage(nextimage) }, 200); } if( dy == 1 ) thumbstoggler.prop('checked', true); else if( dy == -1 ) thumbstoggler.prop('checked', false); } // 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