jquery.fancybox.js (154104B)
1 // ================================================== 2 // fancyBox v3.2.10 3 // 4 // Licensed GPLv3 for open source use 5 // or fancyBox Commercial License for commercial use 6 // 7 // http://fancyapps.com/fancybox/ 8 // Copyright 2017 fancyApps 9 // 10 // ================================================== 11 ;(function (window, document, $, undefined) { 12 'use strict'; 13 14 // If there's no jQuery, fancyBox can't work 15 // ========================================= 16 17 if ( !$ ) { 18 return; 19 } 20 21 // Check if fancyBox is already initialized 22 // ======================================== 23 24 if ( $.fn.fancybox ) { 25 26 if ( 'console' in window ) { 27 console.log( 'fancyBox already initialized' ); 28 } 29 30 return; 31 } 32 33 // Private default settings 34 // ======================== 35 36 var defaults = { 37 38 // Enable infinite gallery navigation 39 loop : false, 40 41 // Space around image, ignored if zoomed-in or viewport width is smaller than 800px 42 margin : [44, 0], 43 44 // Horizontal space between slides 45 gutter : 50, 46 47 // Enable keyboard navigation 48 keyboard : true, 49 50 // Should display navigation arrows at the screen edges 51 arrows : true, 52 53 // Should display infobar (counter and arrows at the top) 54 infobar : true, 55 56 // Should display toolbar (buttons at the top) 57 toolbar : true, 58 59 // What buttons should appear in the top right corner. 60 // Buttons will be created using templates from `btnTpl` option 61 // and they will be placed into toolbar (class="fancybox-toolbar"` element) 62 buttons : [ 63 'slideShow', 64 'fullScreen', 65 'thumbs', 66 'share', 67 //'download', 68 //'zoom', 69 'close' 70 ], 71 72 // Detect "idle" time in seconds 73 idleTime : 3, 74 75 // Should display buttons at top right corner of the content 76 // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax' 77 // Use template from `btnTpl.smallBtn` for customization 78 smallBtn : 'auto', 79 80 // Disable right-click and use simple image protection for images 81 protect : false, 82 83 // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc 84 modal : false, 85 86 image : { 87 88 // Wait for images to load before displaying 89 // Requires predefined image dimensions 90 // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found 91 preload : "auto" 92 93 }, 94 95 ajax : { 96 97 // Object containing settings for ajax request 98 settings : { 99 100 // This helps to indicate that request comes from the modal 101 // Feel free to change naming 102 data : { 103 fancybox : true 104 } 105 } 106 107 }, 108 109 iframe : { 110 111 // Iframe template 112 tpl : '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen allowtransparency="true" src=""></iframe>', 113 114 // Preload iframe before displaying it 115 // This allows to calculate iframe content width and height 116 // (note: Due to "Same Origin Policy", you can't get cross domain data). 117 preload : true, 118 119 // Custom CSS styling for iframe wrapping element 120 // You can use this to set custom iframe dimensions 121 css : {}, 122 123 // Iframe tag attributes 124 attr : { 125 scrolling : 'auto' 126 } 127 128 }, 129 130 // Default content type if cannot be detected automatically 131 defaultType : 'image', 132 133 // Open/close animation type 134 // Possible values: 135 // false - disable 136 // "zoom" - zoom images from/to thumbnail 137 // "fade" 138 // "zoom-in-out" 139 // 140 animationEffect : "zoom", 141 142 // Duration in ms for open/close animation 143 animationDuration : 500, 144 145 // Should image change opacity while zooming 146 // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios 147 zoomOpacity : "auto", 148 149 // Transition effect between slides 150 // 151 // Possible values: 152 // false - disable 153 // "fade' 154 // "slide' 155 // "circular' 156 // "tube' 157 // "zoom-in-out' 158 // "rotate' 159 // 160 transitionEffect : "fade", 161 162 // Duration in ms for transition animation 163 transitionDuration : 366, 164 165 // Custom CSS class for slide element 166 slideClass : '', 167 168 // Custom CSS class for layout 169 baseClass : '', 170 171 // Base template for layout 172 baseTpl : 173 '<div class="fancybox-container" role="dialog" tabindex="-1">' + 174 '<div class="fancybox-bg"></div>' + 175 '<div class="fancybox-inner">' + 176 '<div class="fancybox-infobar">' + 177 '<span data-fancybox-index></span> / <span data-fancybox-count></span>' + 178 '</div>' + 179 '<div class="fancybox-toolbar">{{buttons}}</div>' + 180 '<div class="fancybox-navigation">{{arrows}}</div>' + 181 '<div class="fancybox-stage"></div>' + 182 '<div class="fancybox-caption-wrap"><div class="fancybox-caption"></div></div>' + 183 '</div>' + 184 '</div>', 185 186 // Loading indicator template 187 spinnerTpl : '<div class="fancybox-loading"></div>', 188 189 // Error message template 190 errorTpl : '<div class="fancybox-error"><p>{{ERROR}}<p></div>', 191 192 btnTpl : { 193 194 download : '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}">' + 195 '<svg viewBox="0 0 40 40">' + 196 '<path d="M20,23 L20,8 L20,23 L13,16 L20,23 L27,16 L20,23 M26,28 L13,28 L27,28 L14,28" />' + 197 '</svg>' + 198 '</a>', 199 200 zoom : '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' + 201 '<svg viewBox="0 0 40 40">' + 202 '<path d="M 18,17 m-8,0 a 8,8 0 1,0 16,0 a 8,8 0 1,0 -16,0 M25,23 L31,29 L25,23" />' + 203 '</svg>' + 204 '</button>', 205 206 close : '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' + 207 '<svg viewBox="0 0 40 40">' + 208 '<path d="M10,10 L30,30 M30,10 L10,30" />' + 209 '</svg>' + 210 '</button>', 211 212 // This small close button will be appended to your html/inline/ajax content by default, 213 // if "smallBtn" option is not set to false 214 smallBtn : '<button data-fancybox-close class="fancybox-close-small" title="{{CLOSE}}"></button>', 215 216 // Arrows 217 arrowLeft : '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' + 218 '<svg viewBox="0 0 40 40">' + 219 '<path d="M10,20 L30,20 L10,20 L18,28 L10,20 L18,12 L10,20"></path>' + 220 '</svg>' + 221 '</button>', 222 223 arrowRight : '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' + 224 '<svg viewBox="0 0 40 40">' + 225 '<path d="M30,20 L10,20 L30,20 L22,28 L30,20 L22,12 L30,20"></path>' + 226 '</svg>' + 227 '</button>' 228 }, 229 230 // Container is injected into this element 231 parentEl : 'body', 232 233 234 // Focus handling 235 // ============== 236 237 // Try to focus on the first focusable element after opening 238 autoFocus : false, 239 240 // Put focus back to active element after closing 241 backFocus : true, 242 243 // Do not let user to focus on element outside modal content 244 trapFocus : true, 245 246 247 // Module specific options 248 // ======================= 249 250 fullScreen : { 251 autoStart : false, 252 }, 253 254 // Set `touch: false` to disable dragging/swiping 255 touch : { 256 vertical : true, // Allow to drag content vertically 257 momentum : true // Continue movement after releasing mouse/touch when panning 258 }, 259 260 // Hash value when initializing manually, 261 // set `false` to disable hash change 262 hash : null, 263 264 // Customize or add new media types 265 // Example: 266 /* 267 media : { 268 youtube : { 269 params : { 270 autoplay : 0 271 } 272 } 273 } 274 */ 275 media : {}, 276 277 slideShow : { 278 autoStart : false, 279 speed : 4000 280 }, 281 282 thumbs : { 283 autoStart : false, // Display thumbnails on opening 284 hideOnClose : true, // Hide thumbnail grid when closing animation starts 285 parentEl : '.fancybox-container', // Container is injected into this element 286 axis : 'y' // Vertical (y) or horizontal (x) scrolling 287 }, 288 289 // Use mousewheel to navigate gallery 290 // If 'auto' - enabled for images only 291 wheel : 'auto', 292 293 // Callbacks 294 //========== 295 296 // See Documentation/API/Events for more information 297 // Example: 298 /* 299 afterShow: function( instance, current ) { 300 console.info( 'Clicked element:' ); 301 console.info( current.opts.$orig ); 302 } 303 */ 304 305 onInit : $.noop, // When instance has been initialized 306 307 beforeLoad : $.noop, // Before the content of a slide is being loaded 308 afterLoad : $.noop, // When the content of a slide is done loading 309 310 beforeShow : $.noop, // Before open animation starts 311 afterShow : $.noop, // When content is done loading and animating 312 313 beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close. 314 afterClose : $.noop, // After instance has been closed 315 316 onActivate : $.noop, // When instance is brought to front 317 onDeactivate : $.noop, // When other instance has been activated 318 319 320 // Interaction 321 // =========== 322 323 // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, 324 // each option can be string or method that returns value. 325 // 326 // Possible values: 327 // "close" - close instance 328 // "next" - move to next gallery item 329 // "nextOrClose" - move to next gallery item or close if gallery has only one item 330 // "toggleControls" - show/hide controls 331 // "zoom" - zoom image (if loaded) 332 // false - do nothing 333 334 // Clicked on the content 335 clickContent : function( current, event ) { 336 return current.type === 'image' ? 'zoom' : false; 337 }, 338 339 // Clicked on the slide 340 clickSlide : 'close', 341 342 // Clicked on the background (backdrop) element 343 clickOutside : 'close', 344 345 // Same as previous two, but for double click 346 dblclickContent : false, 347 dblclickSlide : false, 348 dblclickOutside : false, 349 350 351 // Custom options when mobile device is detected 352 // ============================================= 353 354 mobile : { 355 idleTime : false, 356 margin : 0, 357 358 clickContent : function( current, event ) { 359 return current.type === 'image' ? 'toggleControls' : false; 360 }, 361 clickSlide : function( current, event ) { 362 return current.type === 'image' ? 'toggleControls' : 'close'; 363 }, 364 dblclickContent : function( current, event ) { 365 return current.type === 'image' ? 'zoom' : false; 366 }, 367 dblclickSlide : function( current, event ) { 368 return current.type === 'image' ? 'zoom' : false; 369 } 370 }, 371 372 373 // Internationalization 374 // ============ 375 376 lang : 'en', 377 i18n : { 378 'en' : { 379 CLOSE : 'Close', 380 NEXT : 'Next', 381 PREV : 'Previous', 382 ERROR : 'The requested content cannot be loaded. <br/> Please try again later.', 383 PLAY_START : 'Start slideshow', 384 PLAY_STOP : 'Pause slideshow', 385 FULL_SCREEN : 'Full screen', 386 THUMBS : 'Thumbnails', 387 DOWNLOAD : 'Download', 388 SHARE : 'Share', 389 ZOOM : 'Zoom' 390 }, 391 'de' : { 392 CLOSE : 'Schliessen', 393 NEXT : 'Weiter', 394 PREV : 'Zurück', 395 ERROR : 'Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es später nochmal.', 396 PLAY_START : 'Diaschau starten', 397 PLAY_STOP : 'Diaschau beenden', 398 FULL_SCREEN : 'Vollbild', 399 THUMBS : 'Vorschaubilder', 400 DOWNLOAD : 'Herunterladen', 401 SHARE : 'Teilen', 402 ZOOM : 'Maßstab' 403 } 404 } 405 406 }; 407 408 // Few useful variables and methods 409 // ================================ 410 411 var $W = $(window); 412 var $D = $(document); 413 414 var called = 0; 415 416 417 // Check if an object is a jQuery object and not a native JavaScript object 418 // ======================================================================== 419 420 var isQuery = function ( obj ) { 421 return obj && obj.hasOwnProperty && obj instanceof $; 422 }; 423 424 425 // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" 426 // =============================================================================== 427 428 var requestAFrame = (function () { 429 return window.requestAnimationFrame || 430 window.webkitRequestAnimationFrame || 431 window.mozRequestAnimationFrame || 432 window.oRequestAnimationFrame || 433 // if all else fails, use setTimeout 434 function (callback) { 435 return window.setTimeout(callback, 1000 / 60); 436 }; 437 })(); 438 439 440 // Detect the supported transition-end event property name 441 // ======================================================= 442 443 var transitionEnd = (function () { 444 var t, el = document.createElement("fakeelement"); 445 446 var transitions = { 447 "transition" : "transitionend", 448 "OTransition" : "oTransitionEnd", 449 "MozTransition" : "transitionend", 450 "WebkitTransition": "webkitTransitionEnd" 451 }; 452 453 for (t in transitions) { 454 if (el.style[t] !== undefined){ 455 return transitions[t]; 456 } 457 } 458 459 return 'transitionend'; 460 })(); 461 462 463 // Force redraw on an element. 464 // This helps in cases where the browser doesn't redraw an updated element properly. 465 // ================================================================================= 466 467 var forceRedraw = function( $el ) { 468 return ( $el && $el.length && $el[0].offsetHeight ); 469 }; 470 471 472 // Class definition 473 // ================ 474 475 var FancyBox = function( content, opts, index ) { 476 var self = this; 477 478 self.opts = $.extend( true, { index : index }, $.fancybox.defaults, opts || {} ); 479 480 if ( $.fancybox.isMobile ) { 481 self.opts = $.extend( true, {}, self.opts, self.opts.mobile ); 482 } 483 484 // Exclude buttons option from deep merging 485 if ( opts && $.isArray( opts.buttons ) ) { 486 self.opts.buttons = opts.buttons; 487 } 488 489 self.id = self.opts.id || ++called; 490 self.group = []; 491 492 self.currIndex = parseInt( self.opts.index, 10 ) || 0; 493 self.prevIndex = null; 494 495 self.prevPos = null; 496 self.currPos = 0; 497 498 self.firstRun = null; 499 500 // Create group elements from original item collection 501 self.createGroup( content ); 502 503 if ( !self.group.length ) { 504 return; 505 } 506 507 // Save last active element and current scroll position 508 self.$lastFocus = $(document.activeElement).blur(); 509 510 // Collection of gallery objects 511 self.slides = {}; 512 513 self.init(); 514 }; 515 516 $.extend(FancyBox.prototype, { 517 518 // Create DOM structure 519 // ==================== 520 521 init : function() { 522 var self = this, 523 firstItem = self.group[ self.currIndex ], 524 firstItemOpts = firstItem.opts, 525 scrollbarWidth = $.fancybox.scrollbarWidth, 526 $scrollDiv, 527 $container, 528 buttonStr; 529 530 self.scrollTop = $D.scrollTop(); 531 self.scrollLeft = $D.scrollLeft(); 532 533 534 // Hide scrollbars 535 // =============== 536 537 if ( !$.fancybox.getInstance() ) { 538 539 $( 'body' ).addClass( 'fancybox-active' ); 540 541 // iOS hack 542 if ( /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream ) { 543 544 // iOS has problems for input elements inside fixed containers, 545 // the workaround is to apply `position: fixed` to `<body>` element, 546 // unfortunately, this makes it lose the scrollbars and forces address bar to appear. 547 548 if ( firstItem.type !== 'image' ) { 549 $( 'body' ).css( 'top', $( 'body' ).scrollTop() * -1 ).addClass( 'fancybox-iosfix' ); 550 } 551 552 } else if ( !$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight ) { 553 554 if ( scrollbarWidth === undefined ) { 555 $scrollDiv = $('<div style="width:50px;height:50px;overflow:scroll;" />').appendTo( 'body' ); 556 557 scrollbarWidth = $.fancybox.scrollbarWidth = $scrollDiv[0].offsetWidth - $scrollDiv[0].clientWidth; 558 559 $scrollDiv.remove(); 560 } 561 562 $( 'head' ).append( '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar { margin-right: ' + scrollbarWidth + 'px; }</style>' ); 563 $( 'body' ).addClass( 'compensate-for-scrollbar' ); 564 } 565 } 566 567 568 // Build html markup and set references 569 // ==================================== 570 571 // Build html code for buttons and insert into main template 572 buttonStr = ''; 573 574 $.each( firstItemOpts.buttons, function( index, value ) { 575 buttonStr += ( firstItemOpts.btnTpl[ value ] || '' ); 576 }); 577 578 // Create markup from base template, it will be initially hidden to 579 // avoid unnecessary work like painting while initializing is not complete 580 $container = $( 581 self.translate( self, 582 firstItemOpts.baseTpl 583 .replace( '\{\{buttons\}\}', buttonStr ) 584 .replace( '\{\{arrows\}\}', firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight ) 585 ) 586 ) 587 .attr( 'id', 'fancybox-container-' + self.id ) 588 .addClass( 'fancybox-is-hidden' ) 589 .addClass( firstItemOpts.baseClass ) 590 .data( 'FancyBox', self ) 591 .appendTo( firstItemOpts.parentEl ); 592 593 // Create object holding references to jQuery wrapped nodes 594 self.$refs = { 595 container : $container 596 }; 597 598 [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption', 'navigation' ].forEach(function(item) { 599 self.$refs[ item ] = $container.find( '.fancybox-' + item ); 600 }); 601 602 self.trigger( 'onInit' ); 603 604 // Enable events, deactive previous instances 605 self.activate(); 606 607 // Build slides, load and reveal content 608 self.jumpTo( self.currIndex ); 609 }, 610 611 612 // Simple i18n support - replaces object keys found in template 613 // with corresponding values 614 // ============================================================ 615 616 translate : function( obj, str ) { 617 var arr = obj.opts.i18n[ obj.opts.lang ]; 618 619 return str.replace(/\{\{(\w+)\}\}/g, function(match, n) { 620 var value = arr[n]; 621 622 if ( value === undefined ) { 623 return match; 624 } 625 626 return value; 627 }); 628 }, 629 630 // Create array of gally item objects 631 // Check if each object has valid type and content 632 // =============================================== 633 634 createGroup : function ( content ) { 635 var self = this; 636 var items = $.makeArray( content ); 637 638 $.each(items, function( i, item ) { 639 var obj = {}, 640 opts = {}, 641 $item, 642 type, 643 found, 644 src, 645 srcParts; 646 647 // Step 1 - Make sure we have an object 648 // ==================================== 649 650 if ( $.isPlainObject( item ) ) { 651 652 // We probably have manual usage here, something like 653 // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) 654 655 obj = item; 656 opts = item.opts || item; 657 658 } else if ( $.type( item ) === 'object' && $( item ).length ) { 659 660 // Here we probably have jQuery collection returned by some selector 661 $item = $( item ); 662 663 opts = $item.data(); 664 opts = $.extend( {}, opts, opts.options || {} ); 665 666 // Here we store clicked element 667 opts.$orig = $item; 668 669 obj.src = opts.src || $item.attr( 'href' ); 670 671 // Assume that simple syntax is used, for example: 672 // `$.fancybox.open( $("#test"), {} );` 673 if ( !obj.type && !obj.src ) { 674 obj.type = 'inline'; 675 obj.src = item; 676 } 677 678 } else { 679 680 // Assume we have a simple html code, for example: 681 // $.fancybox.open( '<div><h1>Hi!</h1></div>' ); 682 683 obj = { 684 type : 'html', 685 src : item + '' 686 }; 687 688 } 689 690 // Each gallery object has full collection of options 691 obj.opts = $.extend( true, {}, self.opts, opts ); 692 693 // Do not merge buttons array 694 if ( $.isArray( opts.buttons ) ) { 695 obj.opts.buttons = opts.buttons; 696 } 697 698 699 // Step 2 - Make sure we have content type, if not - try to guess 700 // ============================================================== 701 702 type = obj.type || obj.opts.type; 703 src = obj.src || ''; 704 705 if ( !type && src ) { 706 if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) { 707 type = 'image'; 708 709 } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) { 710 type = 'pdf'; 711 712 } else if ( found = src.match(/\.(mp4|mov|ogv)((\?|#).*)?$/i) ) { 713 type = 'video'; 714 715 if ( !obj.opts.videoFormat ) { 716 obj.opts.videoFormat = 'video/' + ( found[1] === 'ogv' ? 'ogg' : found[1] ); 717 } 718 719 } else if ( src.charAt(0) === '#' ) { 720 type = 'inline'; 721 } 722 } 723 724 if ( type ) { 725 obj.type = type; 726 727 } else { 728 self.trigger( 'objectNeedsType', obj ); 729 } 730 731 732 // Step 3 - Some adjustments 733 // ========================= 734 735 obj.index = self.group.length; 736 737 // Check if $orig and $thumb objects exist 738 if ( obj.opts.$orig && !obj.opts.$orig.length ) { 739 delete obj.opts.$orig; 740 } 741 742 if ( !obj.opts.$thumb && obj.opts.$orig ) { 743 obj.opts.$thumb = obj.opts.$orig.find( 'img:first' ); 744 } 745 746 if ( obj.opts.$thumb && !obj.opts.$thumb.length ) { 747 delete obj.opts.$thumb; 748 } 749 750 // "caption" is a "special" option, it can be used to customize caption per gallery item .. 751 if ( $.type( obj.opts.caption ) === 'function' ) { 752 obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] ); 753 } 754 755 if ( $.type( self.opts.caption ) === 'function' ) { 756 obj.opts.caption = self.opts.caption.apply( item, [ self, obj ] ); 757 } 758 759 // Make sure we have caption as a string or jQuery object 760 if ( !( obj.opts.caption instanceof $ ) ) { 761 obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + ''; 762 } 763 764 // Check if url contains "filter" used to filter the content 765 // Example: "ajax.html #something" 766 if ( type === 'ajax' ) { 767 srcParts = src.split(/\s+/, 2); 768 769 if ( srcParts.length > 1 ) { 770 obj.src = srcParts.shift(); 771 772 obj.opts.filter = srcParts.shift(); 773 } 774 } 775 776 if ( obj.opts.smallBtn == 'auto' ) { 777 778 if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) { 779 obj.opts.toolbar = false; 780 obj.opts.smallBtn = true; 781 782 } else { 783 obj.opts.smallBtn = false; 784 } 785 786 } 787 788 // If the type is "pdf", then simply load file into iframe 789 if ( type === 'pdf' ) { 790 obj.type = 'iframe'; 791 792 obj.opts.iframe.preload = false; 793 } 794 795 // Hide all buttons and disable interactivity for modal items 796 if ( obj.opts.modal ) { 797 798 obj.opts = $.extend(true, obj.opts, { 799 // Remove buttons 800 infobar : 0, 801 toolbar : 0, 802 803 smallBtn : 0, 804 805 // Disable keyboard navigation 806 keyboard : 0, 807 808 // Disable some modules 809 slideShow : 0, 810 fullScreen : 0, 811 thumbs : 0, 812 touch : 0, 813 814 // Disable click event handlers 815 clickContent : false, 816 clickSlide : false, 817 clickOutside : false, 818 dblclickContent : false, 819 dblclickSlide : false, 820 dblclickOutside : false 821 }); 822 823 } 824 825 // Step 4 - Add processed object to group 826 // ====================================== 827 828 self.group.push( obj ); 829 830 }); 831 832 }, 833 834 835 // Attach an event handler functions for: 836 // - navigation buttons 837 // - browser scrolling, resizing; 838 // - focusing 839 // - keyboard 840 // - detect idle 841 // ====================================== 842 843 addEvents : function() { 844 var self = this; 845 846 self.removeEvents(); 847 848 // Make navigation elements clickable 849 self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) { 850 e.stopPropagation(); 851 e.preventDefault(); 852 853 self.close( e ); 854 855 }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) { 856 e.stopPropagation(); 857 e.preventDefault(); 858 859 self.previous(); 860 861 }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) { 862 e.stopPropagation(); 863 e.preventDefault(); 864 865 self.next(); 866 867 }).on( 'click.fb', '[data-fancybox-zoom]', function(e) { 868 // Click handler for zoom button 869 self[ self.isScaledDown() ? 'scaleToActual' : 'scaleToFit' ](); 870 }); 871 872 873 // Handle page scrolling and browser resizing 874 $W.on('orientationchange.fb resize.fb', function(e) { 875 876 if ( e && e.originalEvent && e.originalEvent.type === "resize" ) { 877 878 requestAFrame(function() { 879 self.update(); 880 }); 881 882 } else { 883 884 self.$refs.stage.hide(); 885 886 setTimeout(function() { 887 self.$refs.stage.show(); 888 889 self.update(); 890 }, 600); 891 892 } 893 894 }); 895 896 // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal 897 // (a.k.a. "escaping the modal") 898 $D.on('focusin.fb', function(e) { 899 var instance = $.fancybox ? $.fancybox.getInstance() : null; 900 901 if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) { 902 return; 903 } 904 905 if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) { 906 e.stopPropagation(); 907 908 instance.focus(); 909 910 // Sometimes page gets scrolled, set it back 911 $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); 912 } 913 }); 914 915 916 // Enable keyboard navigation 917 $D.on('keydown.fb', function (e) { 918 var current = self.current, 919 keycode = e.keyCode || e.which; 920 921 if ( !current || !current.opts.keyboard ) { 922 return; 923 } 924 925 if ( $(e.target).is('input') || $(e.target).is('textarea') ) { 926 return; 927 } 928 929 // Backspace and Esc keys 930 if ( keycode === 8 || keycode === 27 ) { 931 e.preventDefault(); 932 933 self.close( e ); 934 935 return; 936 } 937 938 // Left arrow and Up arrow 939 if ( keycode === 37 || keycode === 38 ) { 940 e.preventDefault(); 941 942 self.previous(); 943 944 return; 945 } 946 947 // Righ arrow and Down arrow 948 if ( keycode === 39 || keycode === 40 ) { 949 e.preventDefault(); 950 951 self.next(); 952 953 return; 954 } 955 956 self.trigger('afterKeydown', e, keycode); 957 }); 958 959 960 // Hide controls after some inactivity period 961 if ( self.group[ self.currIndex ].opts.idleTime ) { 962 self.idleSecondsCounter = 0; 963 964 $D.on('mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function(e) { 965 self.idleSecondsCounter = 0; 966 967 if ( self.isIdle ) { 968 self.showControls(); 969 } 970 971 self.isIdle = false; 972 }); 973 974 self.idleInterval = window.setInterval(function() { 975 self.idleSecondsCounter++; 976 977 if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime && !self.isDragging ) { 978 self.isIdle = true; 979 self.idleSecondsCounter = 0; 980 981 self.hideControls(); 982 } 983 984 }, 1000); 985 } 986 987 }, 988 989 990 // Remove events added by the core 991 // =============================== 992 993 removeEvents : function() { 994 var self = this; 995 996 $W.off( 'orientationchange.fb resize.fb' ); 997 $D.off( 'focusin.fb keydown.fb .fb-idle' ); 998 999 this.$refs.container.off( '.fb-close .fb-prev .fb-next' ); 1000 1001 if ( self.idleInterval ) { 1002 window.clearInterval( self.idleInterval ); 1003 1004 self.idleInterval = null; 1005 } 1006 }, 1007 1008 1009 // Change to previous gallery item 1010 // =============================== 1011 1012 previous : function( duration ) { 1013 return this.jumpTo( this.currPos - 1, duration ); 1014 }, 1015 1016 1017 // Change to next gallery item 1018 // =========================== 1019 1020 next : function( duration ) { 1021 return this.jumpTo( this.currPos + 1, duration ); 1022 }, 1023 1024 1025 // Switch to selected gallery item 1026 // =============================== 1027 1028 jumpTo : function ( pos, duration, slide ) { 1029 var self = this, 1030 firstRun, 1031 loop, 1032 current, 1033 previous, 1034 canvasWidth, 1035 currentPos, 1036 transitionProps; 1037 1038 var groupLen = self.group.length; 1039 1040 if ( self.isDragging || self.isClosing || ( self.isAnimating && self.firstRun ) ) { 1041 return; 1042 } 1043 1044 pos = parseInt( pos, 10 ); 1045 loop = self.current ? self.current.opts.loop : self.opts.loop; 1046 1047 if ( !loop && ( pos < 0 || pos >= groupLen ) ) { 1048 return false; 1049 } 1050 1051 firstRun = self.firstRun = ( self.firstRun === null ); 1052 1053 if ( groupLen < 2 && !firstRun && !!self.isDragging ) { 1054 return; 1055 } 1056 1057 previous = self.current; 1058 1059 self.prevIndex = self.currIndex; 1060 self.prevPos = self.currPos; 1061 1062 // Create slides 1063 current = self.createSlide( pos ); 1064 1065 if ( groupLen > 1 ) { 1066 if ( loop || current.index > 0 ) { 1067 self.createSlide( pos - 1 ); 1068 } 1069 1070 if ( loop || current.index < groupLen - 1 ) { 1071 self.createSlide( pos + 1 ); 1072 } 1073 } 1074 1075 self.current = current; 1076 self.currIndex = current.index; 1077 self.currPos = current.pos; 1078 1079 self.trigger( 'beforeShow', firstRun ); 1080 1081 self.updateControls(); 1082 1083 currentPos = $.fancybox.getTranslate( current.$slide ); 1084 1085 current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' ); 1086 current.forcedDuration = undefined; 1087 1088 if ( $.isNumeric( duration ) ) { 1089 current.forcedDuration = duration; 1090 } else { 1091 duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ]; 1092 } 1093 1094 duration = parseInt( duration, 10 ); 1095 1096 // Fresh start - reveal container, current slide and start loading content 1097 if ( firstRun ) { 1098 1099 if ( current.opts.animationEffect && duration ) { 1100 self.$refs.container.css( 'transition-duration', duration + 'ms' ); 1101 } 1102 1103 self.$refs.container.removeClass( 'fancybox-is-hidden' ); 1104 1105 forceRedraw( self.$refs.container ); 1106 1107 self.$refs.container.addClass( 'fancybox-is-open' ); 1108 1109 // Make first slide visible (to display loading icon, if needed) 1110 current.$slide.addClass( 'fancybox-slide--current' ); 1111 1112 self.loadSlide( current ); 1113 1114 self.preload( 'image' ); 1115 1116 return; 1117 } 1118 1119 // Clean up 1120 $.each(self.slides, function( index, slide ) { 1121 $.fancybox.stop( slide.$slide ); 1122 }); 1123 1124 // Make current that slide is visible even if content is still loading 1125 current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' ); 1126 1127 // If slides have been dragged, animate them to correct position 1128 if ( current.isMoved ) { 1129 canvasWidth = Math.round( current.$slide.width() ); 1130 1131 $.each(self.slides, function( index, slide ) { 1132 var pos = slide.pos - current.pos; 1133 1134 $.fancybox.animate( slide.$slide, { 1135 top : 0, 1136 left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ) 1137 }, duration, function() { 1138 1139 slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' ); 1140 1141 if ( slide.pos === self.currPos ) { 1142 current.isMoved = false; 1143 1144 self.complete(); 1145 } 1146 }); 1147 }); 1148 1149 } else { 1150 self.$refs.stage.children().removeAttr( 'style' ); 1151 } 1152 1153 // Start transition that reveals current content 1154 // or wait when it will be loaded 1155 1156 if ( current.isLoaded ) { 1157 self.revealContent( current ); 1158 1159 } else { 1160 self.loadSlide( current ); 1161 } 1162 1163 self.preload( 'image' ); 1164 1165 if ( previous.pos === current.pos ) { 1166 return; 1167 } 1168 1169 // Handle previous slide 1170 // ===================== 1171 1172 transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' ); 1173 1174 previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ); 1175 1176 previous.isComplete = false; 1177 1178 if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) { 1179 return; 1180 } 1181 1182 if ( current.isMoved ) { 1183 previous.$slide.addClass( transitionProps ); 1184 1185 } else { 1186 1187 transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect; 1188 1189 $.fancybox.animate( previous.$slide, transitionProps, duration, function() { 1190 previous.$slide.removeClass( transitionProps ).removeAttr( 'style' ); 1191 }); 1192 1193 } 1194 1195 }, 1196 1197 1198 // Create new "slide" element 1199 // These are gallery items that are actually added to DOM 1200 // ======================================================= 1201 1202 createSlide : function( pos ) { 1203 1204 var self = this; 1205 var $slide; 1206 var index; 1207 1208 index = pos % self.group.length; 1209 index = index < 0 ? self.group.length + index : index; 1210 1211 if ( !self.slides[ pos ] && self.group[ index ] ) { 1212 $slide = $('<div class="fancybox-slide"></div>').appendTo( self.$refs.stage ); 1213 1214 self.slides[ pos ] = $.extend( true, {}, self.group[ index ], { 1215 pos : pos, 1216 $slide : $slide, 1217 isLoaded : false, 1218 }); 1219 1220 self.updateSlide( self.slides[ pos ] ); 1221 } 1222 1223 return self.slides[ pos ]; 1224 }, 1225 1226 1227 // Scale image to the actual size of the image 1228 // =========================================== 1229 1230 scaleToActual : function( x, y, duration ) { 1231 1232 var self = this; 1233 1234 var current = self.current; 1235 var $what = current.$content; 1236 1237 var imgPos, posX, posY, scaleX, scaleY; 1238 1239 var canvasWidth = parseInt( current.$slide.width(), 10 ); 1240 var canvasHeight = parseInt( current.$slide.height(), 10 ); 1241 1242 var newImgWidth = current.width; 1243 var newImgHeight = current.height; 1244 1245 if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) { 1246 return; 1247 } 1248 1249 $.fancybox.stop( $what ); 1250 1251 self.isAnimating = true; 1252 1253 x = x === undefined ? canvasWidth * 0.5 : x; 1254 y = y === undefined ? canvasHeight * 0.5 : y; 1255 1256 imgPos = $.fancybox.getTranslate( $what ); 1257 1258 scaleX = newImgWidth / imgPos.width; 1259 scaleY = newImgHeight / imgPos.height; 1260 1261 // Get center position for original image 1262 posX = ( canvasWidth * 0.5 - newImgWidth * 0.5 ); 1263 posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 ); 1264 1265 // Make sure image does not move away from edges 1266 if ( newImgWidth > canvasWidth ) { 1267 posX = imgPos.left * scaleX - ( ( x * scaleX ) - x ); 1268 1269 if ( posX > 0 ) { 1270 posX = 0; 1271 } 1272 1273 if ( posX < canvasWidth - newImgWidth ) { 1274 posX = canvasWidth - newImgWidth; 1275 } 1276 } 1277 1278 if ( newImgHeight > canvasHeight) { 1279 posY = imgPos.top * scaleY - ( ( y * scaleY ) - y ); 1280 1281 if ( posY > 0 ) { 1282 posY = 0; 1283 } 1284 1285 if ( posY < canvasHeight - newImgHeight ) { 1286 posY = canvasHeight - newImgHeight; 1287 } 1288 } 1289 1290 self.updateCursor( newImgWidth, newImgHeight ); 1291 1292 $.fancybox.animate( $what, { 1293 top : posY, 1294 left : posX, 1295 scaleX : scaleX, 1296 scaleY : scaleY 1297 }, duration || 330, function() { 1298 self.isAnimating = false; 1299 }); 1300 1301 // Stop slideshow 1302 if ( self.SlideShow && self.SlideShow.isActive ) { 1303 self.SlideShow.stop(); 1304 } 1305 }, 1306 1307 1308 // Scale image to fit inside parent element 1309 // ======================================== 1310 1311 scaleToFit : function( duration ) { 1312 1313 var self = this; 1314 1315 var current = self.current; 1316 var $what = current.$content; 1317 var end; 1318 1319 if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) { 1320 return; 1321 } 1322 1323 $.fancybox.stop( $what ); 1324 1325 self.isAnimating = true; 1326 1327 end = self.getFitPos( current ); 1328 1329 self.updateCursor( end.width, end.height ); 1330 1331 $.fancybox.animate( $what, { 1332 top : end.top, 1333 left : end.left, 1334 scaleX : end.width / $what.width(), 1335 scaleY : end.height / $what.height() 1336 }, duration || 330, function() { 1337 self.isAnimating = false; 1338 }); 1339 1340 }, 1341 1342 // Calculate image size to fit inside viewport 1343 // =========================================== 1344 1345 getFitPos : function( slide ) { 1346 var self = this; 1347 var $what = slide.$content; 1348 1349 var imgWidth = slide.width; 1350 var imgHeight = slide.height; 1351 1352 var margin = slide.opts.margin; 1353 1354 var canvasWidth, canvasHeight, minRatio, width, height; 1355 1356 if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) { 1357 return false; 1358 } 1359 1360 // Convert "margin to CSS style: [ top, right, bottom, left ] 1361 if ( $.type( margin ) === "number" ) { 1362 margin = [ margin, margin ]; 1363 } 1364 1365 if ( margin.length == 2 ) { 1366 margin = [ margin[0], margin[1], margin[0], margin[1] ]; 1367 } 1368 1369 // We can not use $slide width here, because it can have different diemensions while in transiton 1370 canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] ); 1371 canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] ); 1372 1373 minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight ); 1374 1375 width = Math.floor( minRatio * imgWidth ); 1376 height = Math.floor( minRatio * imgHeight ); 1377 1378 // Use floor rounding to make sure it really fits 1379 return { 1380 top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ], 1381 left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ], 1382 width : width, 1383 height : height 1384 }; 1385 1386 }, 1387 1388 1389 // Update content size and position for all slides 1390 // ============================================== 1391 1392 update : function() { 1393 var self = this; 1394 1395 $.each( self.slides, function( key, slide ) { 1396 self.updateSlide( slide ); 1397 }); 1398 }, 1399 1400 1401 // Update slide content position and size 1402 // ====================================== 1403 1404 updateSlide : function( slide, duration ) { 1405 var self = this, 1406 $what = slide && slide.$content; 1407 1408 if ( $what && ( slide.width || slide.height ) ) { 1409 self.isAnimating = false; 1410 1411 $.fancybox.stop( $what ); 1412 1413 $.fancybox.setTranslate( $what, self.getFitPos( slide ) ); 1414 1415 if ( slide.pos === self.currPos ) { 1416 self.updateCursor(); 1417 } 1418 } 1419 1420 slide.$slide.trigger( 'refresh' ); 1421 1422 self.trigger( 'onUpdate', slide ); 1423 1424 }, 1425 1426 1427 // Horizontally center slide 1428 // ========================= 1429 1430 centerSlide : function( slide, duration ) { 1431 var self = this, canvasWidth, pos; 1432 1433 if ( self.current ) { 1434 canvasWidth = Math.round( slide.$slide.width() ); 1435 pos = slide.pos - self.current.pos; 1436 1437 $.fancybox.animate( slide.$slide, { 1438 top : 0, 1439 left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ), 1440 opacity : 1 1441 }, duration === undefined ? 0 : duration, null, false); 1442 } 1443 }, 1444 1445 1446 // Update cursor style depending if content can be zoomed 1447 // ====================================================== 1448 1449 updateCursor : function( nextWidth, nextHeight ) { 1450 1451 var self = this; 1452 var isScaledDown; 1453 1454 var $container = self.$refs.container.removeClass( 'fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut' ); 1455 1456 if ( !self.current || self.isClosing ) { 1457 return; 1458 } 1459 1460 if ( self.isZoomable() ) { 1461 1462 $container.addClass( 'fancybox-is-zoomable' ); 1463 1464 if ( nextWidth !== undefined && nextHeight !== undefined ) { 1465 isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height; 1466 1467 } else { 1468 isScaledDown = self.isScaledDown(); 1469 } 1470 1471 if ( isScaledDown ) { 1472 1473 // If image is scaled down, then, obviously, it can be zoomed to full size 1474 $container.addClass( 'fancybox-can-zoomIn' ); 1475 1476 } else { 1477 1478 if ( self.current.opts.touch ) { 1479 1480 // If image size ir largen than available available and touch module is not disable, 1481 // then user can do panning 1482 $container.addClass( 'fancybox-can-drag' ); 1483 1484 } else { 1485 $container.addClass( 'fancybox-can-zoomOut' ); 1486 } 1487 1488 } 1489 1490 } else if ( self.current.opts.touch ) { 1491 $container.addClass( 'fancybox-can-drag' ); 1492 } 1493 1494 }, 1495 1496 1497 // Check if current slide is zoomable 1498 // ================================== 1499 1500 isZoomable : function() { 1501 1502 var self = this; 1503 1504 var current = self.current; 1505 var fitPos; 1506 1507 if ( !current || self.isClosing ) { 1508 return; 1509 } 1510 1511 // Assume that slide is zoomable if 1512 // - image is loaded successfuly 1513 // - click action is "zoom" 1514 // - actual size of the image is smaller than available area 1515 if ( current.type === 'image' && current.isLoaded && !current.hasError && 1516 ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) ) 1517 ) { 1518 1519 fitPos = self.getFitPos( current ); 1520 1521 if ( current.width > fitPos.width || current.height > fitPos.height ) { 1522 return true; 1523 } 1524 1525 } 1526 1527 return false; 1528 1529 }, 1530 1531 1532 // Check if current image dimensions are smaller than actual 1533 // ========================================================= 1534 1535 isScaledDown : function() { 1536 1537 var self = this; 1538 1539 var current = self.current; 1540 var $what = current.$content; 1541 1542 var rez = false; 1543 1544 if ( $what ) { 1545 rez = $.fancybox.getTranslate( $what ); 1546 rez = rez.width < current.width || rez.height < current.height; 1547 } 1548 1549 return rez; 1550 1551 }, 1552 1553 1554 // Check if image dimensions exceed parent element 1555 // =============================================== 1556 1557 canPan : function() { 1558 1559 var self = this; 1560 1561 var current = self.current; 1562 var $what = current.$content; 1563 1564 var rez = false; 1565 1566 if ( $what ) { 1567 rez = self.getFitPos( current ); 1568 rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1; 1569 } 1570 1571 return rez; 1572 1573 }, 1574 1575 1576 // Load content into the slide 1577 // =========================== 1578 1579 loadSlide : function( slide ) { 1580 1581 var self = this, type, $slide; 1582 var ajaxLoad; 1583 1584 if ( slide.isLoading ) { 1585 return; 1586 } 1587 1588 if ( slide.isLoaded ) { 1589 return; 1590 } 1591 1592 slide.isLoading = true; 1593 1594 self.trigger( 'beforeLoad', slide ); 1595 1596 type = slide.type; 1597 $slide = slide.$slide; 1598 1599 $slide 1600 .off( 'refresh' ) 1601 .trigger( 'onReset' ) 1602 .addClass( 'fancybox-slide--' + ( type || 'unknown' ) ) 1603 .addClass( slide.opts.slideClass ); 1604 1605 // Create content depending on the type 1606 1607 switch ( type ) { 1608 1609 case 'image': 1610 1611 self.setImage( slide ); 1612 1613 break; 1614 1615 case 'iframe': 1616 1617 self.setIframe( slide ); 1618 1619 break; 1620 1621 case 'html': 1622 1623 self.setContent( slide, slide.src || slide.content ); 1624 1625 break; 1626 1627 case 'inline': 1628 1629 if ( $( slide.src ).length ) { 1630 self.setContent( slide, $( slide.src ) ); 1631 1632 } else { 1633 self.setError( slide ); 1634 } 1635 1636 break; 1637 1638 case 'ajax': 1639 1640 self.showLoading( slide ); 1641 1642 ajaxLoad = $.ajax( $.extend( {}, slide.opts.ajax.settings, { 1643 url : slide.src, 1644 success : function ( data, textStatus ) { 1645 1646 if ( textStatus === 'success' ) { 1647 self.setContent( slide, data ); 1648 } 1649 1650 }, 1651 error : function ( jqXHR, textStatus ) { 1652 1653 if ( jqXHR && textStatus !== 'abort' ) { 1654 self.setError( slide ); 1655 } 1656 1657 } 1658 })); 1659 1660 $slide.one( 'onReset', function () { 1661 ajaxLoad.abort(); 1662 }); 1663 1664 break; 1665 1666 case 'video' : 1667 1668 self.setContent( slide, 1669 '<video controls>' + 1670 '<source src="' + slide.src + '" type="' + slide.opts.videoFormat + '">' + 1671 'Your browser doesn\'t support HTML5 video' + 1672 '</video>' 1673 ); 1674 1675 break; 1676 1677 default: 1678 1679 self.setError( slide ); 1680 1681 break; 1682 1683 } 1684 1685 return true; 1686 1687 }, 1688 1689 1690 // Use thumbnail image, if possible 1691 // ================================ 1692 1693 setImage : function( slide ) { 1694 1695 var self = this; 1696 var srcset = slide.opts.srcset || slide.opts.image.srcset; 1697 1698 var found, temp, pxRatio, windowWidth; 1699 1700 // If we have "srcset", then we need to find matching "src" value. 1701 // This is necessary, because when you set an src attribute, the browser will preload the image 1702 // before any javascript or even CSS is applied. 1703 if ( srcset ) { 1704 pxRatio = window.devicePixelRatio || 1; 1705 windowWidth = window.innerWidth * pxRatio; 1706 1707 temp = srcset.split(',').map(function ( el ) { 1708 var ret = {}; 1709 1710 el.trim().split(/\s+/).forEach(function ( el, i ) { 1711 var value = parseInt( el.substring(0, el.length - 1), 10 ); 1712 1713 if ( i === 0 ) { 1714 return ( ret.url = el ); 1715 } 1716 1717 if ( value ) { 1718 ret.value = value; 1719 ret.postfix = el[ el.length - 1 ]; 1720 } 1721 1722 }); 1723 1724 return ret; 1725 }); 1726 1727 // Sort by value 1728 temp.sort(function (a, b) { 1729 return a.value - b.value; 1730 }); 1731 1732 // Ok, now we have an array of all srcset values 1733 for ( var j = 0; j < temp.length; j++ ) { 1734 var el = temp[ j ]; 1735 1736 if ( ( el.postfix === 'w' && el.value >= windowWidth ) || ( el.postfix === 'x' && el.value >= pxRatio ) ) { 1737 found = el; 1738 break; 1739 } 1740 } 1741 1742 // If not found, take the last one 1743 if ( !found && temp.length ) { 1744 found = temp[ temp.length - 1 ]; 1745 } 1746 1747 if ( found ) { 1748 slide.src = found.url; 1749 1750 // If we have default width/height values, we can calculate height for matching source 1751 if ( slide.width && slide.height && found.postfix == 'w' ) { 1752 slide.height = ( slide.width / slide.height ) * found.value; 1753 slide.width = found.value; 1754 } 1755 } 1756 } 1757 1758 // This will be wrapper containing both ghost and actual image 1759 slide.$content = $('<div class="fancybox-image-wrap"></div>') 1760 .addClass( 'fancybox-is-hidden' ) 1761 .appendTo( slide.$slide ); 1762 1763 1764 // If we have a thumbnail, we can display it while actual image is loading 1765 // Users will not stare at black screen and actual image will appear gradually 1766 if ( slide.opts.preload !== false && slide.opts.width && slide.opts.height && ( slide.opts.thumb || slide.opts.$thumb ) ) { 1767 1768 slide.width = slide.opts.width; 1769 slide.height = slide.opts.height; 1770 1771 slide.$ghost = $('<img />') 1772 .one('error', function() { 1773 1774 $(this).remove(); 1775 1776 slide.$ghost = null; 1777 1778 self.setBigImage( slide ); 1779 1780 }) 1781 .one('load', function() { 1782 1783 self.afterLoad( slide ); 1784 1785 self.setBigImage( slide ); 1786 1787 }) 1788 .addClass( 'fancybox-image' ) 1789 .appendTo( slide.$content ) 1790 .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) ); 1791 1792 } else { 1793 1794 self.setBigImage( slide ); 1795 1796 } 1797 1798 }, 1799 1800 1801 // Create full-size image 1802 // ====================== 1803 1804 setBigImage : function ( slide ) { 1805 var self = this; 1806 var $img = $('<img />'); 1807 1808 slide.$image = $img 1809 .one('error', function() { 1810 1811 self.setError( slide ); 1812 1813 }) 1814 .one('load', function() { 1815 1816 // Clear timeout that checks if loading icon needs to be displayed 1817 clearTimeout( slide.timouts ); 1818 1819 slide.timouts = null; 1820 1821 if ( self.isClosing ) { 1822 return; 1823 } 1824 1825 slide.width = slide.opts.width || this.naturalWidth; 1826 slide.height = slide.opts.height || this.naturalHeight; 1827 1828 if ( slide.opts.image.srcset ) { 1829 $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset ); 1830 } 1831 1832 self.hideLoading( slide ); 1833 1834 if ( slide.$ghost ) { 1835 1836 slide.timouts = setTimeout(function() { 1837 slide.timouts = null; 1838 1839 slide.$ghost.hide(); 1840 1841 }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) ); 1842 1843 } else { 1844 self.afterLoad( slide ); 1845 } 1846 1847 }) 1848 .addClass( 'fancybox-image' ) 1849 .attr('src', slide.src) 1850 .appendTo( slide.$content ); 1851 1852 if ( ( $img[0].complete || $img[0].readyState == "complete" ) && $img[0].naturalWidth && $img[0].naturalHeight ) { 1853 $img.trigger( 'load' ); 1854 1855 } else if( $img[0].error ) { 1856 $img.trigger( 'error' ); 1857 1858 } else { 1859 1860 slide.timouts = setTimeout(function() { 1861 if ( !$img[0].complete && !slide.hasError ) { 1862 self.showLoading( slide ); 1863 } 1864 1865 }, 100); 1866 1867 } 1868 1869 }, 1870 1871 1872 // Create iframe wrapper, iframe and bindings 1873 // ========================================== 1874 1875 setIframe : function( slide ) { 1876 var self = this, 1877 opts = slide.opts.iframe, 1878 $slide = slide.$slide, 1879 $iframe; 1880 1881 slide.$content = $('<div class="fancybox-content' + ( opts.preload ? ' fancybox-is-hidden' : '' ) + '"></div>') 1882 .css( opts.css ) 1883 .appendTo( $slide ); 1884 1885 $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) ) 1886 .attr( opts.attr ) 1887 .appendTo( slide.$content ); 1888 1889 if ( opts.preload ) { 1890 1891 self.showLoading( slide ); 1892 1893 // Unfortunately, it is not always possible to determine if iframe is successfully loaded 1894 // (due to browser security policy) 1895 1896 $iframe.on('load.fb error.fb', function(e) { 1897 this.isReady = 1; 1898 1899 slide.$slide.trigger( 'refresh' ); 1900 1901 self.afterLoad( slide ); 1902 }); 1903 1904 // Recalculate iframe content size 1905 // =============================== 1906 1907 $slide.on('refresh.fb', function() { 1908 var $wrap = slide.$content, 1909 frameWidth = opts.css.width, 1910 frameHeight = opts.css.height, 1911 scrollWidth, 1912 $contents, 1913 $body; 1914 1915 if ( $iframe[0].isReady !== 1 ) { 1916 return; 1917 } 1918 1919 // Check if content is accessible, 1920 // it will fail if frame is not with the same origin 1921 1922 try { 1923 $contents = $iframe.contents(); 1924 $body = $contents.find('body'); 1925 1926 } catch (ignore) {} 1927 1928 // Calculate dimensions for the wrapper 1929 if ( $body && $body.length ) { 1930 1931 if ( frameWidth === undefined ) { 1932 scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth; 1933 1934 frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) ); 1935 frameWidth += $wrap.outerWidth() - $wrap.innerWidth(); 1936 } 1937 1938 if ( frameHeight === undefined ) { 1939 frameHeight = Math.ceil( $body.outerHeight(true) ); 1940 frameHeight += $wrap.outerHeight() - $wrap.innerHeight(); 1941 } 1942 1943 // Resize wrapper to fit iframe content 1944 if ( frameWidth ) { 1945 $wrap.width( frameWidth ); 1946 } 1947 1948 if ( frameHeight ) { 1949 $wrap.height( frameHeight ); 1950 } 1951 } 1952 1953 $wrap.removeClass( 'fancybox-is-hidden' ); 1954 1955 }); 1956 1957 } else { 1958 1959 this.afterLoad( slide ); 1960 1961 } 1962 1963 $iframe.attr( 'src', slide.src ); 1964 1965 if ( slide.opts.smallBtn === true ) { 1966 slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) ); 1967 } 1968 1969 // Remove iframe if closing or changing gallery item 1970 $slide.one( 'onReset', function () { 1971 1972 // This helps IE not to throw errors when closing 1973 try { 1974 1975 $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' ); 1976 1977 } catch ( ignore ) {} 1978 1979 $( this ).empty(); 1980 1981 slide.isLoaded = false; 1982 1983 }); 1984 1985 }, 1986 1987 1988 // Wrap and append content to the slide 1989 // ====================================== 1990 1991 setContent : function ( slide, content ) { 1992 1993 var self = this; 1994 1995 if ( self.isClosing ) { 1996 return; 1997 } 1998 1999 self.hideLoading( slide ); 2000 2001 slide.$slide.empty(); 2002 2003 if ( isQuery( content ) && content.parent().length ) { 2004 2005 // If content is a jQuery object, then it will be moved to the slide. 2006 // The placeholder is created so we will know where to put it back. 2007 // If user is navigating gallery fast, then the content might be already inside fancyBox 2008 // ===================================================================================== 2009 2010 // Make sure content is not already moved to fancyBox 2011 content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' ); 2012 2013 // Create temporary element marking original place of the content 2014 slide.$placeholder = $( '<div></div>' ).hide().insertAfter( content ); 2015 2016 // Make sure content is visible 2017 content.css('display', 'inline-block'); 2018 2019 } else if ( !slide.hasError ) { 2020 2021 // If content is just a plain text, try to convert it to html 2022 if ( $.type( content ) === 'string' ) { 2023 content = $('<div>').append( $.trim( content ) ).contents(); 2024 2025 // If we have text node, then add wrapping element to make vertical alignment work 2026 if ( content[0].nodeType === 3 ) { 2027 content = $('<div>').html( content ); 2028 } 2029 } 2030 2031 // If "filter" option is provided, then filter content 2032 if ( slide.opts.filter ) { 2033 content = $('<div>').html( content ).find( slide.opts.filter ); 2034 } 2035 2036 } 2037 2038 slide.$slide.one('onReset', function () { 2039 2040 // Pause all html5 video/audio 2041 $( this ).find( 'video,audio' ).trigger( 'pause' ); 2042 2043 // Put content back 2044 if ( slide.$placeholder ) { 2045 slide.$placeholder.after( content.hide() ).remove(); 2046 2047 slide.$placeholder = null; 2048 } 2049 2050 // Remove custom close button 2051 if ( slide.$smallBtn ) { 2052 slide.$smallBtn.remove(); 2053 2054 slide.$smallBtn = null; 2055 } 2056 2057 // Remove content and mark slide as not loaded 2058 if ( !slide.hasError ) { 2059 $(this).empty(); 2060 2061 slide.isLoaded = false; 2062 } 2063 2064 }); 2065 2066 slide.$content = $( content ).appendTo( slide.$slide ); 2067 2068 this.afterLoad( slide ); 2069 }, 2070 2071 // Display error message 2072 // ===================== 2073 2074 setError : function ( slide ) { 2075 2076 slide.hasError = true; 2077 2078 slide.$slide.removeClass( 'fancybox-slide--' + slide.type ); 2079 2080 this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) ); 2081 2082 }, 2083 2084 2085 // Show loading icon inside the slide 2086 // ================================== 2087 2088 showLoading : function( slide ) { 2089 2090 var self = this; 2091 2092 slide = slide || self.current; 2093 2094 if ( slide && !slide.$spinner ) { 2095 slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide ); 2096 } 2097 2098 }, 2099 2100 // Remove loading icon from the slide 2101 // ================================== 2102 2103 hideLoading : function( slide ) { 2104 2105 var self = this; 2106 2107 slide = slide || self.current; 2108 2109 if ( slide && slide.$spinner ) { 2110 slide.$spinner.remove(); 2111 2112 delete slide.$spinner; 2113 } 2114 2115 }, 2116 2117 2118 // Adjustments after slide content has been loaded 2119 // =============================================== 2120 2121 afterLoad : function( slide ) { 2122 2123 var self = this; 2124 2125 if ( self.isClosing ) { 2126 return; 2127 } 2128 2129 slide.isLoading = false; 2130 slide.isLoaded = true; 2131 2132 self.trigger( 'afterLoad', slide ); 2133 2134 self.hideLoading( slide ); 2135 2136 if ( slide.opts.smallBtn && !slide.$smallBtn ) { 2137 slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content.filter('div,form').first() ); 2138 } 2139 2140 if ( slide.opts.protect && slide.$content && !slide.hasError ) { 2141 2142 // Disable right click 2143 slide.$content.on( 'contextmenu.fb', function( e ) { 2144 if ( e.button == 2 ) { 2145 e.preventDefault(); 2146 } 2147 2148 return true; 2149 }); 2150 2151 // Add fake element on top of the image 2152 // This makes a bit harder for user to select image 2153 if ( slide.type === 'image' ) { 2154 $( '<div class="fancybox-spaceball"></div>' ).appendTo( slide.$content ); 2155 } 2156 2157 } 2158 2159 self.revealContent( slide ); 2160 2161 }, 2162 2163 2164 // Make content visible 2165 // This method is called right after content has been loaded or 2166 // user navigates gallery and transition should start 2167 // ============================================================ 2168 2169 revealContent : function( slide ) { 2170 2171 var self = this; 2172 var $slide = slide.$slide; 2173 2174 var effect, effectClassName, duration, opacity, end, start = false; 2175 2176 effect = slide.opts[ self.firstRun ? 'animationEffect' : 'transitionEffect' ]; 2177 duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ]; 2178 2179 duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 ); 2180 2181 if ( slide.isMoved || slide.pos !== self.currPos || !duration ) { 2182 effect = false; 2183 } 2184 2185 // Check if can zoom 2186 if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) { 2187 effect = 'fade'; 2188 } 2189 2190 // Zoom animation 2191 // ============== 2192 2193 if ( effect === 'zoom' ) { 2194 end = self.getFitPos( slide ); 2195 2196 end.scaleX = end.width / start.width; 2197 end.scaleY = end.height / start.height; 2198 2199 delete end.width; 2200 delete end.height; 2201 2202 // Check if we need to animate opacity 2203 opacity = slide.opts.zoomOpacity; 2204 2205 if ( opacity == 'auto' ) { 2206 opacity = Math.abs( slide.width / slide.height - start.width / start.height ) > 0.1; 2207 } 2208 2209 if ( opacity ) { 2210 start.opacity = 0.1; 2211 end.opacity = 1; 2212 } 2213 2214 // Draw image at start position 2215 $.fancybox.setTranslate( slide.$content.removeClass( 'fancybox-is-hidden' ), start ); 2216 2217 forceRedraw( slide.$content ); 2218 2219 // Start animation 2220 $.fancybox.animate( slide.$content, end, duration, function() { 2221 self.complete(); 2222 }); 2223 2224 return; 2225 } 2226 2227 self.updateSlide( slide ); 2228 2229 2230 // Simply show content 2231 // =================== 2232 2233 if ( !effect ) { 2234 forceRedraw( $slide ); 2235 2236 slide.$content.removeClass( 'fancybox-is-hidden' ); 2237 2238 if ( slide.pos === self.currPos ) { 2239 self.complete(); 2240 } 2241 2242 return; 2243 } 2244 2245 $.fancybox.stop( $slide ); 2246 2247 effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos >= self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect; 2248 2249 $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName ); 2250 2251 slide.$content.removeClass( 'fancybox-is-hidden' ); 2252 2253 //Force reflow for CSS3 transitions 2254 forceRedraw( $slide ); 2255 2256 $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) { 2257 $slide.removeClass( effectClassName ).removeAttr( 'style' ); 2258 2259 if ( slide.pos === self.currPos ) { 2260 self.complete(); 2261 } 2262 2263 }, true); 2264 2265 }, 2266 2267 2268 // Check if we can and have to zoom from thumbnail 2269 //================================================ 2270 2271 getThumbPos : function( slide ) { 2272 2273 var self = this; 2274 var rez = false; 2275 2276 // Check if element is inside the viewport by at least 1 pixel 2277 var isElementVisible = function( $el ) { 2278 var element = $el[0]; 2279 2280 var elementRect = element.getBoundingClientRect(); 2281 var parentRects = []; 2282 2283 var visibleInAllParents; 2284 2285 while ( element.parentElement !== null ) { 2286 if ( $(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto' ) { 2287 parentRects.push(element.parentElement.getBoundingClientRect()); 2288 } 2289 2290 element = element.parentElement; 2291 } 2292 2293 visibleInAllParents = parentRects.every(function(parentRect){ 2294 var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left); 2295 var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top); 2296 2297 return visiblePixelX > 0 && visiblePixelY > 0; 2298 }); 2299 2300 return visibleInAllParents && 2301 elementRect.bottom > 0 && elementRect.right > 0 && 2302 elementRect.left < $(window).width() && elementRect.top < $(window).height(); 2303 }; 2304 2305 var $thumb = slide.opts.$thumb; 2306 var thumbPos = $thumb ? $thumb.offset() : 0; 2307 var slidePos; 2308 2309 if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) { 2310 slidePos = self.$refs.stage.offset(); 2311 2312 rez = { 2313 top : thumbPos.top - slidePos.top + parseFloat( $thumb.css( "border-top-width" ) || 0 ), 2314 left : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ), 2315 width : $thumb.width(), 2316 height : $thumb.height(), 2317 scaleX : 1, 2318 scaleY : 1 2319 }; 2320 } 2321 2322 return rez; 2323 }, 2324 2325 2326 // Final adjustments after current gallery item is moved to position 2327 // and it`s content is loaded 2328 // ================================================================== 2329 2330 complete : function() { 2331 var self = this, 2332 current = self.current, 2333 slides = {}, 2334 promise; 2335 2336 if ( current.isMoved || !current.isLoaded || current.isComplete ) { 2337 return; 2338 } 2339 2340 current.isComplete = true; 2341 2342 current.$slide.siblings().trigger( 'onReset' ); 2343 2344 self.preload( 'inline' ); 2345 2346 // Trigger any CSS3 transiton inside the slide 2347 forceRedraw( current.$slide ); 2348 2349 current.$slide.addClass( 'fancybox-slide--complete' ); 2350 2351 // Remove unnecessary slides 2352 $.each( self.slides, function( key, slide ) { 2353 if ( slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1 ) { 2354 slides[ slide.pos ] = slide; 2355 2356 } else if ( slide ) { 2357 $.fancybox.stop( slide.$slide ); 2358 2359 slide.$slide.off().remove(); 2360 } 2361 }); 2362 2363 self.slides = slides; 2364 2365 self.updateCursor(); 2366 2367 self.trigger( 'afterShow' ); 2368 2369 // Play first html5 video/audio 2370 current.$slide.find( 'video,audio' ).first().trigger( 'play' ); 2371 2372 // Try to focus on the first focusable element 2373 if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) { 2374 self.focus(); 2375 } 2376 2377 }, 2378 2379 2380 // Preload next and previous slides 2381 // ================================ 2382 2383 preload : function( type ) { 2384 var self = this, 2385 next = self.slides[ self.currPos + 1 ], 2386 prev = self.slides[ self.currPos - 1 ]; 2387 2388 if ( next && next.type === type ) { 2389 self.loadSlide( next ); 2390 } 2391 2392 if ( prev && prev.type === type ) { 2393 self.loadSlide( prev ); 2394 } 2395 }, 2396 2397 2398 // Try to find and focus on the first focusable element 2399 // ==================================================== 2400 2401 focus : function() { 2402 var current = this.current; 2403 var $el; 2404 2405 if ( this.isClosing ) { 2406 return; 2407 } 2408 2409 if ( current && current.isComplete ) { 2410 2411 // Look for first input with autofocus attribute 2412 $el = current.$slide.find('input[autofocus]:enabled:visible:first'); 2413 2414 if ( !$el.length ) { 2415 $el = current.$slide.find('button,:input,[tabindex],a').filter(':enabled:visible:first'); 2416 } 2417 } 2418 2419 $el = $el && $el.length ? $el : this.$refs.container; 2420 2421 $el.focus(); 2422 }, 2423 2424 2425 // Activates current instance - brings container to the front and enables keyboard, 2426 // notifies other instances about deactivating 2427 // ================================================================================= 2428 2429 activate : function () { 2430 var self = this; 2431 2432 // Deactivate all instances 2433 $( '.fancybox-container' ).each(function () { 2434 var instance = $(this).data( 'FancyBox' ); 2435 2436 // Skip self and closing instances 2437 if (instance && instance.id !== self.id && !instance.isClosing) { 2438 instance.trigger( 'onDeactivate' ); 2439 2440 instance.removeEvents(); 2441 2442 instance.isVisible = false; 2443 } 2444 2445 }); 2446 2447 self.isVisible = true; 2448 2449 if ( self.current || self.isIdle ) { 2450 self.update(); 2451 2452 self.updateControls(); 2453 } 2454 2455 self.trigger( 'onActivate' ); 2456 2457 self.addEvents(); 2458 }, 2459 2460 2461 // Start closing procedure 2462 // This will start "zoom-out" animation if needed and clean everything up afterwards 2463 // ================================================================================= 2464 2465 close : function( e, d ) { 2466 2467 var self = this; 2468 var current = self.current; 2469 2470 var effect, duration; 2471 var $what, opacity, start, end; 2472 2473 var done = function() { 2474 self.cleanUp( e ); 2475 }; 2476 2477 if ( self.isClosing ) { 2478 return false; 2479 } 2480 2481 self.isClosing = true; 2482 2483 // If beforeClose callback prevents closing, make sure content is centered 2484 if ( self.trigger( 'beforeClose', e ) === false ) { 2485 self.isClosing = false; 2486 2487 requestAFrame(function() { 2488 self.update(); 2489 }); 2490 2491 return false; 2492 } 2493 2494 // Remove all events 2495 // If there are multiple instances, they will be set again by "activate" method 2496 self.removeEvents(); 2497 2498 if ( current.timouts ) { 2499 clearTimeout( current.timouts ); 2500 } 2501 2502 $what = current.$content; 2503 effect = current.opts.animationEffect; 2504 duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 ); 2505 2506 // Remove other slides 2507 current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' ); 2508 2509 current.$slide.siblings().trigger( 'onReset' ).remove(); 2510 2511 // Trigger animations 2512 if ( duration ) { 2513 self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' ); 2514 } 2515 2516 // Clean up 2517 self.hideLoading( current ); 2518 2519 self.hideControls(); 2520 2521 self.updateCursor(); 2522 2523 // Check if possible to zoom-out 2524 if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) { 2525 effect = 'fade'; 2526 } 2527 2528 if ( effect === 'zoom' ) { 2529 $.fancybox.stop( $what ); 2530 2531 start = $.fancybox.getTranslate( $what ); 2532 2533 start.width = start.width * start.scaleX; 2534 start.height = start.height * start.scaleY; 2535 2536 // Check if we need to animate opacity 2537 opacity = current.opts.zoomOpacity; 2538 2539 if ( opacity == 'auto' ) { 2540 opacity = Math.abs( current.width / current.height - end.width / end.height ) > 0.1; 2541 } 2542 2543 if ( opacity ) { 2544 end.opacity = 0; 2545 } 2546 2547 start.scaleX = start.width / end.width; 2548 start.scaleY = start.height / end.height; 2549 2550 start.width = end.width; 2551 start.height = end.height; 2552 2553 $.fancybox.setTranslate( current.$content, start ); 2554 2555 forceRedraw( current.$content ); 2556 2557 $.fancybox.animate( current.$content, end, duration, done ); 2558 2559 return true; 2560 } 2561 2562 if ( effect && duration ) { 2563 2564 // If skip animation 2565 if ( e === true ) { 2566 setTimeout( done, duration ); 2567 2568 } else { 2569 $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done ); 2570 } 2571 2572 } else { 2573 done(); 2574 } 2575 2576 return true; 2577 }, 2578 2579 2580 // Final adjustments after removing the instance 2581 // ============================================= 2582 2583 cleanUp : function( e ) { 2584 var self = this, 2585 $body = $( 'body' ), 2586 instance, 2587 offset; 2588 2589 self.current.$slide.trigger( 'onReset' ); 2590 2591 self.$refs.container.empty().remove(); 2592 2593 self.trigger( 'afterClose', e ); 2594 2595 // Place back focus 2596 if ( self.$lastFocus && !!self.current.opts.backFocus ) { 2597 self.$lastFocus.focus(); 2598 } 2599 2600 self.current = null; 2601 2602 // Check if there are other instances 2603 instance = $.fancybox.getInstance(); 2604 2605 if ( instance ) { 2606 instance.activate(); 2607 2608 } else { 2609 2610 $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); 2611 2612 $body.removeClass( 'fancybox-active compensate-for-scrollbar' ); 2613 2614 if ( $body.hasClass( 'fancybox-iosfix' ) ) { 2615 offset = parseInt(document.body.style.top, 10); 2616 2617 $body.removeClass( 'fancybox-iosfix' ).css( 'top', '' ).scrollTop( offset * -1 ); 2618 } 2619 2620 $( '#fancybox-style-noscroll' ).remove(); 2621 2622 } 2623 2624 }, 2625 2626 2627 // Call callback and trigger an event 2628 // ================================== 2629 2630 trigger : function( name, slide ) { 2631 var args = Array.prototype.slice.call(arguments, 1), 2632 self = this, 2633 obj = slide && slide.opts ? slide : self.current, 2634 rez; 2635 2636 if ( obj ) { 2637 args.unshift( obj ); 2638 2639 } else { 2640 obj = self; 2641 } 2642 2643 args.unshift( self ); 2644 2645 if ( $.isFunction( obj.opts[ name ] ) ) { 2646 rez = obj.opts[ name ].apply( obj, args ); 2647 } 2648 2649 if ( rez === false ) { 2650 return rez; 2651 } 2652 2653 if ( name === 'afterClose' || !self.$refs ) { 2654 $D.trigger( name + '.fb', args ); 2655 2656 } else { 2657 self.$refs.container.trigger( name + '.fb', args ); 2658 } 2659 2660 }, 2661 2662 2663 // Update infobar values, navigation button states and reveal caption 2664 // ================================================================== 2665 2666 updateControls : function ( force ) { 2667 2668 var self = this; 2669 2670 var current = self.current, 2671 index = current.index, 2672 caption = current.opts.caption, 2673 $container = self.$refs.container, 2674 $caption = self.$refs.caption; 2675 2676 // Recalculate content dimensions 2677 current.$slide.trigger( 'refresh' ); 2678 2679 self.$caption = caption && caption.length ? $caption.html( caption ) : null; 2680 2681 if ( !self.isHiddenControls && !self.isIdle ) { 2682 self.showControls(); 2683 } 2684 2685 // Update info and navigation elements 2686 $container.find('[data-fancybox-count]').html( self.group.length ); 2687 $container.find('[data-fancybox-index]').html( index + 1 ); 2688 2689 $container.find('[data-fancybox-prev]').prop( 'disabled', ( !current.opts.loop && index <= 0 ) ); 2690 $container.find('[data-fancybox-next]').prop( 'disabled', ( !current.opts.loop && index >= self.group.length - 1 ) ); 2691 2692 if ( current.type === 'image' ) { 2693 2694 // Update download button source 2695 $container.find('[data-fancybox-download]').attr( 'href', current.opts.image.src || current.src ).show(); 2696 2697 } else { 2698 $container.find('[data-fancybox-download],[data-fancybox-zoom]').hide(); 2699 } 2700 }, 2701 2702 // Hide toolbar and caption 2703 // ======================== 2704 2705 hideControls : function () { 2706 2707 this.isHiddenControls = true; 2708 2709 this.$refs.container.removeClass( 'fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav' ); 2710 2711 }, 2712 2713 showControls : function() { 2714 var self = this; 2715 var opts = self.current ? self.current.opts : self.opts; 2716 var $container = self.$refs.container; 2717 2718 self.isHiddenControls = false; 2719 self.idleSecondsCounter = 0; 2720 2721 $container 2722 .toggleClass( 'fancybox-show-toolbar', !!( opts.toolbar && opts.buttons ) ) 2723 .toggleClass( 'fancybox-show-infobar', !!( opts.infobar && self.group.length > 1 ) ) 2724 .toggleClass( 'fancybox-show-nav', !!( opts.arrows && self.group.length > 1 ) ) 2725 .toggleClass( 'fancybox-is-modal', !!opts.modal ); 2726 2727 if ( self.$caption ) { 2728 $container.addClass( 'fancybox-show-caption '); 2729 2730 } else { 2731 $container.removeClass( 'fancybox-show-caption' ); 2732 } 2733 2734 }, 2735 2736 2737 // Toggle toolbar and caption 2738 // ========================== 2739 2740 toggleControls : function() { 2741 if ( this.isHiddenControls ) { 2742 this.showControls(); 2743 2744 } else { 2745 this.hideControls(); 2746 } 2747 2748 }, 2749 2750 2751 }); 2752 2753 2754 $.fancybox = { 2755 2756 version : "3.2.10", 2757 defaults : defaults, 2758 2759 2760 // Get current instance and execute a command. 2761 // 2762 // Examples of usage: 2763 // 2764 // $instance = $.fancybox.getInstance(); 2765 // $.fancybox.getInstance().jumpTo( 1 ); 2766 // $.fancybox.getInstance( 'jumpTo', 1 ); 2767 // $.fancybox.getInstance( function() { 2768 // console.info( this.currIndex ); 2769 // }); 2770 // ====================================================== 2771 2772 getInstance : function ( command ) { 2773 var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data( 'FancyBox' ); 2774 var args = Array.prototype.slice.call(arguments, 1); 2775 2776 if ( instance instanceof FancyBox ) { 2777 2778 if ( $.type( command ) === 'string' ) { 2779 instance[ command ].apply( instance, args ); 2780 2781 } else if ( $.type( command ) === 'function' ) { 2782 command.apply( instance, args ); 2783 } 2784 2785 return instance; 2786 } 2787 2788 return false; 2789 2790 }, 2791 2792 2793 // Create new instance 2794 // =================== 2795 2796 open : function ( items, opts, index ) { 2797 return new FancyBox( items, opts, index ); 2798 }, 2799 2800 2801 // Close current or all instances 2802 // ============================== 2803 2804 close : function ( all ) { 2805 var instance = this.getInstance(); 2806 2807 if ( instance ) { 2808 instance.close(); 2809 2810 // Try to find and close next instance 2811 2812 if ( all === true ) { 2813 this.close(); 2814 } 2815 } 2816 2817 }, 2818 2819 // Close instances and unbind all events 2820 // ============================== 2821 2822 destroy : function() { 2823 2824 this.close( true ); 2825 2826 $D.off( 'click.fb-start' ); 2827 2828 }, 2829 2830 2831 // Try to detect mobile devices 2832 // ============================ 2833 2834 isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), 2835 2836 2837 // Detect if 'translate3d' support is available 2838 // ============================================ 2839 2840 use3d : (function() { 2841 var div = document.createElement('div'); 2842 2843 return window.getComputedStyle && window.getComputedStyle( div ).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11); 2844 }()), 2845 2846 // Helper function to get current visual state of an element 2847 // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] 2848 // ===================================================================== 2849 2850 getTranslate : function( $el ) { 2851 var matrix; 2852 2853 if ( !$el || !$el.length ) { 2854 return false; 2855 } 2856 2857 matrix = $el.eq( 0 ).css('transform'); 2858 2859 if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) { 2860 matrix = matrix.split('(')[1]; 2861 matrix = matrix.split(')')[0]; 2862 matrix = matrix.split(','); 2863 } else { 2864 matrix = []; 2865 } 2866 2867 if ( matrix.length ) { 2868 2869 // If IE 2870 if ( matrix.length > 10 ) { 2871 matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ]; 2872 2873 } else { 2874 matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]]; 2875 } 2876 2877 matrix = matrix.map(parseFloat); 2878 2879 } else { 2880 matrix = [ 0, 0, 1, 1 ]; 2881 2882 var transRegex = /\.*translate\((.*)px,(.*)px\)/i; 2883 var transRez = transRegex.exec( $el.eq( 0 ).attr('style') ); 2884 2885 if ( transRez ) { 2886 matrix[ 0 ] = parseFloat( transRez[2] ); 2887 matrix[ 1 ] = parseFloat( transRez[1] ); 2888 } 2889 } 2890 2891 return { 2892 top : matrix[ 0 ], 2893 left : matrix[ 1 ], 2894 scaleX : matrix[ 2 ], 2895 scaleY : matrix[ 3 ], 2896 opacity : parseFloat( $el.css('opacity') ), 2897 width : $el.width(), 2898 height : $el.height() 2899 }; 2900 2901 }, 2902 2903 2904 // Shortcut for setting "translate3d" properties for element 2905 // Can set be used to set opacity, too 2906 // ======================================================== 2907 2908 setTranslate : function( $el, props ) { 2909 var str = ''; 2910 var css = {}; 2911 2912 if ( !$el || !props ) { 2913 return; 2914 } 2915 2916 if ( props.left !== undefined || props.top !== undefined ) { 2917 str = ( props.left === undefined ? $el.position().left : props.left ) + 'px, ' + ( props.top === undefined ? $el.position().top : props.top ) + 'px'; 2918 2919 if ( this.use3d ) { 2920 str = 'translate3d(' + str + ', 0px)'; 2921 2922 } else { 2923 str = 'translate(' + str + ')'; 2924 } 2925 } 2926 2927 if ( props.scaleX !== undefined && props.scaleY !== undefined ) { 2928 str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')'; 2929 } 2930 2931 if ( str.length ) { 2932 css.transform = str; 2933 } 2934 2935 if ( props.opacity !== undefined ) { 2936 css.opacity = props.opacity; 2937 } 2938 2939 if ( props.width !== undefined ) { 2940 css.width = props.width; 2941 } 2942 2943 if ( props.height !== undefined ) { 2944 css.height = props.height; 2945 } 2946 2947 return $el.css( css ); 2948 }, 2949 2950 2951 // Simple CSS transition handler 2952 // ============================= 2953 2954 animate : function ( $el, to, duration, callback, leaveAnimationName ) { 2955 if ( $.isFunction( duration ) ) { 2956 callback = duration; 2957 duration = null; 2958 } 2959 2960 if ( !$.isPlainObject( to ) ) { 2961 $el.removeAttr( 'style' ); 2962 } 2963 2964 $el.on( transitionEnd, function(e) { 2965 2966 // Skip events from child elements and z-index change 2967 if ( e && e.originalEvent && ( !$el.is( e.originalEvent.target ) || e.originalEvent.propertyName == 'z-index' ) ) { 2968 return; 2969 } 2970 2971 $.fancybox.stop( $el ); 2972 2973 if ( $.isPlainObject( to ) ) { 2974 2975 if ( to.scaleX !== undefined && to.scaleY !== undefined ) { 2976 $el.css( 'transition-duration', '' ); 2977 2978 to.width = Math.round( $el.width() * to.scaleX ); 2979 to.height = Math.round( $el.height() * to.scaleY ); 2980 2981 to.scaleX = 1; 2982 to.scaleY = 1; 2983 2984 $.fancybox.setTranslate( $el, to ); 2985 } 2986 2987 if ( leaveAnimationName === false ) { 2988 $el.removeAttr( 'style' ); 2989 } 2990 2991 } else if ( leaveAnimationName !== true ) { 2992 $el.removeClass( to ); 2993 } 2994 2995 if ( $.isFunction( callback ) ) { 2996 callback( e ); 2997 } 2998 2999 }); 3000 3001 if ( $.isNumeric( duration ) ) { 3002 $el.css( 'transition-duration', duration + 'ms' ); 3003 } 3004 3005 if ( $.isPlainObject( to ) ) { 3006 $.fancybox.setTranslate( $el, to ); 3007 3008 } else { 3009 $el.addClass( to ); 3010 } 3011 3012 if ( to.scaleX && $el.hasClass( 'fancybox-image-wrap' ) ) { 3013 $el.parent().addClass( 'fancybox-is-scaling' ); 3014 } 3015 3016 // Make sure that `transitionend` callback gets fired 3017 $el.data("timer", setTimeout(function() { 3018 $el.trigger( 'transitionend' ); 3019 }, duration + 16)); 3020 3021 }, 3022 3023 stop : function( $el ) { 3024 clearTimeout( $el.data("timer") ); 3025 3026 $el.off( 'transitionend' ).css( 'transition-duration', '' ); 3027 3028 if ( $el.hasClass( 'fancybox-image-wrap' ) ) { 3029 $el.parent().removeClass( 'fancybox-is-scaling' ); 3030 } 3031 } 3032 3033 }; 3034 3035 3036 // Default click handler for "fancyboxed" links 3037 // ============================================ 3038 3039 function _run( e ) { 3040 var $target = $( e.currentTarget ), 3041 opts = e.data ? e.data.options : {}, 3042 value = $target.attr( 'data-fancybox' ) || '', 3043 index = 0, 3044 items = []; 3045 3046 // Avoid opening multiple times 3047 if ( e.isDefaultPrevented() ) { 3048 return; 3049 } 3050 3051 e.preventDefault(); 3052 3053 // Get all related items and find index for clicked one 3054 if ( value ) { 3055 items = opts.selector ? $( opts.selector ) : ( e.data ? e.data.items : [] ); 3056 items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' ); 3057 3058 index = items.index( $target ); 3059 3060 // Sometimes current item can not be found 3061 // (for example, when slider clones items) 3062 if ( index < 0 ) { 3063 index = 0; 3064 } 3065 3066 } else { 3067 items = [ $target ]; 3068 } 3069 3070 $.fancybox.open( items, opts, index ); 3071 } 3072 3073 3074 // Create a jQuery plugin 3075 // ====================== 3076 3077 $.fn.fancybox = function (options) { 3078 var selector; 3079 3080 options = options || {}; 3081 selector = options.selector || false; 3082 3083 if ( selector ) { 3084 3085 $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, { 3086 options : options 3087 }, _run ); 3088 3089 } else { 3090 3091 this.off( 'click.fb-start' ).on( 'click.fb-start', { 3092 items : this, 3093 options : options 3094 }, _run); 3095 3096 } 3097 3098 return this; 3099 }; 3100 3101 3102 // Self initializing plugin 3103 // ======================== 3104 3105 $D.on( 'click.fb-start', '[data-fancybox]', _run ); 3106 3107 }( window, document, window.jQuery || jQuery )); 3108 3109 // ========================================================================== 3110 // 3111 // Media 3112 // Adds additional media type support 3113 // 3114 // ========================================================================== 3115 ;(function ($) { 3116 3117 'use strict'; 3118 3119 // Formats matching url to final form 3120 3121 var format = function (url, rez, params) { 3122 if ( !url ) { 3123 return; 3124 } 3125 3126 params = params || ''; 3127 3128 if ( $.type(params) === "object" ) { 3129 params = $.param(params, true); 3130 } 3131 3132 $.each(rez, function (key, value) { 3133 url = url.replace('$' + key, value || ''); 3134 }); 3135 3136 if (params.length) { 3137 url += (url.indexOf('?') > 0 ? '&' : '?') + params; 3138 } 3139 3140 return url; 3141 }; 3142 3143 // Object containing properties for each media type 3144 3145 var defaults = { 3146 youtube : { 3147 matcher : /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i, 3148 params : { 3149 autoplay : 1, 3150 autohide : 1, 3151 fs : 1, 3152 rel : 0, 3153 hd : 1, 3154 wmode : 'transparent', 3155 enablejsapi : 1, 3156 html5 : 1 3157 }, 3158 paramPlace : 8, 3159 type : 'iframe', 3160 url : '//www.youtube.com/embed/$4', 3161 thumb : '//img.youtube.com/vi/$4/hqdefault.jpg' 3162 }, 3163 3164 vimeo : { 3165 matcher : /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/, 3166 params : { 3167 autoplay : 1, 3168 hd : 1, 3169 show_title : 1, 3170 show_byline : 1, 3171 show_portrait : 0, 3172 fullscreen : 1, 3173 api : 1 3174 }, 3175 paramPlace : 3, 3176 type : 'iframe', 3177 url : '//player.vimeo.com/video/$2' 3178 }, 3179 3180 metacafe : { 3181 matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/, 3182 type : 'iframe', 3183 url : '//www.metacafe.com/embed/$1/?ap=1' 3184 }, 3185 3186 dailymotion : { 3187 matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, 3188 params : { 3189 additionalInfos : 0, 3190 autoStart : 1 3191 }, 3192 type : 'iframe', 3193 url : '//www.dailymotion.com/embed/video/$1' 3194 }, 3195 3196 vine : { 3197 matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/, 3198 type : 'iframe', 3199 url : '//vine.co/v/$1/embed/simple' 3200 }, 3201 3202 instagram : { 3203 matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, 3204 type : 'image', 3205 url : '//$1/p/$2/media/?size=l' 3206 }, 3207 3208 // Examples: 3209 // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 3210 // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z 3211 // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 3212 gmap_place : { 3213 matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i, 3214 type : 'iframe', 3215 url : function (rez) { 3216 return '//maps.google.' + rez[2] + '/?ll=' + ( rez[9] ? rez[9] + '&z=' + Math.floor( rez[10] ) + ( rez[12] ? rez[12].replace(/^\//, "&") : '' ) : rez[12] ) + '&output=' + ( rez[12] && rez[12].indexOf('layer=c') > 0 ? 'svembed' : 'embed' ); 3217 } 3218 }, 3219 3220 // Examples: 3221 // https://www.google.com/maps/search/Empire+State+Building/ 3222 // https://www.google.com/maps/search/?api=1&query=centurylink+field 3223 // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393 3224 gmap_search : { 3225 matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i, 3226 type : 'iframe', 3227 url : function (rez) { 3228 return '//maps.google.' + rez[2] + '/maps?q=' + rez[5].replace('query=', 'q=').replace('api=1', '') + '&output=embed'; 3229 } 3230 } 3231 }; 3232 3233 $(document).on('objectNeedsType.fb', function (e, instance, item) { 3234 3235 var url = item.src || '', 3236 type = false, 3237 media, 3238 thumb, 3239 rez, 3240 params, 3241 urlParams, 3242 paramObj, 3243 provider; 3244 3245 media = $.extend( true, {}, defaults, item.opts.media ); 3246 3247 // Look for any matching media type 3248 $.each(media, function ( providerName, providerOpts ) { 3249 rez = url.match( providerOpts.matcher ); 3250 3251 if ( !rez ) { 3252 return; 3253 } 3254 3255 type = providerOpts.type; 3256 paramObj = {}; 3257 3258 if ( providerOpts.paramPlace && rez[ providerOpts.paramPlace ] ) { 3259 urlParams = rez[ providerOpts.paramPlace ]; 3260 3261 if ( urlParams[ 0 ] == '?' ) { 3262 urlParams = urlParams.substring(1); 3263 } 3264 3265 urlParams = urlParams.split('&'); 3266 3267 for ( var m = 0; m < urlParams.length; ++m ) { 3268 var p = urlParams[ m ].split('=', 2); 3269 3270 if ( p.length == 2 ) { 3271 paramObj[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") ); 3272 } 3273 } 3274 } 3275 3276 params = $.extend( true, {}, providerOpts.params, item.opts[ providerName ], paramObj ); 3277 3278 url = $.type( providerOpts.url ) === "function" ? providerOpts.url.call( this, rez, params, item ) : format( providerOpts.url, rez, params ); 3279 thumb = $.type( providerOpts.thumb ) === "function" ? providerOpts.thumb.call( this, rez, params, item ) : format( providerOpts.thumb, rez ); 3280 3281 if ( providerName === 'vimeo' ) { 3282 url = url.replace('&%23', '#'); 3283 } 3284 3285 return false; 3286 }); 3287 3288 // If it is found, then change content type and update the url 3289 3290 if ( type ) { 3291 item.src = url; 3292 item.type = type; 3293 3294 if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) { 3295 item.opts.thumb = thumb; 3296 } 3297 3298 if ( type === 'iframe' ) { 3299 $.extend(true, item.opts, { 3300 iframe : { 3301 preload : false, 3302 attr : { 3303 scrolling : "no" 3304 } 3305 } 3306 }); 3307 3308 item.contentProvider = provider; 3309 3310 item.opts.slideClass += ' fancybox-slide--' + ( provider == 'gmap_place' || provider == 'gmap_search' ? 'map' : 'video' ); 3311 } 3312 3313 } else if ( url ) { 3314 item.type = item.opts.defaultType; 3315 } 3316 3317 }); 3318 3319 }( window.jQuery || jQuery )); 3320 3321 // ========================================================================== 3322 // 3323 // Guestures 3324 // Adds touch guestures, handles click and tap events 3325 // 3326 // ========================================================================== 3327 ;(function (window, document, $) { 3328 'use strict'; 3329 3330 var requestAFrame = (function () { 3331 return window.requestAnimationFrame || 3332 window.webkitRequestAnimationFrame || 3333 window.mozRequestAnimationFrame || 3334 window.oRequestAnimationFrame || 3335 // if all else fails, use setTimeout 3336 function (callback) { 3337 return window.setTimeout(callback, 1000 / 60); 3338 }; 3339 })(); 3340 3341 var cancelAFrame = (function () { 3342 return window.cancelAnimationFrame || 3343 window.webkitCancelAnimationFrame || 3344 window.mozCancelAnimationFrame || 3345 window.oCancelAnimationFrame || 3346 function (id) { 3347 window.clearTimeout(id); 3348 }; 3349 })(); 3350 3351 var pointers = function( e ) { 3352 var result = []; 3353 3354 e = e.originalEvent || e || window.e; 3355 e = e.touches && e.touches.length ? e.touches : ( e.changedTouches && e.changedTouches.length ? e.changedTouches : [ e ] ); 3356 3357 for ( var key in e ) { 3358 3359 if ( e[ key ].pageX ) { 3360 result.push( { x : e[ key ].pageX, y : e[ key ].pageY } ); 3361 3362 } else if ( e[ key ].clientX ) { 3363 result.push( { x : e[ key ].clientX, y : e[ key ].clientY } ); 3364 } 3365 } 3366 3367 return result; 3368 }; 3369 3370 var distance = function( point2, point1, what ) { 3371 if ( !point1 || !point2 ) { 3372 return 0; 3373 } 3374 3375 if ( what === 'x' ) { 3376 return point2.x - point1.x; 3377 3378 } else if ( what === 'y' ) { 3379 return point2.y - point1.y; 3380 } 3381 3382 return Math.sqrt( Math.pow( point2.x - point1.x, 2 ) + Math.pow( point2.y - point1.y, 2 ) ); 3383 }; 3384 3385 var isClickable = function( $el ) { 3386 if ( $el.is('a,area,button,[role="button"],input,label,select,summary,textarea') || $.isFunction( $el.get(0).onclick ) || $el.data('selectable') ) { 3387 return true; 3388 } 3389 3390 // Check for attributes like data-fancybox-next or data-fancybox-close 3391 for ( var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++ ) { 3392 if ( atts[i].nodeName.substr(0, 14) === 'data-fancybox-' ) { 3393 return true; 3394 } 3395 } 3396 3397 return false; 3398 }; 3399 3400 var hasScrollbars = function( el ) { 3401 var overflowY = window.getComputedStyle( el )['overflow-y']; 3402 var overflowX = window.getComputedStyle( el )['overflow-x']; 3403 3404 var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight; 3405 var horizontal = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth; 3406 3407 return vertical || horizontal; 3408 }; 3409 3410 var isScrollable = function ( $el ) { 3411 var rez = false; 3412 3413 while ( true ) { 3414 rez = hasScrollbars( $el.get(0) ); 3415 3416 if ( rez ) { 3417 break; 3418 } 3419 3420 $el = $el.parent(); 3421 3422 if ( !$el.length || $el.hasClass( 'fancybox-stage' ) || $el.is( 'body' ) ) { 3423 break; 3424 } 3425 } 3426 3427 return rez; 3428 }; 3429 3430 3431 var Guestures = function ( instance ) { 3432 var self = this; 3433 3434 self.instance = instance; 3435 3436 self.$bg = instance.$refs.bg; 3437 self.$stage = instance.$refs.stage; 3438 self.$container = instance.$refs.container; 3439 3440 self.destroy(); 3441 3442 self.$container.on( 'touchstart.fb.touch mousedown.fb.touch', $.proxy(self, 'ontouchstart') ); 3443 }; 3444 3445 Guestures.prototype.destroy = function() { 3446 this.$container.off( '.fb.touch' ); 3447 }; 3448 3449 Guestures.prototype.ontouchstart = function( e ) { 3450 var self = this; 3451 3452 var $target = $( e.target ); 3453 var instance = self.instance; 3454 var current = instance.current; 3455 var $content = current.$content; 3456 3457 var isTouchDevice = ( e.type == 'touchstart' ); 3458 3459 // Do not respond to both (touch and mouse) events 3460 if ( isTouchDevice ) { 3461 self.$container.off( 'mousedown.fb.touch' ); 3462 } 3463 3464 // Ignore right click 3465 if ( e.originalEvent && e.originalEvent.button == 2 ) { 3466 return; 3467 } 3468 3469 // Ignore taping on links, buttons, input elements 3470 if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) { 3471 return; 3472 } 3473 3474 // Ignore clicks on the scrollbar 3475 if ( !$target.is('img') && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) { 3476 return; 3477 } 3478 3479 // Ignore clicks while zooming or closing 3480 if ( !current || self.instance.isAnimating || self.instance.isClosing ) { 3481 e.stopPropagation(); 3482 e.preventDefault(); 3483 3484 return; 3485 } 3486 3487 self.realPoints = self.startPoints = pointers( e ); 3488 3489 if ( !self.startPoints ) { 3490 return; 3491 } 3492 3493 e.stopPropagation(); 3494 3495 self.startEvent = e; 3496 3497 self.canTap = true; 3498 self.$target = $target; 3499 self.$content = $content; 3500 self.opts = current.opts.touch; 3501 3502 self.isPanning = false; 3503 self.isSwiping = false; 3504 self.isZooming = false; 3505 self.isScrolling = false; 3506 3507 self.sliderStartPos = self.sliderLastPos || { top: 0, left: 0 }; 3508 self.contentStartPos = $.fancybox.getTranslate( self.$content ); 3509 self.contentLastPos = null; 3510 3511 self.startTime = new Date().getTime(); 3512 self.distanceX = self.distanceY = self.distance = 0; 3513 3514 self.canvasWidth = Math.round( current.$slide[0].clientWidth ); 3515 self.canvasHeight = Math.round( current.$slide[0].clientHeight ); 3516 3517 $(document) 3518 .off( '.fb.touch' ) 3519 .on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, "ontouchend")) 3520 .on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, "ontouchmove")); 3521 3522 if ( $.fancybox.isMobile ) { 3523 document.addEventListener('scroll', self.onscroll, true); 3524 } 3525 3526 if ( !(self.opts || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) { 3527 3528 // Prevent image ghosting while dragging 3529 if ( $target.is('img') ) { 3530 e.preventDefault(); 3531 } 3532 3533 return; 3534 } 3535 3536 if ( !( $.fancybox.isMobile && ( isScrollable( $target ) || isScrollable( $target.parent() ) ) ) ) { 3537 e.preventDefault(); 3538 } 3539 3540 if ( self.startPoints.length === 1 ) { 3541 if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) { 3542 $.fancybox.stop( self.$content ); 3543 3544 self.$content.css( 'transition-duration', '' ); 3545 3546 self.isPanning = true; 3547 3548 } else { 3549 self.isSwiping = true; 3550 } 3551 3552 self.$container.addClass( 'fancybox-controls--isGrabbing' ); 3553 } 3554 3555 if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) { 3556 self.canTap = false; 3557 self.isSwiping = false; 3558 self.isPanning = false; 3559 3560 self.isZooming = true; 3561 3562 $.fancybox.stop( self.$content ); 3563 3564 self.$content.css( 'transition-duration', '' ); 3565 3566 self.centerPointStartX = ( ( self.startPoints[0].x + self.startPoints[1].x ) * 0.5 ) - $(window).scrollLeft(); 3567 self.centerPointStartY = ( ( self.startPoints[0].y + self.startPoints[1].y ) * 0.5 ) - $(window).scrollTop(); 3568 3569 self.percentageOfImageAtPinchPointX = ( self.centerPointStartX - self.contentStartPos.left ) / self.contentStartPos.width; 3570 self.percentageOfImageAtPinchPointY = ( self.centerPointStartY - self.contentStartPos.top ) / self.contentStartPos.height; 3571 3572 self.startDistanceBetweenFingers = distance( self.startPoints[0], self.startPoints[1] ); 3573 } 3574 3575 }; 3576 3577 Guestures.prototype.onscroll = function(e) { 3578 self.isScrolling = true; 3579 }; 3580 3581 Guestures.prototype.ontouchmove = function( e ) { 3582 var self = this, 3583 $target = $(e.target); 3584 3585 if ( self.isScrolling || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) { 3586 self.canTap = false; 3587 3588 return; 3589 } 3590 3591 self.newPoints = pointers( e ); 3592 3593 if ( !( self.opts || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) { 3594 return; 3595 } 3596 3597 if ( !(self.isSwiping && self.isSwiping === true) ) { 3598 e.preventDefault(); 3599 } 3600 3601 self.distanceX = distance( self.newPoints[0], self.startPoints[0], 'x' ); 3602 self.distanceY = distance( self.newPoints[0], self.startPoints[0], 'y' ); 3603 3604 self.distance = distance( self.newPoints[0], self.startPoints[0] ) 3605 3606 // Skip false ontouchmove events (Chrome) 3607 if ( self.distance > 0 ) { 3608 if ( self.isSwiping ) { 3609 self.onSwipe(e); 3610 3611 } else if ( self.isPanning ) { 3612 self.onPan(); 3613 3614 } else if ( self.isZooming ) { 3615 self.onZoom(); 3616 } 3617 } 3618 3619 }; 3620 3621 Guestures.prototype.onSwipe = function(e) { 3622 var self = this, 3623 swiping = self.isSwiping, 3624 left = self.sliderStartPos.left || 0, 3625 angle; 3626 3627 // If direction is not yet determined 3628 if ( swiping === true ) { 3629 3630 // We need at least 10px distance to correctly calculate an angle 3631 if ( Math.abs( self.distance ) > 10 ) { 3632 self.canTap = false; 3633 3634 if ( self.instance.group.length < 2 && self.opts.vertical ) { 3635 self.isSwiping = 'y'; 3636 3637 } else if ( self.instance.isDragging || self.opts.vertical === false || ( self.opts.vertical === 'auto' && $( window ).width() > 800 ) ) { 3638 self.isSwiping = 'x'; 3639 3640 } else { 3641 angle = Math.abs( Math.atan2( self.distanceY, self.distanceX ) * 180 / Math.PI ); 3642 3643 self.isSwiping = ( angle > 45 && angle < 135 ) ? 'y' : 'x'; 3644 } 3645 3646 self.canTap = false; 3647 3648 if ( self.isSwiping === 'y' && $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) { 3649 self.isScrolling = true; 3650 3651 return; 3652 } 3653 3654 self.instance.isDragging = self.isSwiping; 3655 3656 // Reset points to avoid jumping, because we dropped first swipes to calculate the angle 3657 self.startPoints = self.newPoints; 3658 3659 $.each(self.instance.slides, function( index, slide ) { 3660 $.fancybox.stop( slide.$slide ); 3661 3662 slide.$slide.css( 'transition-duration', '' ); 3663 3664 slide.inTransition = false; 3665 3666 if ( slide.pos === self.instance.current.pos ) { 3667 self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left; 3668 } 3669 }); 3670 3671 // Stop slideshow 3672 if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) { 3673 self.instance.SlideShow.stop(); 3674 } 3675 } 3676 3677 return; 3678 } 3679 3680 // Sticky edges 3681 if ( swiping == 'x' ) { 3682 if ( self.distanceX > 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === 0 && !self.instance.current.opts.loop ) ) ) { 3683 left = left + Math.pow( self.distanceX, 0.8 ); 3684 3685 } else if ( self.distanceX < 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop ) ) ) { 3686 left = left - Math.pow( -self.distanceX, 0.8 ); 3687 3688 } else { 3689 left = left + self.distanceX; 3690 } 3691 } 3692 3693 self.sliderLastPos = { 3694 top : swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY, 3695 left : left 3696 }; 3697 3698 if ( self.requestId ) { 3699 cancelAFrame( self.requestId ); 3700 3701 self.requestId = null; 3702 } 3703 3704 self.requestId = requestAFrame(function() { 3705 3706 if ( self.sliderLastPos ) { 3707 $.each(self.instance.slides, function( index, slide ) { 3708 var pos = slide.pos - self.instance.currPos; 3709 3710 $.fancybox.setTranslate( slide.$slide, { 3711 top : self.sliderLastPos.top, 3712 left : self.sliderLastPos.left + ( pos * self.canvasWidth ) + ( pos * slide.opts.gutter ) 3713 }); 3714 }); 3715 3716 self.$container.addClass( 'fancybox-is-sliding' ); 3717 } 3718 3719 }); 3720 3721 }; 3722 3723 Guestures.prototype.onPan = function() { 3724 var self = this; 3725 3726 // Sometimes, when tapping causally, image can move a bit and that breaks double tapping 3727 if ( distance( self.newPoints[0], self.realPoints[0] ) < ($.fancybox.isMobile ? 10 : 5) ) { 3728 self.startPoints = self.newPoints; 3729 return; 3730 } 3731 3732 self.canTap = false; 3733 3734 self.contentLastPos = self.limitMovement(); 3735 3736 if ( self.requestId ) { 3737 cancelAFrame( self.requestId ); 3738 3739 self.requestId = null; 3740 } 3741 3742 self.requestId = requestAFrame(function() { 3743 $.fancybox.setTranslate( self.$content, self.contentLastPos ); 3744 }); 3745 }; 3746 3747 // Make panning sticky to the edges 3748 Guestures.prototype.limitMovement = function() { 3749 var self = this; 3750 3751 var canvasWidth = self.canvasWidth; 3752 var canvasHeight = self.canvasHeight; 3753 3754 var distanceX = self.distanceX; 3755 var distanceY = self.distanceY; 3756 3757 var contentStartPos = self.contentStartPos; 3758 3759 var currentOffsetX = contentStartPos.left; 3760 var currentOffsetY = contentStartPos.top; 3761 3762 var currentWidth = contentStartPos.width; 3763 var currentHeight = contentStartPos.height; 3764 3765 var minTranslateX, minTranslateY, 3766 maxTranslateX, maxTranslateY, 3767 newOffsetX, newOffsetY; 3768 3769 if ( currentWidth > canvasWidth ) { 3770 newOffsetX = currentOffsetX + distanceX; 3771 3772 } else { 3773 newOffsetX = currentOffsetX; 3774 } 3775 3776 newOffsetY = currentOffsetY + distanceY; 3777 3778 // Slow down proportionally to traveled distance 3779 minTranslateX = Math.max( 0, canvasWidth * 0.5 - currentWidth * 0.5 ); 3780 minTranslateY = Math.max( 0, canvasHeight * 0.5 - currentHeight * 0.5 ); 3781 3782 maxTranslateX = Math.min( canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5 ); 3783 maxTranslateY = Math.min( canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5 ); 3784 3785 if ( currentWidth > canvasWidth ) { 3786 3787 // -> 3788 if ( distanceX > 0 && newOffsetX > minTranslateX ) { 3789 newOffsetX = minTranslateX - 1 + Math.pow( -minTranslateX + currentOffsetX + distanceX, 0.8 ) || 0; 3790 } 3791 3792 // <- 3793 if ( distanceX < 0 && newOffsetX < maxTranslateX ) { 3794 newOffsetX = maxTranslateX + 1 - Math.pow( maxTranslateX - currentOffsetX - distanceX, 0.8 ) || 0; 3795 } 3796 3797 } 3798 3799 if ( currentHeight > canvasHeight ) { 3800 3801 // \/ 3802 if ( distanceY > 0 && newOffsetY > minTranslateY ) { 3803 newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8 ) || 0; 3804 } 3805 3806 // /\ 3807 if ( distanceY < 0 && newOffsetY < maxTranslateY ) { 3808 newOffsetY = maxTranslateY + 1 - Math.pow ( maxTranslateY - currentOffsetY - distanceY, 0.8 ) || 0; 3809 } 3810 3811 } 3812 3813 return { 3814 top : newOffsetY, 3815 left : newOffsetX, 3816 scaleX : contentStartPos.scaleX, 3817 scaleY : contentStartPos.scaleY 3818 }; 3819 3820 }; 3821 3822 Guestures.prototype.limitPosition = function( newOffsetX, newOffsetY, newWidth, newHeight ) { 3823 var self = this; 3824 3825 var canvasWidth = self.canvasWidth; 3826 var canvasHeight = self.canvasHeight; 3827 3828 if ( newWidth > canvasWidth ) { 3829 newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; 3830 newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; 3831 3832 } else { 3833 3834 // Center horizontally 3835 newOffsetX = Math.max( 0, canvasWidth / 2 - newWidth / 2 ); 3836 3837 } 3838 3839 if ( newHeight > canvasHeight ) { 3840 newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; 3841 newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; 3842 3843 } else { 3844 3845 // Center vertically 3846 newOffsetY = Math.max( 0, canvasHeight / 2 - newHeight / 2 ); 3847 3848 } 3849 3850 return { 3851 top : newOffsetY, 3852 left : newOffsetX 3853 }; 3854 3855 }; 3856 3857 Guestures.prototype.onZoom = function() { 3858 var self = this; 3859 3860 // Calculate current distance between points to get pinch ratio and new width and height 3861 3862 var currentWidth = self.contentStartPos.width; 3863 var currentHeight = self.contentStartPos.height; 3864 3865 var currentOffsetX = self.contentStartPos.left; 3866 var currentOffsetY = self.contentStartPos.top; 3867 3868 var endDistanceBetweenFingers = distance( self.newPoints[0], self.newPoints[1] ); 3869 3870 var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; 3871 3872 var newWidth = Math.floor( currentWidth * pinchRatio ); 3873 var newHeight = Math.floor( currentHeight * pinchRatio ); 3874 3875 // This is the translation due to pinch-zooming 3876 var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; 3877 var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; 3878 3879 //Point between the two touches 3880 3881 var centerPointEndX = ((self.newPoints[0].x + self.newPoints[1].x) / 2) - $(window).scrollLeft(); 3882 var centerPointEndY = ((self.newPoints[0].y + self.newPoints[1].y) / 2) - $(window).scrollTop(); 3883 3884 // And this is the translation due to translation of the centerpoint 3885 // between the two fingers 3886 3887 var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; 3888 var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; 3889 3890 // The new offset is the old/current one plus the total translation 3891 3892 var newOffsetX = currentOffsetX + ( translateFromZoomingX + translateFromTranslatingX ); 3893 var newOffsetY = currentOffsetY + ( translateFromZoomingY + translateFromTranslatingY ); 3894 3895 var newPos = { 3896 top : newOffsetY, 3897 left : newOffsetX, 3898 scaleX : self.contentStartPos.scaleX * pinchRatio, 3899 scaleY : self.contentStartPos.scaleY * pinchRatio 3900 }; 3901 3902 self.canTap = false; 3903 3904 self.newWidth = newWidth; 3905 self.newHeight = newHeight; 3906 3907 self.contentLastPos = newPos; 3908 3909 if ( self.requestId ) { 3910 cancelAFrame( self.requestId ); 3911 3912 self.requestId = null; 3913 } 3914 3915 self.requestId = requestAFrame(function() { 3916 $.fancybox.setTranslate( self.$content, self.contentLastPos ); 3917 }); 3918 3919 }; 3920 3921 Guestures.prototype.ontouchend = function( e ) { 3922 var self = this; 3923 var dMs = Math.max( (new Date().getTime() ) - self.startTime, 1); 3924 3925 var swiping = self.isSwiping; 3926 var panning = self.isPanning; 3927 var zooming = self.isZooming; 3928 var scrolling = self.isScrolling; 3929 3930 self.endPoints = pointers( e ); 3931 3932 self.$container.removeClass( 'fancybox-controls--isGrabbing' ); 3933 3934 $(document).off( '.fb.touch' ); 3935 3936 document.removeEventListener('scroll', self.onscroll, true); 3937 3938 if ( self.requestId ) { 3939 cancelAFrame( self.requestId ); 3940 3941 self.requestId = null; 3942 } 3943 3944 self.isSwiping = false; 3945 self.isPanning = false; 3946 self.isZooming = false; 3947 self.isScrolling = false; 3948 3949 self.instance.isDragging = false; 3950 3951 if ( self.canTap ) { 3952 return self.onTap( e ); 3953 } 3954 3955 self.speed = 366; 3956 3957 // Speed in px/ms 3958 self.velocityX = self.distanceX / dMs * 0.5; 3959 self.velocityY = self.distanceY / dMs * 0.5; 3960 3961 self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) ); 3962 3963 if ( panning ) { 3964 self.endPanning(); 3965 3966 } else if ( zooming ) { 3967 self.endZooming(); 3968 3969 } else { 3970 self.endSwiping( swiping, scrolling ); 3971 } 3972 3973 return; 3974 }; 3975 3976 Guestures.prototype.endSwiping = function( swiping, scrolling ) { 3977 var self = this, 3978 ret = false, 3979 len = self.instance.group.length; 3980 3981 self.sliderLastPos = null; 3982 3983 // Close if swiped vertically / navigate if horizontally 3984 if ( swiping == 'y' && !scrolling && Math.abs( self.distanceY ) > 50 ) { 3985 3986 // Continue vertical movement 3987 $.fancybox.animate( self.instance.current.$slide, { 3988 top : self.sliderStartPos.top + self.distanceY + ( self.velocityY * 150 ), 3989 opacity : 0 3990 }, 150 ); 3991 3992 ret = self.instance.close( true, 300 ); 3993 3994 } else if ( swiping == 'x' && self.distanceX > 50 && len > 1 ) { 3995 ret = self.instance.previous( self.speedX ); 3996 3997 } else if ( swiping == 'x' && self.distanceX < -50 && len > 1 ) { 3998 ret = self.instance.next( self.speedX ); 3999 } 4000 4001 if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) { 4002 if ( scrolling || len < 2 ) { 4003 self.instance.centerSlide( self.instance.current, 150 ); 4004 } else { 4005 self.instance.jumpTo( self.instance.current.index ); 4006 } 4007 } 4008 4009 self.$container.removeClass( 'fancybox-is-sliding' ); 4010 4011 }; 4012 4013 // Limit panning from edges 4014 // ======================== 4015 4016 Guestures.prototype.endPanning = function() { 4017 4018 var self = this; 4019 var newOffsetX, newOffsetY, newPos; 4020 4021 if ( !self.contentLastPos ) { 4022 return; 4023 } 4024 4025 if ( self.opts.momentum === false ) { 4026 newOffsetX = self.contentLastPos.left; 4027 newOffsetY = self.contentLastPos.top; 4028 4029 } else { 4030 4031 // Continue movement 4032 newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed ); 4033 newOffsetY = self.contentLastPos.top + ( self.velocityY * self.speed ); 4034 } 4035 4036 newPos = self.limitPosition( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); 4037 4038 newPos.width = self.contentStartPos.width; 4039 newPos.height = self.contentStartPos.height; 4040 4041 $.fancybox.animate( self.$content, newPos, 330 ); 4042 }; 4043 4044 4045 Guestures.prototype.endZooming = function() { 4046 var self = this; 4047 4048 var current = self.instance.current; 4049 4050 var newOffsetX, newOffsetY, newPos, reset; 4051 4052 var newWidth = self.newWidth; 4053 var newHeight = self.newHeight; 4054 4055 if ( !self.contentLastPos ) { 4056 return; 4057 } 4058 4059 newOffsetX = self.contentLastPos.left; 4060 newOffsetY = self.contentLastPos.top; 4061 4062 reset = { 4063 top : newOffsetY, 4064 left : newOffsetX, 4065 width : newWidth, 4066 height : newHeight, 4067 scaleX : 1, 4068 scaleY : 1 4069 }; 4070 4071 // Reset scalex/scaleY values; this helps for perfomance and does not break animation 4072 $.fancybox.setTranslate( self.$content, reset ); 4073 4074 if ( newWidth < self.canvasWidth && newHeight < self.canvasHeight ) { 4075 self.instance.scaleToFit( 150 ); 4076 4077 } else if ( newWidth > current.width || newHeight > current.height ) { 4078 self.instance.scaleToActual( self.centerPointStartX, self.centerPointStartY, 150 ); 4079 4080 } else { 4081 4082 newPos = self.limitPosition( newOffsetX, newOffsetY, newWidth, newHeight ); 4083 4084 // Switch from scale() to width/height or animation will not work correctly 4085 $.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) ); 4086 4087 $.fancybox.animate( self.$content, newPos, 150 ); 4088 } 4089 4090 }; 4091 4092 Guestures.prototype.onTap = function(e) { 4093 var self = this; 4094 var $target = $( e.target ); 4095 4096 var instance = self.instance; 4097 var current = instance.current; 4098 4099 var endPoints = ( e && pointers( e ) ) || self.startPoints; 4100 4101 var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0; 4102 var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0; 4103 4104 var where; 4105 4106 var process = function ( prefix ) { 4107 4108 var action = current.opts[ prefix ]; 4109 4110 if ( $.isFunction( action ) ) { 4111 action = action.apply( instance, [ current, e ] ); 4112 } 4113 4114 if ( !action) { 4115 return; 4116 } 4117 4118 switch ( action ) { 4119 4120 case "close" : 4121 4122 instance.close( self.startEvent ); 4123 4124 break; 4125 4126 case "toggleControls" : 4127 4128 instance.toggleControls( true ); 4129 4130 break; 4131 4132 case "next" : 4133 4134 instance.next(); 4135 4136 break; 4137 4138 case "nextOrClose" : 4139 4140 if ( instance.group.length > 1 ) { 4141 instance.next(); 4142 4143 } else { 4144 instance.close( self.startEvent ); 4145 } 4146 4147 break; 4148 4149 case "zoom" : 4150 4151 if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) { 4152 4153 if ( instance.canPan() ) { 4154 instance.scaleToFit(); 4155 4156 } else if ( instance.isScaledDown() ) { 4157 instance.scaleToActual( tapX, tapY ); 4158 4159 } else if ( instance.group.length < 2 ) { 4160 instance.close( self.startEvent ); 4161 } 4162 } 4163 4164 break; 4165 } 4166 4167 }; 4168 4169 // Ignore right click 4170 if ( e.originalEvent && e.originalEvent.button == 2 ) { 4171 return; 4172 } 4173 4174 // Skip if clicked on the scrollbar 4175 if ( !$target.is('img') && tapX > $target[0].clientWidth + $target.offset().left ) { 4176 return; 4177 } 4178 4179 // Check where is clicked 4180 if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) { 4181 where = 'Outside'; 4182 4183 } else if ( $target.is( '.fancybox-slide' ) ) { 4184 where = 'Slide'; 4185 4186 } else if ( instance.current.$content && instance.current.$content.find( $target ).addBack().filter( $target ).length ) { 4187 where = 'Content'; 4188 4189 } else { 4190 return; 4191 } 4192 4193 // Check if this is a double tap 4194 if ( self.tapped ) { 4195 4196 // Stop previously created single tap 4197 clearTimeout( self.tapped ); 4198 self.tapped = null; 4199 4200 // Skip if distance between taps is too big 4201 if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 ) { 4202 return this; 4203 } 4204 4205 // OK, now we assume that this is a double-tap 4206 process( 'dblclick' + where ); 4207 4208 } else { 4209 4210 // Single tap will be processed if user has not clicked second time within 300ms 4211 // or there is no need to wait for double-tap 4212 self.tapX = tapX; 4213 self.tapY = tapY; 4214 4215 if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) { 4216 4217 self.tapped = setTimeout(function() { 4218 self.tapped = null; 4219 4220 process( 'click' + where ); 4221 4222 }, 500); 4223 4224 } else { 4225 process( 'click' + where ); 4226 } 4227 4228 } 4229 4230 return this; 4231 }; 4232 4233 $(document).on('onActivate.fb', function (e, instance) { 4234 if ( instance && !instance.Guestures ) { 4235 instance.Guestures = new Guestures( instance ); 4236 } 4237 }); 4238 4239 }( window, document, window.jQuery || jQuery )); 4240 4241 // ========================================================================== 4242 // 4243 // SlideShow 4244 // Enables slideshow functionality 4245 // 4246 // Example of usage: 4247 // $.fancybox.getInstance().SlideShow.start() 4248 // 4249 // ========================================================================== 4250 ;(function (document, $) { 4251 'use strict'; 4252 4253 $.extend(true, $.fancybox.defaults, { 4254 btnTpl : { 4255 slideShow : 4256 '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}">' + 4257 '<svg viewBox="0 0 40 40">' + 4258 '<path d="M13,12 L27,20 L13,27 Z" />' + 4259 '<path d="M15,10 v19 M23,10 v19" />' + 4260 '</svg>' + 4261 '</button>' 4262 }, 4263 slideShow : { 4264 autoStart : false, 4265 speed : 3000 4266 } 4267 }); 4268 4269 var SlideShow = function( instance ) { 4270 this.instance = instance; 4271 this.init(); 4272 }; 4273 4274 $.extend( SlideShow.prototype, { 4275 timer : null, 4276 isActive : false, 4277 $button : null, 4278 4279 init : function() { 4280 var self = this; 4281 4282 self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function() { 4283 self.toggle(); 4284 }); 4285 4286 if ( self.instance.group.length < 2 || !self.instance.group[ self.instance.currIndex ].opts.slideShow ) { 4287 self.$button.hide(); 4288 } 4289 }, 4290 4291 set : function( force ) { 4292 var self = this; 4293 4294 // Check if reached last element 4295 if ( self.instance && self.instance.current && (force === true || self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1 )) { 4296 self.timer = setTimeout(function() { 4297 if ( self.isActive ) { 4298 self.instance.jumpTo( (self.instance.currIndex + 1) % self.instance.group.length ); 4299 } 4300 4301 }, self.instance.current.opts.slideShow.speed); 4302 4303 } else { 4304 self.stop(); 4305 self.instance.idleSecondsCounter = 0; 4306 self.instance.showControls(); 4307 } 4308 }, 4309 4310 clear : function() { 4311 var self = this; 4312 4313 clearTimeout( self.timer ); 4314 4315 self.timer = null; 4316 }, 4317 4318 start : function() { 4319 var self = this; 4320 var current = self.instance.current; 4321 4322 if ( current ) { 4323 self.isActive = true; 4324 4325 self.$button 4326 .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_STOP ) 4327 .removeClass( 'fancybox-button--play' ) 4328 .addClass( 'fancybox-button--pause' ); 4329 4330 self.set( true ); 4331 } 4332 }, 4333 4334 stop : function() { 4335 var self = this; 4336 var current = self.instance.current; 4337 4338 self.clear(); 4339 4340 self.$button 4341 .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_START ) 4342 .removeClass( 'fancybox-button--pause' ) 4343 .addClass( 'fancybox-button--play' ); 4344 4345 self.isActive = false; 4346 }, 4347 4348 toggle : function() { 4349 var self = this; 4350 4351 if ( self.isActive ) { 4352 self.stop(); 4353 4354 } else { 4355 self.start(); 4356 } 4357 } 4358 4359 }); 4360 4361 $(document).on({ 4362 'onInit.fb' : function(e, instance) { 4363 if ( instance && !instance.SlideShow ) { 4364 instance.SlideShow = new SlideShow( instance ); 4365 } 4366 }, 4367 4368 'beforeShow.fb' : function(e, instance, current, firstRun) { 4369 var SlideShow = instance && instance.SlideShow; 4370 4371 if ( firstRun ) { 4372 4373 if ( SlideShow && current.opts.slideShow.autoStart ) { 4374 SlideShow.start(); 4375 } 4376 4377 } else if ( SlideShow && SlideShow.isActive ) { 4378 SlideShow.clear(); 4379 } 4380 }, 4381 4382 'afterShow.fb' : function(e, instance, current) { 4383 var SlideShow = instance && instance.SlideShow; 4384 4385 if ( SlideShow && SlideShow.isActive ) { 4386 SlideShow.set(); 4387 } 4388 }, 4389 4390 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4391 var SlideShow = instance && instance.SlideShow; 4392 4393 // "P" or Spacebar 4394 if ( SlideShow && current.opts.slideShow && ( keycode === 80 || keycode === 32 ) && !$(document.activeElement).is( 'button,a,input' ) ) { 4395 keypress.preventDefault(); 4396 4397 SlideShow.toggle(); 4398 } 4399 }, 4400 4401 'beforeClose.fb onDeactivate.fb' : function(e, instance) { 4402 var SlideShow = instance && instance.SlideShow; 4403 4404 if ( SlideShow ) { 4405 SlideShow.stop(); 4406 } 4407 } 4408 }); 4409 4410 // Page Visibility API to pause slideshow when window is not active 4411 $(document).on("visibilitychange", function() { 4412 var instance = $.fancybox.getInstance(); 4413 var SlideShow = instance && instance.SlideShow; 4414 4415 if ( SlideShow && SlideShow.isActive ) { 4416 if ( document.hidden ) { 4417 SlideShow.clear(); 4418 4419 } else { 4420 SlideShow.set(); 4421 } 4422 } 4423 }); 4424 4425 }( document, window.jQuery || jQuery )); 4426 4427 // ========================================================================== 4428 // 4429 // FullScreen 4430 // Adds fullscreen functionality 4431 // 4432 // ========================================================================== 4433 ;(function (document, $) { 4434 'use strict'; 4435 4436 // Collection of methods supported by user browser 4437 var fn = (function () { 4438 4439 var fnMap = [ 4440 [ 4441 'requestFullscreen', 4442 'exitFullscreen', 4443 'fullscreenElement', 4444 'fullscreenEnabled', 4445 'fullscreenchange', 4446 'fullscreenerror' 4447 ], 4448 // new WebKit 4449 [ 4450 'webkitRequestFullscreen', 4451 'webkitExitFullscreen', 4452 'webkitFullscreenElement', 4453 'webkitFullscreenEnabled', 4454 'webkitfullscreenchange', 4455 'webkitfullscreenerror' 4456 4457 ], 4458 // old WebKit (Safari 5.1) 4459 [ 4460 'webkitRequestFullScreen', 4461 'webkitCancelFullScreen', 4462 'webkitCurrentFullScreenElement', 4463 'webkitCancelFullScreen', 4464 'webkitfullscreenchange', 4465 'webkitfullscreenerror' 4466 4467 ], 4468 [ 4469 'mozRequestFullScreen', 4470 'mozCancelFullScreen', 4471 'mozFullScreenElement', 4472 'mozFullScreenEnabled', 4473 'mozfullscreenchange', 4474 'mozfullscreenerror' 4475 ], 4476 [ 4477 'msRequestFullscreen', 4478 'msExitFullscreen', 4479 'msFullscreenElement', 4480 'msFullscreenEnabled', 4481 'MSFullscreenChange', 4482 'MSFullscreenError' 4483 ] 4484 ]; 4485 4486 var val; 4487 var ret = {}; 4488 var i, j; 4489 4490 for ( i = 0; i < fnMap.length; i++ ) { 4491 val = fnMap[ i ]; 4492 4493 if ( val && val[ 1 ] in document ) { 4494 for ( j = 0; j < val.length; j++ ) { 4495 ret[ fnMap[ 0 ][ j ] ] = val[ j ]; 4496 } 4497 4498 return ret; 4499 } 4500 } 4501 4502 return false; 4503 })(); 4504 4505 // If browser does not have Full Screen API, then simply unset default button template and stop 4506 if ( !fn ) { 4507 4508 if ( $ && $.fancybox ) { 4509 $.fancybox.defaults.btnTpl.fullScreen = false; 4510 } 4511 4512 return; 4513 } 4514 4515 var FullScreen = { 4516 4517 request : function ( elem ) { 4518 4519 elem = elem || document.documentElement; 4520 4521 elem[ fn.requestFullscreen ]( elem.ALLOW_KEYBOARD_INPUT ); 4522 4523 }, 4524 exit : function () { 4525 4526 document[ fn.exitFullscreen ](); 4527 4528 }, 4529 toggle : function ( elem ) { 4530 4531 elem = elem || document.documentElement; 4532 4533 if ( this.isFullscreen() ) { 4534 this.exit(); 4535 4536 } else { 4537 this.request( elem ); 4538 } 4539 4540 }, 4541 isFullscreen : function() { 4542 4543 return Boolean( document[ fn.fullscreenElement ] ); 4544 4545 }, 4546 enabled : function() { 4547 4548 return Boolean( document[ fn.fullscreenEnabled ] ); 4549 4550 } 4551 }; 4552 4553 $.extend(true, $.fancybox.defaults, { 4554 btnTpl : { 4555 fullScreen : 4556 '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fullscreen" title="{{FULL_SCREEN}}">' + 4557 '<svg viewBox="0 0 40 40">' + 4558 '<path d="M9,12 h22 v16 h-22 v-16 v16 h22 v-16 Z" />' + 4559 '</svg>' + 4560 '</button>' 4561 }, 4562 fullScreen : { 4563 autoStart : false 4564 } 4565 }); 4566 4567 $(document).on({ 4568 'onInit.fb' : function(e, instance) { 4569 var $container; 4570 4571 if ( instance && instance.group[ instance.currIndex ].opts.fullScreen ) { 4572 $container = instance.$refs.container; 4573 4574 $container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function(e) { 4575 4576 e.stopPropagation(); 4577 e.preventDefault(); 4578 4579 FullScreen.toggle( $container[ 0 ] ); 4580 4581 }); 4582 4583 if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) { 4584 FullScreen.request( $container[ 0 ] ); 4585 } 4586 4587 // Expose API 4588 instance.FullScreen = FullScreen; 4589 4590 } else if ( instance ) { 4591 instance.$refs.toolbar.find('[data-fancybox-fullscreen]').hide(); 4592 } 4593 4594 }, 4595 4596 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4597 4598 // "P" or Spacebar 4599 if ( instance && instance.FullScreen && keycode === 70 ) { 4600 keypress.preventDefault(); 4601 4602 instance.FullScreen.toggle( instance.$refs.container[ 0 ] ); 4603 } 4604 4605 }, 4606 4607 'beforeClose.fb' : function( instance ) { 4608 if ( instance && instance.FullScreen ) { 4609 FullScreen.exit(); 4610 } 4611 } 4612 }); 4613 4614 $(document).on(fn.fullscreenchange, function() { 4615 var isFullscreen = FullScreen.isFullscreen(), 4616 instance = $.fancybox.getInstance(); 4617 4618 if ( instance ) { 4619 4620 // If image is zooming, then force to stop and reposition properly 4621 if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) { 4622 instance.current.$content.css( 'transition', 'none' ); 4623 4624 instance.isAnimating = false; 4625 4626 instance.update( true, true, 0 ); 4627 } 4628 4629 instance.trigger( 'onFullscreenChange', isFullscreen ); 4630 4631 instance.$refs.container.toggleClass( 'fancybox-is-fullscreen', isFullscreen ); 4632 } 4633 4634 }); 4635 4636 }( document, window.jQuery || jQuery )); 4637 4638 // ========================================================================== 4639 // 4640 // Thumbs 4641 // Displays thumbnails in a grid 4642 // 4643 // ========================================================================== 4644 ;(function (document, $) { 4645 'use strict'; 4646 4647 // Make sure there are default values 4648 $.fancybox.defaults = $.extend(true, { 4649 btnTpl : { 4650 thumbs : 4651 '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}">' + 4652 '<svg viewBox="0 0 120 120">' + 4653 '<path d="M30,30 h14 v14 h-14 Z M50,30 h14 v14 h-14 Z M70,30 h14 v14 h-14 Z M30,50 h14 v14 h-14 Z M50,50 h14 v14 h-14 Z M70,50 h14 v14 h-14 Z M30,70 h14 v14 h-14 Z M50,70 h14 v14 h-14 Z M70,70 h14 v14 h-14 Z" />' + 4654 '</svg>' + 4655 '</button>' 4656 }, 4657 thumbs : { 4658 autoStart : false, // Display thumbnails on opening 4659 hideOnClose : true, // Hide thumbnail grid when closing animation starts 4660 parentEl : '.fancybox-container', // Container is injected into this element 4661 axis : 'y' // Vertical (y) or horizontal (x) scrolling 4662 } 4663 }, $.fancybox.defaults); 4664 4665 var FancyThumbs = function( instance ) { 4666 this.init( instance ); 4667 }; 4668 4669 $.extend( FancyThumbs.prototype, { 4670 4671 $button : null, 4672 $grid : null, 4673 $list : null, 4674 isVisible : false, 4675 isActive : false, 4676 4677 init : function( instance ) { 4678 var self = this; 4679 4680 self.instance = instance; 4681 4682 instance.Thumbs = self; 4683 4684 // Enable thumbs if at least two group items have thumbnails 4685 var first = instance.group[0], 4686 second = instance.group[1]; 4687 4688 self.opts = instance.group[ instance.currIndex ].opts.thumbs; 4689 4690 self.$button = instance.$refs.toolbar.find( '[data-fancybox-thumbs]' ); 4691 4692 if ( self.opts && first && second && ( 4693 ( first.type == 'image' || first.opts.thumb || first.opts.$thumb ) && 4694 ( second.type == 'image' || second.opts.thumb || second.opts.$thumb ) 4695 )) { 4696 4697 self.$button.show().on('click', function() { 4698 self.toggle(); 4699 }); 4700 4701 self.isActive = true; 4702 4703 } else { 4704 self.$button.hide(); 4705 } 4706 }, 4707 4708 create : function() { 4709 var self = this, 4710 instance = self.instance, 4711 parentEl = self.opts.parentEl, 4712 list, 4713 src; 4714 4715 self.$grid = $('<div class="fancybox-thumbs fancybox-thumbs-' + self.opts.axis + '"></div>').appendTo( instance.$refs.container.find( parentEl ).addBack().filter( parentEl ) ); 4716 4717 // Build list HTML 4718 list = '<ul>'; 4719 4720 $.each(instance.group, function( i, item ) { 4721 src = item.opts.thumb || ( item.opts.$thumb ? item.opts.$thumb.attr( 'src' ) : null ); 4722 4723 if ( !src && item.type === 'image' ) { 4724 src = item.src; 4725 } 4726 4727 if ( src && src.length ) { 4728 list += '<li data-index="' + i + '" tabindex="0" class="fancybox-thumbs-loading"><img data-src="' + src + '" /></li>'; 4729 } 4730 }); 4731 4732 list += '</ul>'; 4733 4734 self.$list = $( list ).appendTo( self.$grid ).on('click', 'li', function() { 4735 instance.jumpTo( $(this).data('index') ); 4736 }); 4737 4738 self.$list.find( 'img' ).hide().one('load', function() { 4739 var $parent = $(this).parent().removeClass( 'fancybox-thumbs-loading' ), 4740 thumbWidth = $parent.outerWidth(), 4741 thumbHeight = $parent.outerHeight(), 4742 width, 4743 height, 4744 widthRatio, 4745 heightRatio; 4746 4747 width = this.naturalWidth || this.width; 4748 height = this.naturalHeight || this.height; 4749 4750 // Calculate thumbnail dimensions; center vertically and horizontally 4751 widthRatio = width / thumbWidth; 4752 heightRatio = height / thumbHeight; 4753 4754 if (widthRatio >= 1 && heightRatio >= 1) { 4755 if (widthRatio > heightRatio) { 4756 width = width / heightRatio; 4757 height = thumbHeight; 4758 4759 } else { 4760 width = thumbWidth; 4761 height = height / widthRatio; 4762 } 4763 } 4764 4765 $(this).css({ 4766 width : Math.floor(width), 4767 height : Math.floor(height), 4768 'margin-top' : height > thumbHeight ? ( Math.floor(thumbHeight * 0.3 - height * 0.3 ) ) : Math.floor(thumbHeight * 0.5 - height * 0.5 ), 4769 'margin-left' : Math.floor(thumbWidth * 0.5 - width * 0.5 ) 4770 }).show(); 4771 4772 }) 4773 .each(function() { 4774 this.src = $( this ).data( 'src' ); 4775 }); 4776 4777 if ( self.opts.axis === 'x' ) { 4778 self.$list.width( parseInt( self.$grid.css("padding-right") ) + ( instance.group.length * self.$list.children().eq(0).outerWidth(true) ) + 'px' ); 4779 } 4780 }, 4781 4782 focus : function( duration ) { 4783 var self = this, 4784 $list = self.$list, 4785 thumb, 4786 thumbPos; 4787 4788 if ( self.instance.current ) { 4789 thumb = $list.children() 4790 .removeClass( 'fancybox-thumbs-active' ) 4791 .filter('[data-index="' + self.instance.current.index + '"]') 4792 .addClass('fancybox-thumbs-active'); 4793 4794 thumbPos = thumb.position(); 4795 4796 // Check if need to scroll to make current thumb visible 4797 if ( self.opts.axis === 'y' && ( thumbPos.top < 0 || thumbPos.top > ( $list.height() - thumb.outerHeight() ) ) ) { 4798 $list.stop().animate({ 'scrollTop' : $list.scrollTop() + thumbPos.top }, duration); 4799 4800 } else if ( self.opts.axis === 'x' && ( 4801 thumbPos.left < $list.parent().scrollLeft() || 4802 thumbPos.left > ( $list.parent().scrollLeft() + ( $list.parent().width() - thumb.outerWidth() ) ) 4803 ) 4804 ) { 4805 $list.parent().stop().animate({ 'scrollLeft' : thumbPos.left }, duration); 4806 } 4807 } 4808 }, 4809 4810 update : function() { 4811 this.instance.$refs.container.toggleClass( 'fancybox-show-thumbs', this.isVisible ); 4812 4813 if ( this.isVisible ) { 4814 if ( !this.$grid ) { 4815 this.create(); 4816 } 4817 4818 this.instance.trigger( 'onThumbsShow' ); 4819 4820 this.focus( 0 ); 4821 4822 } else if ( this.$grid ) { 4823 this.instance.trigger( 'onThumbsHide' ); 4824 } 4825 4826 // Update content position 4827 this.instance.update(); 4828 }, 4829 4830 hide : function() { 4831 this.isVisible = false; 4832 this.update(); 4833 }, 4834 4835 show : function() { 4836 this.isVisible = true; 4837 this.update(); 4838 }, 4839 4840 toggle : function() { 4841 this.isVisible = !this.isVisible; 4842 this.update(); 4843 } 4844 }); 4845 4846 $(document).on({ 4847 4848 'onInit.fb' : function(e, instance) { 4849 var Thumbs; 4850 4851 if ( instance && !instance.Thumbs ) { 4852 Thumbs = new FancyThumbs( instance ); 4853 4854 if ( Thumbs.isActive && Thumbs.opts.autoStart === true ) { 4855 Thumbs.show(); 4856 } 4857 } 4858 }, 4859 4860 'beforeShow.fb' : function(e, instance, item, firstRun) { 4861 var Thumbs = instance && instance.Thumbs; 4862 4863 if ( Thumbs && Thumbs.isVisible ) { 4864 Thumbs.focus( firstRun ? 0 : 250 ); 4865 } 4866 }, 4867 4868 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4869 var Thumbs = instance && instance.Thumbs; 4870 4871 // "G" 4872 if ( Thumbs && Thumbs.isActive && keycode === 71 ) { 4873 keypress.preventDefault(); 4874 4875 Thumbs.toggle(); 4876 } 4877 }, 4878 4879 'beforeClose.fb' : function( e, instance ) { 4880 var Thumbs = instance && instance.Thumbs; 4881 4882 if ( Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false ) { 4883 Thumbs.$grid.hide(); 4884 } 4885 } 4886 4887 }); 4888 4889 }(document, window.jQuery)); 4890 4891 //// ========================================================================== 4892 // 4893 // Share 4894 // Displays simple form for sharing current url 4895 // 4896 // ========================================================================== 4897 ;(function (document, $) { 4898 'use strict'; 4899 4900 $.extend(true, $.fancybox.defaults, { 4901 btnTpl : { 4902 share : 4903 '<button data-fancybox-share class="fancybox-button fancybox-button--share" title="{{SHARE}}">' + 4904 '<svg viewBox="0 0 40 40">' + 4905 '<path d="M6,30 C8,18 19,16 23,16 L23,16 L23,10 L33,20 L23,29 L23,24 C19,24 8,27 6,30 Z">' + 4906 '</svg>' + 4907 '</button>' 4908 }, 4909 share : { 4910 tpl : 4911 '<div class="fancybox-share">' + 4912 '<h1>{{SHARE}}</h1>' + 4913 '<p class="fancybox-share__links">' + 4914 '<a class="fancybox-share__button fancybox-share__button--fb" href="https://www.facebook.com/sharer/sharer.php?u={{url}}">' + 4915 '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m287 456v-299c0-21 6-35 35-35h38v-63c-7-1-29-3-55-3-54 0-91 33-91 94v306m143-254h-205v72h196" /></svg>' + 4916 '<span>Facebook</span>' + 4917 '</a>' + 4918 '<a class="fancybox-share__button fancybox-share__button--pt" href="https://www.pinterest.com/pin/create/button/?url={{url}}&description={{descr}}&media={{media}}">' + 4919 '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m265 56c-109 0-164 78-164 144 0 39 15 74 47 87 5 2 10 0 12-5l4-19c2-6 1-8-3-13-9-11-15-25-15-45 0-58 43-110 113-110 62 0 96 38 96 88 0 67-30 122-73 122-24 0-42-19-36-44 6-29 20-60 20-81 0-19-10-35-31-35-25 0-44 26-44 60 0 21 7 36 7 36l-30 125c-8 37-1 83 0 87 0 3 4 4 5 2 2-3 32-39 42-75l16-64c8 16 31 29 56 29 74 0 124-67 124-157 0-69-58-132-146-132z" fill="#fff"/></svg>' + 4920 '<span>Pinterest</span>' + 4921 '</a>' + 4922 '<a class="fancybox-share__button fancybox-share__button--tw" href="https://twitter.com/intent/tweet?url={{url}}&text={{descr}}">' + 4923 '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m456 133c-14 7-31 11-47 13 17-10 30-27 37-46-15 10-34 16-52 20-61-62-157-7-141 75-68-3-129-35-169-85-22 37-11 86 26 109-13 0-26-4-37-9 0 39 28 72 65 80-12 3-25 4-37 2 10 33 41 57 77 57-42 30-77 38-122 34 170 111 378-32 359-208 16-11 30-25 41-42z" /></svg>' + 4924 '<span>Twitter</span>' + 4925 '</a>' + 4926 '</p>' + 4927 '<p><input class="fancybox-share__input" type="text" value="{{url_raw}}" /></p>' + 4928 '</div>' 4929 } 4930 }); 4931 4932 function escapeHtml(string) { 4933 var entityMap = { 4934 '&': '&', 4935 '<': '<', 4936 '>': '>', 4937 '"': '"', 4938 "'": ''', 4939 '/': '/', 4940 '`': '`', 4941 '=': '=' 4942 }; 4943 4944 return String(string).replace(/[&<>"'`=\/]/g, function (s) { 4945 return entityMap[s]; 4946 }); 4947 } 4948 4949 $(document).on('click', '[data-fancybox-share]', function() { 4950 var f = $.fancybox.getInstance(), 4951 url, 4952 tpl; 4953 4954 if ( f ) { 4955 url = f.current.opts.hash === false ? f.current.src : window.location; 4956 tpl = f.current.opts.share.tpl 4957 .replace( /\{\{media\}\}/g, f.current.type === 'image' ? encodeURIComponent( f.current.src ) : '' ) 4958 .replace( /\{\{url\}\}/g, encodeURIComponent( url ) ) 4959 .replace( /\{\{url_raw\}\}/g, escapeHtml( url ) ) 4960 .replace( /\{\{descr\}\}/g, f.$caption ? encodeURIComponent( f.$caption.text() ) : '' ); 4961 4962 $.fancybox.open({ 4963 src : f.translate( f, tpl ), 4964 type : 'html', 4965 opts : { 4966 animationEffect : "fade", 4967 animationDuration : 250, 4968 afterLoad : function(instance, current) { 4969 // Opening links in a popup window 4970 current.$content.find('.fancybox-share__links a').click(function() { 4971 window.open(this.href, "Share", "width=550, height=450"); 4972 return false; 4973 }); 4974 } 4975 } 4976 }); 4977 } 4978 4979 }); 4980 4981 }( document, window.jQuery || jQuery )); 4982 4983 // ========================================================================== 4984 // 4985 // Hash 4986 // Enables linking to each modal 4987 // 4988 // ========================================================================== 4989 ;(function (document, window, $) { 4990 'use strict'; 4991 4992 // Simple $.escapeSelector polyfill (for jQuery prior v3) 4993 if ( !$.escapeSelector ) { 4994 $.escapeSelector = function( sel ) { 4995 var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; 4996 var fcssescape = function( ch, asCodePoint ) { 4997 if ( asCodePoint ) { 4998 // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER 4999 if ( ch === "\0" ) { 5000 return "\uFFFD"; 5001 } 5002 5003 // Control characters and (dependent upon position) numbers get escaped as code points 5004 return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; 5005 } 5006 5007 // Other potentially-special ASCII characters get backslash-escaped 5008 return "\\" + ch; 5009 }; 5010 5011 return ( sel + "" ).replace( rcssescape, fcssescape ); 5012 }; 5013 } 5014 5015 // Create new history entry only once 5016 var shouldCreateHistory = true; 5017 5018 // Variable containing last hash value set by fancyBox 5019 // It will be used to determine if fancyBox needs to close after hash change is detected 5020 var currentHash = null; 5021 5022 // Throttling the history change 5023 var timerID = null; 5024 5025 // Get info about gallery name and current index from url 5026 function parseUrl() { 5027 var hash = window.location.hash.substr( 1 ); 5028 var rez = hash.split( '-' ); 5029 var index = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1; 5030 var gallery = rez.join( '-' ); 5031 5032 // Index is starting from 1 5033 if ( index < 1 ) { 5034 index = 1; 5035 } 5036 5037 return { 5038 hash : hash, 5039 index : index, 5040 gallery : gallery 5041 }; 5042 } 5043 5044 // Trigger click evnt on links to open new fancyBox instance 5045 function triggerFromUrl( url ) { 5046 var $el; 5047 5048 if ( url.gallery !== '' ) { 5049 5050 // If we can find element matching 'data-fancybox' atribute, then trigger click event for that .. 5051 $el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 ); 5052 5053 if ( !$el.length ) { 5054 // .. if not, try finding element by ID 5055 $el = $( "#" + $.escapeSelector( url.gallery ) + "" ); 5056 } 5057 5058 if ( $el.length ) { 5059 shouldCreateHistory = false; 5060 5061 $el.trigger( 'click' ); 5062 } 5063 5064 } 5065 } 5066 5067 // Get gallery name from current instance 5068 function getGalleryID( instance ) { 5069 var opts; 5070 5071 if ( !instance ) { 5072 return false; 5073 } 5074 5075 opts = instance.current ? instance.current.opts : instance.opts; 5076 5077 return opts.hash || ( opts.$orig ? opts.$orig.data( 'fancybox' ) : '' ); 5078 } 5079 5080 // Start when DOM becomes ready 5081 $(function() { 5082 5083 // Check if user has disabled this module 5084 if ( $.fancybox.defaults.hash === false ) { 5085 return; 5086 } 5087 5088 // Update hash when opening/closing fancyBox 5089 $(document).on({ 5090 'onInit.fb' : function( e, instance ) { 5091 var url, gallery; 5092 5093 if ( instance.group[ instance.currIndex ].opts.hash === false ) { 5094 return; 5095 } 5096 5097 url = parseUrl(); 5098 gallery = getGalleryID( instance ); 5099 5100 // Make sure gallery start index matches index from hash 5101 if ( gallery && url.gallery && gallery == url.gallery ) { 5102 instance.currIndex = url.index - 1; 5103 } 5104 }, 5105 5106 'beforeShow.fb' : function( e, instance, current ) { 5107 var gallery; 5108 5109 if ( !current || current.opts.hash === false ) { 5110 return; 5111 } 5112 5113 gallery = getGalleryID( instance ); 5114 5115 // Update window hash 5116 if ( gallery && gallery !== '' ) { 5117 5118 if ( window.location.hash.indexOf( gallery ) < 0 ) { 5119 instance.opts.origHash = window.location.hash; 5120 } 5121 5122 currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' ); 5123 5124 if ( 'replaceState' in window.history ) { 5125 if ( timerID ) { 5126 clearTimeout( timerID ); 5127 } 5128 5129 timerID = setTimeout(function() { 5130 window.history[ shouldCreateHistory ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' + currentHash ); 5131 5132 timerID = null; 5133 5134 shouldCreateHistory = false; 5135 5136 }, 300); 5137 5138 } else { 5139 window.location.hash = currentHash; 5140 } 5141 5142 } 5143 5144 }, 5145 5146 'beforeClose.fb' : function( e, instance, current ) { 5147 var gallery, origHash; 5148 5149 if ( timerID ) { 5150 clearTimeout( timerID ); 5151 } 5152 5153 if ( current.opts.hash === false ) { 5154 return; 5155 } 5156 5157 gallery = getGalleryID( instance ); 5158 origHash = instance && instance.opts.origHash ? instance.opts.origHash : ''; 5159 5160 // Remove hash from location bar 5161 if ( gallery && gallery !== '' ) { 5162 5163 if ( 'replaceState' in history ) { 5164 window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash ); 5165 5166 } else { 5167 window.location.hash = origHash; 5168 5169 // Keep original scroll position 5170 $( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft ); 5171 } 5172 } 5173 5174 currentHash = null; 5175 } 5176 }); 5177 5178 // Check if need to close after url has changed 5179 $(window).on('hashchange.fb', function() { 5180 var url = parseUrl(); 5181 5182 if ( $.fancybox.getInstance() ) { 5183 if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) { 5184 currentHash = null; 5185 5186 $.fancybox.close(); 5187 } 5188 5189 } else if ( url.gallery !== '' ) { 5190 triggerFromUrl( url ); 5191 } 5192 }); 5193 5194 // Check current hash and trigger click event on matching element to start fancyBox, if needed 5195 setTimeout(function() { 5196 triggerFromUrl( parseUrl() ); 5197 }, 50); 5198 }); 5199 5200 }( document, window, window.jQuery || jQuery )); 5201 5202 ;(function (document, $) { 5203 'use strict'; 5204 5205 var prevTime = new Date().getTime(); 5206 5207 $(document).on({ 5208 'onInit.fb' : function( e, instance, current ) { 5209 instance.$refs.stage.on('mousewheel DOMMouseScroll wheel MozMousePixelScroll', function(e) { 5210 var current = instance.current, 5211 currTime = new Date().getTime(); 5212 5213 if ( instance.group.length < 1 || current.opts.wheel === false || ( current.opts.wheel === 'auto' && current.type !== 'image' ) ) { 5214 return; 5215 } 5216 5217 e.preventDefault(); 5218 e.stopPropagation(); 5219 5220 if ( current.$slide.hasClass( 'fancybox-animated' ) ) { 5221 return; 5222 } 5223 5224 e = e.originalEvent || e; 5225 5226 if ( currTime - prevTime < 250 ) { 5227 return; 5228 } 5229 5230 prevTime = currTime; 5231 5232 instance[ ( -e.deltaY || -e.deltaX || e.wheelDelta || -e.detail ) < 0 ? 'next' : 'previous' ](); 5233 5234 }); 5235 } 5236 }); 5237 5238 }( document, window.jQuery || jQuery ));