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