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