1/*! 2 photobox v1.9.2 3 (c) 2012 Yair Even Or 4 5 Do not sell this software or use it as part of a package which is sold 6*/ 7 8 9;(function($, doc, win){ 10 "use strict"; 11 12 var Photobox, photobox, options, images=[], imageLinks, activeImage = -1, activeURL, lastActive, activeType, prevImage, nextImage, thumbsStripe, docElm, APControl, changeImage, 13 $doc = $(doc), 14 $win = $(win), 15 isOldIE = !('placeholder' in doc.createElement('input')), 16 noPointerEvents = (function(){ var el = $('<p>')[0]; el.style.cssText = 'pointer-events:auto'; return !el.style.pointerEvents})(), 17 isTouchDevice = false, // assume "false" unless there's a touch 18 thumbsContainerWidth, thumbsTotalWidth, activeThumb = $(), 19 blankImg = "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", 20 transformOrigin = getPrefixed('transformOrigin'), 21 transition = getPrefixed('transition'), 22 transitionend = "transitionend webkitTransitionEnd oTransitionEnd otransitionend", 23 // Normalize rAF 24 raf = window.requestAnimationFrame 25 || window.webkitRequestAnimationFrame 26 || window.mozRequestAnimationFrame 27 || window.msRequestAnimationFrame 28 || function(cb) { return window.setTimeout(cb, 1000 / 60); }, 29 30 // Preload images 31 preload = {}, preloadPrev = new Image(), preloadNext = new Image(), 32 // DOM elements 33 closeBtn, image, video, prevBtn, nextBtn, thumbsToggler, caption, captionText, pbLoader, autoplayBtn, thumbs, wrapper, 34 35 defaults = { 36 single : false, // if "true" - gallery will only show a single image, with no way to navigate 37 beforeShow : null, // Callback before showing an image 38 afterClose : null, // Callback after closing the gallery 39 loop : true, // Allows to navigate between first and last images 40 thumb : null, // A relative path from the link to the thumbnail (if it's not inside the link) 41 thumbs : true, // Show gallery thumbnails below the presented photo 42 thumbAttr : 'data-src', // Attribute to get the image for the thumbnail from 43 captionTmpl : '<div class="title">{title}</div><div class="counter">({currentImageIdx}/{totalImagesCount})</div>', 44 autoplay : false, // should autoplay on first time or not 45 time : 3000, // autoplay interval, in miliseconds (less than 1000 will hide the autoplay button) 46 history : false, // should use history hashing if possible (HTML5 API) 47 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 48 zoomable : true, // disable/enable mousewheel image zooming 49 rotatable : true, // allow rotation of the image 50 wheelNextPrev : true, // change image using mousewheel left/right 51 keys : { 52 close : [27, 88, 67], // keycodes to close photobox, default: esc (27), 'x' (88), 'c' (67) 53 prev : [37, 80], // keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80) 54 next : [39, 78] // keycodes to navigate to the next image, default: Right arrow (39), 'n' (78) 55 } 56 }, 57 58 59 // DOM structure 60 overlay = $('<div id="pbOverlay">').append( 61 thumbsToggler = $('<input type="checkbox" id="pbThumbsToggler" checked hidden>'), 62 pbLoader = $('<div class="pbLoader"><b></b><b></b><b></b></div>'), 63 prevBtn = $('<div id="pbPrevBtn" class="prevNext"><b></b></div>').on('click', next_prev), 64 nextBtn = $('<div id="pbNextBtn" class="prevNext"><b></b></div>').on('click', next_prev),
65 wrapper = $('<div class="pbWrapper">').append( // gives Perspective 66 image = $('<img>'), 67 video = $('<div class="pbVideo">') 68 ), 69 closeBtn = $('<div id="pbCloseBtn">').on('click', close)[0], 70 autoplayBtn = $('<div id="pbAutoplayBtn">').append( 71 $('<div class="pbProgress">') 72 ), 73 caption = $('<div id="pbCaption">').append( 74 '<label for="pbThumbsToggler" title="Show/hide thumbnails"></label>', 75 '<button title="Rotate right" class="rotateBtn">⟳</button>', 76 captionText = $('<div class="pbCaptionText">'), 77 thumbs = $('<div>').addClass('pbThumbs') 78 ) 79 ); 80 81 /////////////////////////////////////////////// 82 // Should remove this and use underscore/lodash if possible 83 84 function throttle(callback, duration){ 85 var wait = false; 86 return function(){ 87 if( !wait ){ 88 callback.call(); 89 wait = true; 90 setTimeout(function(){wait = false; }, duration); 91 } 92 } 93 } 94 95 /////////////////////////////////////////////// 96 // Initialization (on DOM ready) 97 98 function prepareDOM( force ){ 99 // do not procceed if already called, unless forced to 100 if( document.body.contains(overlay[0]) && !force ) 101 return; 102 103 noPointerEvents && overlay.hide(); 104 105 $doc.on('touchstart.testMouse', function(){ 106 $doc.off('touchstart.testMouse'); 107 isTouchDevice = true; 108 overlay.addClass('mobile'); 109 }); 110 111 autoplayBtn.off().on('click', APControl.toggle); 112 // attach a delegated event on the thumbs container 113 thumbs.off().on('click', 'a', thumbsStripe.click); 114 // if useragent is IE < 10 (user deserves a slap on the face, but I gotta support them still...) 115 isOldIE && overlay.addClass('msie'); 116 117 // cancel prorogation up to the overlay container so it won't close 118 overlay.off().on('click', 'img', function(e){ 119 e.stopPropagation(); 120 }); 121 122 $(doc.body).append(overlay); 123 124 // need this for later: 125 docElm = doc.documentElement; 126 } 127 128 // @param [List of elements to work on, Custom settings, Callback after image is loaded] 129 $.fn.photobox = function(target, settings, callback){ 130 prepareDOM(); 131 132 return this.each(function(){ 133 var PB_data = $(this).data('_photobox'); 134 135 if( PB_data ){ // don't initiate the plugin more than once on the same element 136 if( target === 'destroy') 137 PB_data.destroy(); 138 139 return this; 140 } 141 142 if( typeof target != 'string' ) 143 target = 'a'; 144 145 if( target === 'prepareDOM' ){ 146 prepareDOM( true ); 147 return this; 148 } 149 150 // merge the user settings with the default settings object 151 settings = $.extend({}, defaults, settings || {}); 152 153 // create an instance og Photobox 154 photobox = new Photobox(settings, this, target); 155 156 // Saves the insance on the gallery's target element 157 $(this).data('_photobox', photobox); 158 159 // add a callback to the specific gallery 160 photobox.callback = callback; 161 }); 162 } 163 164 Photobox = function(_options, object, target){ 165 this.options = $.extend({}, _options); 166 this.target = target; 167 this.selector = $(object || doc); 168 169 this.thumbsList = null; 170 // filter the links which actually HAS an image as a child 171 var filtered = this.imageLinksFilter( this.selector.find(target) ); 172 173 this.imageLinks = filtered[0]; // Array of jQuery links 174 this.images = filtered[1]; // 2D Array of image URL & title 175 this.init(); 176 }; 177 178 Photobox.prototype = { 179 init : function(){ 180 // Cache DOM elements for this instance 181 this.DOM = this.DOM(); 182 183 this.DOM.rotateBtn.toggleClass('show', this.options.rotatable); 184 185 // if any node was added or removed from the Selector of the gallery 186 this.observerTimeout = null; 187 this.events.binding.call(this); 188 }, 189 190 // happens only once 191 DOM : function(){ 192 var DOM = {} 193 194 DOM.scope = overlay; 195 DOM.rotateBtn = DOM.scope.find('.rotateBtn'); 196 197 return DOM; 198 }, 199 200 //check if DOM nodes were added or removed, to re-build the imageLinks and thumbnails 201 observeDOM : (function(){ 202 var MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
203 eventListenerSupported = win.addEventListener; 204 205 return function(obj, callback){ 206 if( MutationObserver ){ 207 var that = this, 208 // define a new observer 209 obs = new MutationObserver(function(mutations, observer){ 210 if( mutations[0].addedNodes.length || mutations[0].removedNodes.length ) 211 callback(that); 212 }); 213 // have the observer observe for changes in children 214 obs.observe( obj, { childList:true, subtree:true }); 215 } 216 else if( eventListenerSupported ){ 217 obj.addEventListener('DOMNodeInserted', $.proxy( callback, that ), false); 218 obj.addEventListener('DOMNodeRemoved', $.proxy( callback, that ), false); 219 } 220 } 221 })(), 222 223 open : function(link){ 224 var startImage = $.inArray(link, this.imageLinks); 225 // if image link does not exist in the imageLinks array (probably means it's not a valid part of the gallery) 226 if( startImage == -1 ) 227 return false; 228 229 // load the right gallery selector... 230 options = this.options; 231 images = this.images; 232 imageLinks = this.imageLinks; 233 234 photobox = this; 235 this.setup(1); 236 237 overlay.on(transitionend, function(){ 238 overlay.off(transitionend).addClass('on'); // class 'on' is set when the initial fade-in of the overlay is done 239 changeImage(startImage, true); 240 }).addClass('show'); 241 242 if( isOldIE ) 243 overlay.trigger('MSTransitionEnd'); 244 245 return false; 246 }, 247 248 imageLinksFilter : function(linksObj){ 249 var that = this, 250 images = [], 251 caption = {}, 252 captionlink; 253 254 function linksObjFiler(i){ 255 // search for the thumb inside the link, if not found then see if there's a 'that.settings.thumb' pointer to the thumbnail 256 var link = $(this), 257 thumbImg, 258 thumbSrc = ''; 259 260 caption.content = link[0].getAttribute('title') || ''; 261 262 if( that.options.thumb ) 263 thumbImg = link.find(that.options.thumb)[0]; 264 265 // try a direct child lookup 266 if( !that.options.thumb || !thumbImg ) 267 thumbImg = link.find('img')[0]; 268 269 // if no img child found in the link 270 if( thumbImg ){ 271 captionlink = thumbImg.getAttribute('data-pb-captionlink'); 272 thumbSrc = thumbImg.getAttribute(that.options.thumbAttr) || thumbImg.getAttribute('src'); 273 caption.content = ( thumbImg.getAttribute('alt') || thumbImg.getAttribute('title') || ''); 274 } 275 276 277 // if there is a caption link to be added: 278 if( captionlink ){ 279 captionlink = captionlink.split('['); 280 // parse complex links: text[www.site.com] 281 if( captionlink.length == 2 ){ 282 caption.linkText = captionlink[0]; 283 caption.linkHref = captionlink[1].slice(0,-1); 284 } 285 else{ 286 caption.linkText = captionlink; 287 caption.linkHref = captionlink; 288 } 289 caption.content += ' <a href="'+ caption.linkHref +'">' + caption.linkText + '</a>'; 290 } 291 292 images.push( [link[0].href, caption.content, thumbSrc] ); 293 294 return true; 295 } 296 297 return [linksObj.filter(linksObjFiler), images]; 298 }, 299 300 // things that should happen every time the gallery opens or closes (some messed up code below..) 301 setup : function (open){ 302 var fn = open ? "on" : "off"; 303 304 // thumbs stuff 305 if( options.thumbs ){ 306 if( !isTouchDevice ){ 307 thumbs[fn]('mouseenter.photobox', thumbsStripe.calc) 308 [fn]('mousemove.photobox', thumbsStripe.move); 309 } 310 } 311 312 if( open ){ 313 image.css({'transition':'0s'}).removeAttr('style'); // reset any transition that might be on the element (yes it's ugly) 314 overlay.show(); 315 // Clean up if another gallery was viewed before, which had a thumbsList 316 thumbs 317 .html( this.thumbsList ) 318 .trigger('mouseenter.photobox'); 319 320 if( options.thumbs ){ 321 overlay.addClass('thumbs'); 322 } 323 else{ 324 thumbsToggler.prop('checked', false); 325 overlay.removeClass('thumbs'); 326 } 327 328 // things to hide if there are less than 2 images 329 if( this.images.length < 2 || options.single ) 330 overlay.removeClass('thumbs hasArrows hasCounter hasAutoplay'); 331 else{ 332 overlay.addClass('hasArrows hasCounter') 333 334 // check is the autoplay button should be visible (per gallery) and if so, should it autoplay or not. 335 if( options.time > 1000 ){ 336 overlay.addClass('hasAutoplay'); 337 if( options.autoplay ) 338 APControl.progress.start(); 339 else 340 APControl.pause(); 341 } 342 else 343 overlay.removeClass('hasAutoplay'); 344 } 345 346 options.hideFlash && $('iframe, object, embed').css('visibility', 'hidden'); 347 348 } else { 349 $win.off('resize.photobox'); 350 } 351 352 $doc.off("keydown.photobox")[fn]({ "keydown.photobox": keyDown }); 353 354 if( isTouchDevice ){ 355 overlay.removeClass('hasArrows'); // no need for Arrows on touch-enabled 356 wrapper[fn]('swipe', onSwipe); 357 } 358 359 if( options.zoomable ){ 360 overlay[fn]({"mousewheel.photobox": $.proxy(this.events.callbacks.onScrollZoom, this) }); 361 if( !isOldIE) thumbs[fn]({"mousewheel.photobox": thumbsResize }); 362 } 363 364 if( !options.single && options.wheelNextPrev ){ 365 overlay[fn]({"mousewheel.photobox": throttle(wheelNextPrev,1000) }); 366 } 367 }, 368 369 destroy : function(){ 370 options = this.options; 371 this.selector 372 .off('click.photobox', this.target) 373 .removeData('_photobox'); 374 375 close(); 376 }, 377 378 events : { 379 binding : function(){ 380 var that = this; 381 382 // only generates the thumbStripe once, and listen for any DOM changes on the selector element, if so, re-generate 383 // This is done on "mouseenter" so images will not get called unless it's liekly that they would be needed 384 this.selector.one('mouseenter.photobox', this.target, function(e){ 385 that.thumbsList = thumbsStripe.generate.apply(that); 386 }); 387 388 this.selector.on('click.photobox', this.target, function(e){ 389 e.preventDefault(); 390 that.open(this); 391 }); 392 393 if( !isOldIE && this.selector[0].nodeType == 1 ) // observe normal nodes 394 this.observeDOM( this.selector[0], $.proxy( this.events.callbacks.onDOMchanges, this )); 395 396 this.DOM.rotateBtn.on('click', this.events.callbacks.onRotateBtnClick); 397 }, 398 399 callbacks : { 400 onDOMchanges : function(){ 401 var that = this; 402 // 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 403 clearTimeout(this.observerTimeout); 404 that.observerTimeout = setTimeout( function(){ 405 var filtered = that.imageLinksFilter( that.selector.find(that.target) ), 406 activeIndex = 0, 407 isActiveUrl = false, 408 i; 409 410 // Make sure that ONLY DOM changes in the photobox number of items will trigger a change 411 if(that.imageLinks.length == filtered[0].length) 412 return; 413 414 that.imageLinks = filtered[0]; 415 that.images = filtered[1]; 416 417 // if photobox is opened 418 if( photobox ){ 419 // if gallery which was changed is the currently viewed one
419: 420 if( that.selector == photobox.selector ){ 421 images = that.images; 422 imageLinks = that.imageLinks; 423 // check if the currently VIEWED photo has been detached from a photobox set 424 // if so, remove navigation arrows 425 // TODO: fix the "images" to be an object and not an array. 426 for( i = images.length; i--; ){ 427 if( images[i][0] == activeURL ) 428 isActiveUrl = true; 429 // if not exits any more 430 } 431 // if( isActiveUrl ){ 432 // overlay.removeClass('hasArrows'); 433 // } 434 } 435 } 436 437 // if this gallery has thumbs 438 //if( that.options.thumbs ){ 439 that.thumbsList = thumbsStripe.generate.apply(that); 440 thumbs.html( that.thumbsList ); 441 //} 442 443 if( that.images.length && activeURL && that.options.thumbs ){ 444 activeIndex = that.thumbsList.find('a[href="'+activeURL+'"]').eq(0).parent().index(); 445 446 if( activeIndex == -1 ) 447 activeIndex = 0; 448 449 // updateIndexes(activeIndex); 450 thumbsStripe.changeActive(activeIndex, 0); 451 } 452 }, 50); 453 }, 454 455 onRotateBtnClick : function(){ 456 var rotation = image.data('rotation') || 0, // in "deg" 457 imgScale = image.data('zoom') || 1 458 459 rotation += 90; 460 461 image.removeClass('zoomable').addClass('rotating'); 462 463 image.css('transform', 'rotate('+ rotation +'deg) scale('+ imgScale + ')') 464 .data('rotation', rotation) 465 .on(transitionend, function(){ 466 image.addClass('zoomable').removeClass('rotating'); 467 }); 468 }, 469 470 onScrollZoom : function(e, deltaY, deltaX){ 471 if( deltaX ) return false; 472 473 var that = this; 474 475 if( activeType == 'video' ){ 476 var zoomLevel = video.data('zoom') || 1; 477 zoomLevel += (deltaY / 10); 478 if( zoomLevel < 0.5 ) 479 return false; 480 481 video.data('zoom', zoomLevel).css({width:624*zoomLevel, height:351*zoomLevel}); 482 } 483 else{ 484 raf(function() { 485 var zoomLevel = image.data('zoom') || 1, 486 rotation = image.data('rotation') || 0, 487 position = image.data('position') || '50% 50%', 488 boundingClientRect = image[0].getBoundingClientRect(), 489 value; 490 491 zoomLevel += (deltaY / 10); 492 493 if( zoomLevel < 0.1 ) 494 zoomLevel = 0.1; 495 496 image.data('zoom', zoomLevel); 497 498 value = 'scale('+ zoomLevel +') rotate('+ rotation +'deg)'; 499 500 // if the image was zoomed and now is larger than the window size, allow mouse movemenet reposition 501 if( boundingClientRect.height > docElm.clientHeight || boundingClientRect.width > docElm.clientWidth ){ 502 $doc.on('mousemove.photobox', that.events.callbacks.onMouseMoveimageReposition); 503 value += ' translate('+ position +')'; 504 } 505 else{ 506 $doc.off('mousemove.photobox'); 507 // image[0].style[transformOrigin] = '50% 50%'; 508 } 509 510 image.css({ 'transform':value }); 511 }); 512 } 513 return false;
514 }, 515 516 // moves the image around during zoom mode on mousemove event 517 onMouseMoveimageReposition : function(e){ 518 raf(function() { 519 var //y = (e.clientY / docElm.clientHeight) * (docElm.clientHeight + 200) - 100, // extend the range of the Y axis by 100 each side 520 sensitivity = 1.5, // 1 = same as mouse more, and higher value is less sensitive to mouse move 521 yDelta = (e.clientY / docElm.clientHeight * 100 - 50) / sensitivity, // subtract 50 because the real center is at "0%" 522 xDelta = (e.clientX / docElm.clientWidth * 100 - 50) / sensitivity, // subtract 50 because the real center is at "0%" 523 position, 524 rotationAngel = image.data('rotation') || 0, 525 rotation = (rotationAngel/90)%4 || 0, 526 imgScale = image.data('zoom') || 1; 527 528 if( rotation == 1 || rotation == 3 ) 529 position = yDelta.toFixed(2)+'%, ' + -xDelta.toFixed(2) +'%'; 530 else 531 position = xDelta.toFixed(2)+'%, ' + yDelta.toFixed(2) +'%'; 532 533 image.data('position', position); 534 535 // image[0].style[transformOrigin] = origin; 536 image[0].style.transform = 'rotate('+ rotationAngel +'deg) scale('+ imgScale + ') translate(' + position + ')'; 537 }); 538 } 539 } 540 } 541 } 542 543 // on touch-devices only 544 function onSwipe(e, Dx, Dy){ 545 if( Dx == 1 ){ 546 image.css({transform:'translateX(25%)', transition:'.2s', opacity:0}); 547 setTimeout(function(){ changeImage(prevImage) }, 200); 548 } 549 else if( Dx == -1 ){ 550 image.css({transform:'translateX(-25%)', transition:'.2s', opacity:0}); 551 setTimeout(function(){ changeImage(nextImage) }, 200); 552 } 553 554 if( Dy == 1 ) 555 thumbsToggler.prop('checked', true); 556 else if( Dy == -1 ) 557 thumbsToggler.prop('checked', false); 558 } 559 560 // manage the (bottom) thumbs strip 561 thumbsStripe = (function(){ 562 var containerWidth = 0, 563 scrollWidth = 0, 564 posFromLeft = 0, // Stripe position from the left of the screen 565 stripePos = 0, // When relative mouse position inside the thumbs stripe 566 animated = null, 567 padding, // in percentage to the containerWidth 568 el, $el, ratio, scrollPos, pos; 569 570 return{ 571 // returns a <ul> element which is populated with all the gallery links and thumbs 572 generate : function(){ 573 var thumbsList = $('<ul>'), 574 elements = [], 575 len = this.imageLinks.length, 576 title, thumbSrc, link, type, i; 577 578 for( i = 0; i < len; i++ ){ 579 link = this.imageLinks[i]; 580 581 thumbSrc = this.images[i][2]; 582 // continue if has thumb 583 if( !thumbSrc ) 584 continue; 585 586 title = this.images[i][1]; 587 type = link.rel ? " class='" + link.rel +"'" : ''; 588 elements.push('<li'+ type +'><a href="'+ link.href +'"><img src="'+ thumbSrc +'" alt="" title="'+ title +'" /></a></li>'); 589 }; 590 thumbsList.html( elements.join('') ); 591 return thumbsList; 592 }, 593 594 click : function(e){ 595 e.preventDefault(); 596 597 activeThumb.removeClass('active'); 598 activeThumb = $(this).parent().addClass('active'); 599 600 var imageIndex = $(this.parentNode).index(); 601 return changeImage(imageIndex, 0, 1); 602 }, 603 604 changeActiveTimeout : null, 605 /** Highlights the thumb which represents the photo and centres the thumbs viewer on it. 606 ** @thumbClick - if a user clicked on a thumbnail, don't center on it 607 */ 608 changeActive : function(index, delay, thumbClick){ 609 if( !options.thumbs ) 610 return; 611 612 var lastIndex = activeThumb.index(); 613 activeThumb.removeClass('active'); 614 activeThumb = thumbs.find('li').eq(index).addClass('active'); 615 616 if( thumbClick || !activeThumb[0] ) return; 617 // set the scrollLeft position of the thumbs list to show the active thumb 618 clearTimeout(this.changeActiveTimeout); 619 // give the images time to to settle on their new sizes (because of css transition) and then calculate the center... 620 this.changeActiveTimeout = setTimeout( 621 function(){ 622 var pos = activeThumb[0].offsetLeft + activeThumb[0].clientWidth/2 - docElm.clientWidth/2; 623 delay ? thumbs.delay(800) : thumbs.stop(); 624 thumbs.animate({scrollLeft: pos}, 500, 'swing'); 625 }, 200); 626 }, 627 628 // calculate the thumbs container width, if the window has been resized 629 calc : function(e){ 630 el = thumbs[0]; 631 632 containerWidth = el.clientWidth; 633 scrollWidth = el.scrollWidth; 634 padding = 0.15 * containerWidth; 635 636 posFromLeft = thumbs.offset().left;
637 stripePos = e.pageX - padding - posFromLeft; 638 pos = stripePos / (containerWidth - padding*2); 639 scrollPos = (scrollWidth - containerWidth ) * pos; 640 641 thumbs.animate({scrollLeft:scrollPos}, 200); 642 643 clearTimeout(animated); 644 animated = setTimeout(function(){ 645 animated = null; 646 }, 200); 647 648 return this; 649 }, 650 651 // move the stripe left or right according to mouse position 652 move : function(e){ 653 // don't move anything until initial movement on 'mouseenter' has finished 654 if( animated ) return; 655 656 var ratio = scrollWidth / containerWidth, 657 stripePos = e.pageX - padding - posFromLeft, // the mouse X position, "normalized" to the carousel position 658 pos, scrollPos; 659 660 if( stripePos < 0) stripePos = 0; // 661 662 pos = stripePos / (containerWidth - padding*2); // calculated position between 0 to 1 663 // calculate the percentage of the mouse position within the carousel 664 scrollPos = (scrollWidth - containerWidth ) * pos; 665 666 raf(function(){ 667 el.scrollLeft = scrollPos; 668 }); 669 } 670 } 671 })(); 672 673 // Autoplay controller 674 APControl = { 675 autoPlayTimer : false, 676 play : function(){ 677 APControl.autoPlayTimer = setTimeout(function(){ changeImage(nextImage) }, options.time); 678 APControl.progress.start(); 679 autoplayBtn.removeClass('play'); 680 APControl.setTitle('Click to stop autoplay'); 681 options.autoplay = true; 682 }, 683 pause : function(){ 684 clearTimeout(APControl.autoPlayTimer); 685 APControl.progress.reset(); 686 autoplayBtn.addClass('play'); 687 APControl.setTitle('Click to resume autoplay'); 688 options.autoplay = false; 689 }, 690 progress : { 691 reset : function(){ 692 autoplayBtn.find('div').removeAttr('style'); 693 setTimeout(function(){ autoplayBtn.removeClass('playing') },200); 694 }, 695 start : function(){ 696 if( !isOldIE) 697 autoplayBtn.find('div').css(transition, options.time+'ms'); 698 autoplayBtn.addClass('playing'); 699 } 700 }, 701 // sets the button Title property 702 setTitle : function(text){ 703 if(text) 704 autoplayBtn.prop('title', text + ' (every ' + options.time/1000 + ' seconds)' ); 705 }, 706 // the button onClick handler 707 toggle : function(e){ 708 e.stopPropagation(); 709 APControl[ options.autoplay ? 'pause' : 'play'](); 710 } 711 } 712 713 function getPrefixed(prop){ 714 var i, s = doc.createElement('p').style, v = ['ms','O','Moz','Webkit']; 715 if( s[prop] == '' ) return prop; 716 prop = prop.charAt(0).toUpperCase() + prop.slice(1); 717 for( i = v.length; i--; ) 718 if( s[v[i] + prop] == '' ) 719 return (v[i] + prop); 720 } 721 722 function keyDown(event){ 723 var code = event.keyCode, ok = options.keys, result; 724 // Prevent default keyboard action (like navigating inside the page) 725 return $.inArray(code, ok.close) >= 0 && close() || 726 $.inArray(code, ok.next) >= 0 && !options.single && loophole(nextImage) || 727 $.inArray(code, ok.prev) >= 0 && !options.single && loophole(prevImage) || true; 728 } 729 730 function wheelNextPrev(e, dY, dX){ 731 if( dX == 1 ) 732 loophole(nextImage); 733 else if( dX == -1 ) 734 loophole(prevImage); 735 } 736 737 738 // serves as a callback for pbPrevBtn / pbNextBtn buttons but also is called on keypress events 739 function next_prev(){ 740 // don't get crazy when user clicks next or prev buttons rapidly 741 //if( !image.hasClass('zoomable') ) 742 // return false; 743 744 var idx = (this.id == 'pbPrevBtn') ? prevImage : nextImage; 745 746 loophole(idx); 747 return false;
748 } 749 750 function updateIndexes(idx){ 751 lastActive = activeImage; 752 activeImage = idx; 753 activeURL = images[idx][0]; 754 prevImage = (activeImage || (options.loop ? images.length : 0)) - 1; 755 nextImage = ((activeImage + 1) % images.length) || (options.loop ? 0 : -1); 756 } 757 758 // check if looping is allowed before changing image/video. 759 // A pre-changeImage function, only for linear changes 760 function loophole(idx){ 761 if( !options.loop ){ 762 var afterLast = activeImage == images.length-1 && idx == nextImage, 763 beforeFirst = activeImage == 0 && idx == prevImage; 764 765 if( afterLast || beforeFirst ) 766 return; 767 } 768 769 changeImage(idx); 770 } 771 772 changeImage = (function(){ 773 var timer; 774 775 return function(imageIndex, firstTime, thumbClick){ 776 // throttle mechanism 777 if( timer ) 778 return; 779 780 timer = setTimeout(function(){ 781 timer = null; 782 }, 150); 783 784 $doc.off('mousemove.photobox'); 785 786 if( !imageIndex || imageIndex < 0 ) 787 imageIndex = 0; 788 789 // hide/show next-prev buttons 790 if( !options.loop ){ 791 //nextBtn[ imageIndex == images.length-1 ? 'addClass' : 'removeClass' ]('pbHide'); 792 nextBtn.toggleClass('pbHide', imageIndex == images.length-1); 793 //prevBtn[ imageIndex == 0 ? 'addClass' : 'removeClass' ]('pbHide'); 794 prevBtn.toggleClass('pbHide', imageIndex == 0); 795 } 796 797 // if there's a callback for this point: 798 if( typeof options.beforeShow == "function") 799 options.beforeShow(imageLinks[imageIndex]); 800 801 overlay.removeClass('error'); 802 803 if( activeImage >= 0 ) 804 overlay.addClass( imageIndex > activeImage ? 'next' : 'prev' ); 805 806 updateIndexes(imageIndex); 807 808 // reset things 809 stop(); 810 video.empty(); 811 preload.onerror = null; 812 image.add(video).data('zoom', 1); 813 814 activeType = imageLinks[imageIndex].rel == 'video' ? 'video' : 'image'; 815 816 // check if current link is a video 817 if( activeType == 'video' ){ 818 video.html( newVideo() ).addClass('pbHide'); 819 showContent(firstTime); 820 } 821 else{ 822 // give a tiny delay to the preloader, so it won't be showed when images load very quickly 823 var loaderTimeout = setTimeout(function(){ overlay.addClass('pbLoading'); }, 50); 824 825 if( isOldIE ) overlay.addClass('pbHide'); // should wait for the image onload. just hide the image while old IE display the preloader 826 827 options.autoplay && APControl.progress.reset(); 828 preload = new Image(); 829 preload.onload = function(){ 830 preload.onload = null; 831 832 if( prevImage >= 0 ) preloadPrev.src = images[prevImage][0]; 833 if( nextImage >= 0 ) preloadNext.src = images[nextImage][0]; 834 835 clearTimeout(loaderTimeout); 836 showContent(firstTime); 837 }; 838 preload.onerror = imageError; 839 preload.src = activeURL; 840 } 841 842 // Show Caption text 843 captionText.on(transitionend, captionTextChange).addClass('change'); 844 if( firstTime || isOldIE ) captionTextChange(); 845 846 847 thumbsStripe.changeActive(imageIndex, firstTime, thumbClick); 848 // Save url hash for current image 849 history.save(); 850 } 851 })(); 852 853 function newVideo(){ 854 var url = images[activeImage][0], 855 sign = $('<a>').prop('href',images[activeImage][0])[0].search ? '&' : '?'; 856 url += sign + 'vq=hd720&wmode=opaque';
857 return $("<iframe>").prop({ scrolling:'no', frameborder:0, allowTransparency:true, src:url }).attr({webkitAllowFullScreen:true, mozallowfullscreen:true, allowFullScreen:true}); 858 } 859 860 // show the item's Title & Counter 861 function captionTextChange(){ 862 var tmpl; 863 captionText.off(transitionend).removeClass('change'); 864 865 if( options.captionTmpl ){ 866 tmpl = options.captionTmpl 867 .replace(/{title}/g, images[activeImage][1]) 868 .replace(/{currentImageIdx}/g, activeImage + 1) 869 .replace(/{totalImagesCount}/g, images.length); 870 871 captionText.html(tmpl); 872 } 873 } 874 875 // Handles the history states when changing images 876 var history = { 877 save : function(){ 878 // only save to history urls which are not already in the hash 879 if('pushState' in window.history && decodeURIComponent(window.location.hash.slice(1)) != activeURL && options.history ){ 880 window.history.pushState( 'photobox', doc.title + '-' + images[activeImage][1], window.location.pathname + window.location.search + '#' + encodeURIComponent(activeURL) ); 881 } 882 }, 883 load : function(){ 884 if( options && !options.history ) return false; 885 var hash = decodeURIComponent( window.location.hash.slice(1) ), i, j; 886 if( !hash && overlay.hasClass('show') ) 887 close(); 888 889 $('a[href="' + hash + '"]').trigger('click.photobox'); 890 }, 891 clear : function(){ 892 if( options.history && 'pushState' in window.history ) 893 window.history.pushState('photobox', doc.title, window.location.pathname + window.location.search); 894 } 895 }; 896 897 // Add Photobox special `onpopstate` to the `onpopstate` function 898 window.onpopstate = (function(){ 899 var cached = window.onpopstate; 900 return function(event){ 901 cached && cached.apply(this, arguments); 902 if( event.state == 'photobox' ) 903 history.load(); 904 } 905 })(); 906 907 // handles all image loading error (if image is dead) 908 function imageError(){ 909 overlay.addClass('error'); 910 image[0].src = blankImg; // set the source to a blank image 911 preload.onerror = null; 912 } 913 914 // Shows the content (image/video) on the screen 915 function showContent(firstTime){ 916 var out, showSaftyTimer; 917 showSaftyTimer = setTimeout(show, 2000); 918 919 // hides the current image and prepare ground for an image change 920 pbLoader.fadeOut(300, function(){ 921 overlay.removeClass("pbLoading"); 922 pbLoader.removeAttr('style'); 923 }); 924 overlay.addClass('pbHide'); 925 926 image.add(video).removeClass('zoomable'); // while transitioning an image, do not apply the 'zoomable' class 927 928 // check which element needs to transition-out: 929 if( !firstTime && imageLinks[lastActive].rel == 'video' ){ 930 out = video; 931 image.addClass('prepare'); 932 } 933 else 934 out = image; 935 936 if( firstTime || isOldIE ) 937 show(); 938 else 939 out.on(transitionend, show); 940 941 // in case the 'transitionend' didn't fire 942 // after hiding the last seen image, show the new one 943 function show(){ 944 wrapper.removeAttr('style'); 945 clearTimeout(showSaftyTimer); 946 overlay.removeClass('video'); 947 948 if( activeType == 'video' ){ 949 image.attr('src', blankImg); 950 out.off(transitionend).css({'transition':'none'}); 951 video.addClass('prepare'); 952 overlay.addClass('video'); 953 } 954 else{ 955 // create a new image element (cannot modify the same image SRC due to google chrome stupid bugs showing the previous image even after the SRC had changed) 956 image.replaceWith( image = $('<img src="'+ activeURL +'" class="prepare">') ) 957 } 958 959 // filthy hack for the transitionend event, but cannot work without it: 960 setTimeout(function(){ 961 overlay.removeClass('pbHide next prev'); 962 image.add(video).removeAttr('style').removeClass('prepare').on(transitionend, showDone); 963 if(isOldIE) showDone(); 964 }, 50); 965 } 966 } 967 968 // a callback whenever a transition of an image or a video is done 969 function showDone(){ 970 image.add(video).off(transitionend).addClass('zoomable'); 971 if( activeType == 'video' ) 972 video.removeClass('pbHide'); 973 else{ 974 autoplayBtn && options.autoplay && APControl.play(); 975 } 976 if( photobox && typeof photobox.callback == 'function' ){ 977 photobox.callback(imageLinks[activeImage]); 978 } 979 } 980 981 982 function thumbsResize(e, delta){ 983 e.preventDefault(); 984 e.stopPropagation(); // stop the event from bubbling up to the Overlay and enlarge the content itself 985 var thumbList = photobox.thumbsList, h; 986 thumbList.css('height', thumbList[0].clientHeight + (delta * 10) ); 987 h = caption[0].clientHeight / 2; 988 wrapper[0].style.cssText = "margin-top: -"+ h +"px; padding: "+ h +"px 0;"; 989 thumbs.hide().show(0); 990 //thumbs.trigger('mouseenter').trigger('mousemove'); 991 } 992 993 function stop(){ 994 clearTimeout(APControl.autoPlayTimer); 995 $doc.off('mousemove.photobox'); 996 preload.onload = function(){}; 997 preload.src = preloadPrev.src = preloadNext.src = activeURL; 998 } 999 1000 function close(){ 1001 if( !overlay.hasClass('show') ) 1002 return false;
1003 1004 stop(); 1005 video.find('iframe').prop('src','').empty(); 1006 Photobox.prototype.setup(); 1007 history.clear(); 1008 1009 overlay.removeClass('on video').addClass('pbHide'); 1010 activeImage = -1; 1011 1012 image.on(transitionend, hide); 1013 isOldIE && hide(); 1014 1015 // the "photobox" instance might be needed for async transitionEnd functions, so give it some time before clearing it 1016 setTimeout(function(){ 1017 photobox = null; 1018 },1000); 1019 1020 function hide(){ 1021 if( overlay[0].className == '' ) return; // if already hidden 1022 overlay.removeClass('show pbHide error pbLoading'); 1023 image.removeAttr('class').removeAttr('style').off().data('zoom',1); 1024 // a hack to change the image src to nothing, because you can't do that in CHROME 1025 image[0].src = blankImg; 1026 1027 caption.find('.title').empty(); 1028 1029 if(noPointerEvents) // pointer-events lack support in IE, so just hide the overlay 1030 setTimeout(function(){ overlay.hide(); }, 200); 1031 1032 options.hideFlash && $('iframe, object, embed').css('visibility', 'visible'); 1033 } 1034 1035 // fall-back if the 'transitionend' event didn't fire 1036 setTimeout(hide, 500); 1037 // callback after closing the gallery 1038 if( typeof options.afterClose === 'function' ) 1039 options.afterClose(overlay); 1040 } 1041 1042 1043 /** 1044 * jQuery Plugin to add basic "swipe" support on touch-enabled devices 1045 * 1046 * @author Yair Even Or 1047 * @version 1.0.0 (March 20, 2013) 1048 */ 1049 $.event.special.swipe = { 1050 setup: function(){ 1051 $(this).bind('touchstart', $.event.special.swipe.handler); 1052 }, 1053 1054 teardown: function(){ 1055 $(this).unbind('touchstart', $.event.special.swipe.handler); 1056 }, 1057 1058 handler: function(event){ 1059 var args = [].slice.call( arguments, 1 ), // clone arguments array, remove original event from cloned array 1060 touches = event.originalEvent.touches, 1061 startX, startY, 1062 deltaX = 0, deltaY = 0, 1063 that = this; 1064 1065 event = $.event.fix(event); 1066 1067 if( touches.length == 1 ){ 1068 startX = touches[0].pageX; 1069 startY = touches[0].pageY; 1070 this.addEventListener('touchmove', onTouchMove, false); 1071 } 1072 1073 function cancelTouch(){ 1074 that.removeEventListener('touchmove', onTouchMove); 1075 startX = startY = null; 1076 } 1077 1078 function onTouchMove(e){ 1079 e.preventDefault(); 1080 1081 var Dx = startX - e.touches[0].pageX, 1082 Dy = startY - e.touches[0].pageY; 1083 1084 if( Math.abs(Dx) >= 20 ){ 1085 cancelTouch(); 1086 deltaX = (Dx > 0) ? -1 : 1; 1087 } 1088 else if( Math.abs(Dy) >= 20 ){ 1089 cancelTouch(); 1090 deltaY = (Dy > 0) ? 1 : -1; 1091 } 1092 1093 event.type = 'swipe'; 1094 args.unshift(event, deltaX, deltaY); // add back the new event to the front of the arguments with the delatas 1095 return ($.event.dispatch || $.event.handle).apply(that, args); 1096 } 1097 } 1098 }; 1099 1100 /* MouseWheel plugin 1101 * ! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh) 1102 * Licensed under the MIT License (LICENSE.txt). 1103 * 1104 * Version: 3.1.11 1105 * 1106 * Requires: jQuery 1.2.2+ 1107 */ 1108 !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>
vendor: 2,025 bytes, lines 1108-1119
1108n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.11",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b)["offsetParent"in a.fn?"offsetParent":"parent"]();return c.length||(c=a("body")),parseInt(c.css("fontSize"),10)},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}); 1109 1110 // Expose: 1111 window._photobox = { 1112 DOM : { 1113 overlay : overlay 1114 }, 1115 close : close, 1116 history : history, 1117 defaults : defaults 1118 }; 1119})(jQuery, document, window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.