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