1// ================================================== 2// fancyBox v3.1.20 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 = Math.round( (end.width / start.width) * 100 ) / 100; 2096 end.scaleY = Math.round( (end.height / start.height) * 100 ) / 100; 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.focusBack ) { 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.20", 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 = $el.width() * to.scaleX; 2859 to.height = $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 = e.data ? e.data.items : [], 2910 value = $(target).attr( 'data-fancybox' ) || '', 2911 index = 0; 2912 2913 e.preventDefault(); 2914 e.stopPropagation(); 2915 2916 // Get all related items and find index for clicked one 2917 if ( value ) { 2918 items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' ); 2919 index = items.index( target ); 2920 2921 // Sometimes current item can not be found 2922 // (for example, when slider clones items) 2923 if ( index < 0 ) { 2924 index = 0; 2925 } 2926 2927 } else { 2928 items = [ target ]; 2929 } 2930 2931 $.fancybox.open( items, opts, index ); 2932 } 2933 2934 2935 // Create a jQuery plugin 2936 // ====================== 2937 2938 $.fn.fancybox = function (options) { 2939 var selector; 2940 2941 options = options || {}; 2942 selector = options.selector || false; 2943 2944 if ( selector ) { 2945 2946 $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, { 2947 items : $( selector ), 2948 options : options 2949 }, _run ); 2950 2951 } else { 2952 2953 this.off( 'click.fb-start' ).on( 'click.fb-start', { 2954 items : this, 2955 options : options 2956 }, _run); 2957 2958 } 2959 2960 return this; 2961 }; 2962 2963 2964 // Self initializing plugin 2965 // ======================== 2966 2967 $D.on( 'click.fb-start', '[data-fancybox]', _run ); 2968 2969}( window, document, window.jQuery )); 2970 2971// ========================================================================== 2972// 2973// Media 2974// Adds additional media type support 2975// 2976// ========================================================================== 2977;(function ($) { 2978 2979 'use strict'; 2980 2981 // Formats matching url to final form 2982 2983 var format = function (url, rez, params) { 2984 if ( !url ) { 2985 return; 2986 } 2987 2988 params = params || ''; 2989 2990 if ( $.type(params) === "object" ) { 2991 params = $.param(params, true); 2992 } 2993 2994 $.each(rez, function (key, value) { 2995 url = url.replace('$' + key, value || ''); 2996 }); 2997 2998 if (params.length) { 2999 url += (url.indexOf('?') > 0 ? '&' : '?') + params; 3000 } 3001 3002 return url; 3003 }; 3004 3005 // Object containing properties for each media type 3006 3007 var defaults = { 3008 youtube : { 3009 matcher : /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i, 3010 params : { 3011 autoplay : 1, 3012 autohide : 1, 3013 fs : 1, 3014 rel : 0, 3015 hd : 1, 3016 wmode : 'transparent', 3017 enablejsapi : 1, 3018 html5 : 1 3019 }, 3020 paramPlace : 8, 3021 type : 'iframe', 3022 url : '//www.youtube.com/embed/$4', 3023 thumb : '//img.youtube.com/vi/$4/hqdefault.jpg' 3024 }, 3025 3026 vimeo : { 3027 matcher : /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/, 3028 params : { 3029 autoplay : 1, 3030 hd : 1, 3031 show_title : 1, 3032 show_byline : 1, 3033 show_portrait : 0, 3034 fullscreen : 1, 3035 api : 1 3036 }, 3037 paramPlace : 3, 3038 type : 'iframe', 3039 url : '//player.vimeo.com/video/$2' 3040 }, 3041 3042 metacafe : { 3043 matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/, 3044 type : 'iframe', 3045 url : '//www.metacafe.com/embed/$1/?ap=1' 3046 }, 3047 3048 dailymotion : { 3049 matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, 3050 params : { 3051 additionalInfos : 0, 3052 autoStart : 1 3053 }, 3054 type : 'iframe', 3055 url : '//www.dailymotion.com/embed/video/$1' 3056 }, 3057 3058 vine : { 3059 matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/, 3060 type : 'iframe', 3061 url : '//vine.co/v/$1/embed/simple' 3062 }, 3063 3064 instagram : { 3065 matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, 3066 type : 'image', 3067 url : '//$1/p/$2/media/?size=l' 3068 }, 3069 3070 // Examples: 3071 // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 3072 // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 3073 // https://www.google.lv/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 3074 google_maps : { 3075 matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i, 3076 type : 'iframe', 3077 url : function (rez) { 3078 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' ); 3079 } 3080 } 3081 }; 3082 3083 $(document).on('onInit.fb', function (e, instance) { 3084 3085 $.each(instance.group, function( i, item ) { 3086 3087 var url = item.src || '', 3088 type = false, 3089 media, 3090 thumb, 3091 rez, 3092 params,
3093 urlParams, 3094 o, 3095 provider; 3096 3097 // Skip items that already have content type 3098 if ( item.type ) { 3099 return; 3100 } 3101 3102 media = $.extend( true, {}, defaults, item.opts.media ); 3103 3104 // Look for any matching media type 3105 $.each(media, function ( n, el ) { 3106 rez = url.match(el.matcher); 3107 o = {}; 3108 provider = n; 3109 3110 if (!rez) { 3111 return; 3112 } 3113 3114 type = el.type; 3115 3116 if ( el.paramPlace && rez[ el.paramPlace ] ) { 3117 urlParams = rez[ el.paramPlace ]; 3118 3119 if ( urlParams[ 0 ] == '?' ) { 3120 urlParams = urlParams.substring(1); 3121 } 3122 3123 urlParams = urlParams.split('&'); 3124 3125 for ( var m = 0; m < urlParams.length; ++m ) { 3126 var p = urlParams[ m ].split('=', 2); 3127 3128 if ( p.length == 2 ) { 3129 o[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") ); 3130 } 3131 } 3132 } 3133 3134 params = $.extend( true, {}, el.params, item.opts[ n ], o ); 3135 3136 url = $.type(el.url) === "function" ? el.url.call(this, rez, params, item) : format(el.url, rez, params); 3137 thumb = $.type(el.thumb) === "function" ? el.thumb.call(this, rez, params, item) : format(el.thumb, rez); 3138 3139 if ( provider === 'vimeo' ) { 3140 url = url.replace('&%23', '#'); 3141 } 3142 3143 return false; 3144 }); 3145 3146 // If it is found, then change content type and update the url 3147 3148 if ( type ) { 3149 item.src = url; 3150 item.type = type; 3151 3152 if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) { 3153 item.opts.thumb = thumb; 3154 } 3155 3156 if ( type === 'iframe' ) { 3157 $.extend(true, item.opts, { 3158 iframe : { 3159 preload : false, 3160 attr : { 3161 scrolling : "no" 3162 } 3163 } 3164 }); 3165 3166 item.contentProvider = provider; 3167 3168 item.opts.slideClass += ' fancybox-slide--' + ( provider == 'google_maps' ? 'map' : 'video' ); 3169 } 3170 3171 } else { 3172 3173 // If no content type is found, then set it to `image` as fallback 3174 item.type = 'image'; 3175 } 3176 3177 }); 3178 3179 }); 3180 3181}(window.jQuery)); 3182 3183// ========================================================================== 3184// 3185// Guestures 3186// Adds touch guestures, handles click and tap events 3187// 3188// ========================================================================== 3189;(function (window, document, $) { 3190 'use strict'; 3191 3192 var requestAFrame = (function () { 3193 return window.requestAnimationFrame || 3194 window.webkitRequestAnimationFrame || 3195 window.mozRequestAnimationFrame || 3196 window.oRequestAnimationFrame || 3197 // if all else fails, use setTimeout 3198 function (callback) { 3199 return window.setTimeout(callback, 1000 / 60); 3200 }; 3201 })(); 3202 3203 3204 var cancelAFrame = (function () { 3205 return window.cancelAnimationFrame || 3206 window.webkitCancelAnimationFrame || 3207 window.mozCancelAnimationFrame || 3208 window.oCancelAnimationFrame || 3209 function (id) { 3210 window.clearTimeout(id); 3211 }; 3212 })(); 3213 3214 3215 var pointers = function( e ) { 3216 var result = []; 3217 3218 e = e.originalEvent || e || window.e; 3219 e = e.touches && e.touches.length ? e.touches : ( e.changedTouches && e.changedTouches.length ? e.changedTouches : [ e ] ); 3220 3221 for ( var key in e ) { 3222 3223 if ( e[ key ].pageX ) { 3224 result.push( { x : e[ key ].pageX, y : e[ key ].pageY } ); 3225 3226 } else if ( e[ key ].clientX ) { 3227 result.push( { x : e[ key ].clientX, y : e[ key ].clientY } ); 3228 } 3229 } 3230 3231 return result; 3232 }; 3233 3234 var distance = function( point2, point1, what ) { 3235 if ( !point1 || !point2 ) { 3236 return 0; 3237 } 3238 3239 if ( what === 'x' ) { 3240 return point2.x - point1.x; 3241 3242 } else if ( what === 'y' ) { 3243 return point2.y - point1.y; 3244 } 3245 3246 return Math.sqrt( Math.pow( point2.x - point1.x, 2 ) + Math.pow( point2.y - point1.y, 2 ) ); 3247 }; 3248 3249 var isClickable = function( $el ) { 3250 if ( $el.is('a,button,input,select,textarea') || $.isFunction( $el.get(0).onclick ) ) { 3251 return true; 3252 } 3253 3254 // Check for attributes like data-fancybox-next or data-fancybox-close 3255 for ( var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++ ) { 3256 if ( atts[i].nodeName.substr(0, 14) === 'data-fancybox-' ) { 3257 return true; 3258 } 3259 } 3260 3261 return false;
3262 }; 3263 3264 var hasScrollbars = function( el ) { 3265 var overflowY = window.getComputedStyle( el )['overflow-y']; 3266 var overflowX = window.getComputedStyle( el )['overflow-x']; 3267 3268 var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight; 3269 var horizontal = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth; 3270 3271 return vertical || horizontal; 3272 }; 3273 3274 var isScrollable = function ( $el ) { 3275 var rez = false; 3276 3277 while ( true ) { 3278 rez = hasScrollbars( $el.get(0) ); 3279 3280 if ( rez ) { 3281 break; 3282 } 3283 3284 $el = $el.parent(); 3285 3286 if ( !$el.length || $el.hasClass( 'fancybox-stage' ) || $el.is( 'body' ) ) { 3287 break; 3288 } 3289 } 3290 3291 return rez; 3292 }; 3293 3294 3295 var Guestures = function ( instance ) { 3296 var self = this; 3297 3298 self.instance = instance; 3299 3300 self.$bg = instance.$refs.bg; 3301 self.$stage = instance.$refs.stage; 3302 self.$container = instance.$refs.container; 3303 3304 self.destroy(); 3305 3306 self.$container.on( 'touchstart.fb.touch mousedown.fb.touch', $.proxy(self, 'ontouchstart') ); 3307 }; 3308 3309 Guestures.prototype.destroy = function() { 3310 this.$container.off( '.fb.touch' ); 3311 }; 3312 3313 Guestures.prototype.ontouchstart = function( e ) { 3314 var self = this; 3315 3316 var $target = $( e.target ); 3317 var instance = self.instance; 3318 var current = instance.current; 3319 var $content = current.$content; 3320 3321 var isTouchDevice = ( e.type == 'touchstart' ); 3322 3323 // Do not respond to both events 3324 if ( isTouchDevice ) { 3325 self.$container.off( 'mousedown.fb.touch' ); 3326 } 3327 3328 // Ignore clicks while zooming or closing 3329 if ( !current || self.instance.isAnimating || self.instance.isClosing ) { 3330 e.stopPropagation(); 3331 e.preventDefault(); 3332 3333 return; 3334 } 3335 3336 // Ignore right click 3337 if ( e.originalEvent && e.originalEvent.button == 2 ) { 3338 return; 3339 } 3340 3341 // Ignore taping on links, buttons, input elements 3342 if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) { 3343 return; 3344 } 3345 3346 // Ignore clicks on the scrollbar 3347 if ( e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) { 3348 return; 3349 } 3350 3351 self.startPoints = pointers( e ); 3352 3353 // Prevent zooming if already swiping 3354 if ( !self.startPoints || ( self.startPoints.length > 1 && instance.isSliding ) ) { 3355 return; 3356 } 3357 3358 self.$target = $target; 3359 self.$content = $content; 3360 self.canTap = true; 3361 3362 $(document).off( '.fb.touch' ); 3363 3364 $(document).on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, "ontouchend")); 3365 $(document).on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, "ontouchmove")); 3366 3367 e.stopPropagation(); 3368 3369 if ( !(instance.current.opts.touch || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) { 3370 3371 // Prevent ghosting 3372 if ( $target.is('img') ) { 3373 e.preventDefault(); 3374 } 3375 3376 return; 3377 } 3378 3379 if ( !( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) ) { 3380 e.preventDefault(); 3381 } 3382 3383 self.canvasWidth = Math.round( current.$slide[0].clientWidth ); 3384 self.canvasHeight = Math.round( current.$slide[0].clientHeight ); 3385 3386 self.startTime = new Date().getTime(); 3387 self.distanceX = self.distanceY = self.distance = 0; 3388 3389 self.isPanning = false; 3390 self.isSwiping = false; 3391 self.isZooming = false; 3392 3393 self.sliderStartPos = self.sliderLastPos || { top: 0, left: 0 }; 3394 self.contentStartPos = $.fancybox.getTranslate( self.$content ); 3395 self.contentLastPos = null; 3396 3397 if ( self.startPoints.length === 1 && !self.isZooming ) { 3398 self.canTap = !instance.isSliding; 3399 3400 if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) { 3401 3402 $.fancybox.stop( self.$content ); 3403 3404 self.$content.css( 'transition-duration', '0ms' ); 3405 3406 self.isPanning = true; 3407 3408 } else { 3409 3410 self.isSwiping = true; 3411 } 3412 3413 self.$container.addClass('fancybox-controls--isGrabbing'); 3414 } 3415 3416 if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) { 3417 self.isZooming = true; 3418 3419 self.isSwiping = false;
3420 self.isPanning = false; 3421 3422 $.fancybox.stop( self.$content ); 3423 3424 self.$content.css( 'transition-duration', '0ms' ); 3425 3426 self.centerPointStartX = ( ( self.startPoints[0].x + self.startPoints[1].x ) * 0.5 ) - $(window).scrollLeft(); 3427 self.centerPointStartY = ( ( self.startPoints[0].y + self.startPoints[1].y ) * 0.5 ) - $(window).scrollTop(); 3428 3429 self.percentageOfImageAtPinchPointX = ( self.centerPointStartX - self.contentStartPos.left ) / self.contentStartPos.width; 3430 self.percentageOfImageAtPinchPointY = ( self.centerPointStartY - self.contentStartPos.top ) / self.contentStartPos.height; 3431 3432 self.startDistanceBetweenFingers = distance( self.startPoints[0], self.startPoints[1] ); 3433 } 3434 3435 }; 3436 3437 Guestures.prototype.ontouchmove = function( e ) { 3438 3439 var self = this; 3440 3441 self.newPoints = pointers( e ); 3442 3443 if ( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) { 3444 e.stopPropagation(); 3445 3446 self.canTap = false; 3447 3448 return; 3449 } 3450 3451 if ( !( self.instance.current.opts.touch || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) { 3452 return; 3453 } 3454 3455 self.distanceX = distance( self.newPoints[0], self.startPoints[0], 'x' ); 3456 self.distanceY = distance( self.newPoints[0], self.startPoints[0], 'y' ); 3457 3458 self.distance = distance( self.newPoints[0], self.startPoints[0] ); 3459 3460 // Skip false ontouchmove events (Chrome) 3461 if ( self.distance > 0 ) { 3462 3463 if ( !( self.$target.is( self.$stage ) || self.$stage.find( self.$target ).length ) ) { 3464 return; 3465 } 3466 3467 e.stopPropagation(); 3468 e.preventDefault(); 3469 3470 if ( self.isSwiping ) { 3471 self.onSwipe(); 3472 3473 } else if ( self.isPanning ) { 3474 self.onPan(); 3475 3476 } else if ( self.isZooming ) { 3477 self.onZoom(); 3478 } 3479 3480 } 3481 3482 }; 3483 3484 Guestures.prototype.onSwipe = function() { 3485 3486 var self = this; 3487 3488 var swiping = self.isSwiping; 3489 var left = self.sliderStartPos.left || 0; 3490 var angle; 3491 3492 if ( swiping === true ) { 3493 3494 if ( Math.abs( self.distance ) > 10 ) { 3495 3496 self.canTap = false; 3497 3498 if ( self.instance.group.length < 2 && self.instance.opts.touch.vertical ) { 3499 self.isSwiping = 'y'; 3500 3501 } else if ( self.instance.isSliding || self.instance.opts.touch.vertical === false || ( self.instance.opts.touch.vertical === 'auto' && $( window ).width() > 800 ) ) { 3502 self.isSwiping = 'x'; 3503 3504 } else { 3505 angle = Math.abs( Math.atan2( self.distanceY, self.distanceX ) * 180 / Math.PI ); 3506 3507 self.isSwiping = ( angle > 45 && angle < 135 ) ? 'y' : 'x'; 3508 } 3509 3510 self.instance.isSliding = self.isSwiping; 3511 3512 // Reset points to avoid jumping, because we dropped first swipes to calculate the angle 3513 self.startPoints = self.newPoints; 3514 3515 $.each(self.instance.slides, function( index, slide ) { 3516 $.fancybox.stop( slide.$slide ); 3517 3518 slide.$slide.css( 'transition-duration', '0ms' ); 3519 3520 slide.inTransition = false; 3521 3522 if ( slide.pos === self.instance.current.pos ) { 3523 self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left; 3524 } 3525 }); 3526 3527 //self.instance.current.isMoved = true; 3528 3529 // Stop slideshow 3530 if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) { 3531 self.instance.SlideShow.stop(); 3532 } 3533 } 3534 3535 } else { 3536 3537 if ( swiping == 'x' ) { 3538 3539 // Sticky edges 3540 if ( self.distanceX > 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === 0 && !self.instance.current.opts.loop ) ) ) { 3541 left = left + Math.pow( self.distanceX, 0.8 ); 3542 3543 } else if ( self.distanceX < 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop ) ) ) { 3544 left = left - Math.pow( -self.distanceX, 0.8 ); 3545 3546 } else { 3547 left = left + self.distanceX; 3548 } 3549 3550 } 3551 3552 self.sliderLastPos = { 3553 top : swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY, 3554 left : left 3555 }; 3556 3557 if ( self.requestId ) { 3558 cancelAFrame( self.requestId ); 3559 3560 self.requestId = null; 3561 } 3562 3563 self.requestId = requestAFrame(function() { 3564 3565 if ( self.sliderLastPos ) { 3566 $.each(self.instance.slides, function( index, slide ) { 3567 var pos = slide.pos - self.instance.currPos; 3568 3569 $.fancybox.setTranslate( slide.$slide, { 3570 top : self.sliderLastPos.top, 3571 left : self.sliderLastPos.left + ( pos * self.canvasWidth ) + ( pos * slide.opts.gutter ) 3572 }); 3573 }); 3574 3575 self.$container.addClass( 'fancybox-is-sliding' ); 3576 } 3577 3578 }); 3579 3580 } 3581 3582 }; 3583 3584 Guestures.prototype.onPan = function() { 3585 3586 var self = this; 3587 3588 var newOffsetX, newOffsetY, newPos; 3589 3590 self.canTap = false;
3591 3592 if ( self.contentStartPos.width > self.canvasWidth ) { 3593 newOffsetX = self.contentStartPos.left + self.distanceX; 3594 3595 } else { 3596 newOffsetX = self.contentStartPos.left; 3597 } 3598 3599 newOffsetY = self.contentStartPos.top + self.distanceY; 3600 3601 newPos = self.limitMovement( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); 3602 3603 newPos.scaleX = self.contentStartPos.scaleX; 3604 newPos.scaleY = self.contentStartPos.scaleY; 3605 3606 self.contentLastPos = newPos; 3607 3608 if ( self.requestId ) { 3609 cancelAFrame( self.requestId ); 3610 3611 self.requestId = null; 3612 } 3613 3614 self.requestId = requestAFrame(function() { 3615 $.fancybox.setTranslate( self.$content, self.contentLastPos ); 3616 }); 3617 }; 3618 3619 // Make panning sticky to the edges 3620 Guestures.prototype.limitMovement = function( newOffsetX, newOffsetY, newWidth, newHeight ) { 3621 3622 var self = this; 3623 3624 var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY; 3625 3626 var canvasWidth = self.canvasWidth; 3627 var canvasHeight = self.canvasHeight; 3628 3629 var currentOffsetX = self.contentStartPos.left; 3630 var currentOffsetY = self.contentStartPos.top; 3631 3632 var distanceX = self.distanceX; 3633 var distanceY = self.distanceY; 3634 3635 // Slow down proportionally to traveled distance 3636 3637 minTranslateX = Math.max(0, canvasWidth * 0.5 - newWidth * 0.5 ); 3638 minTranslateY = Math.max(0, canvasHeight * 0.5 - newHeight * 0.5 ); 3639 3640 maxTranslateX = Math.min( canvasWidth - newWidth, canvasWidth * 0.5 - newWidth * 0.5 ); 3641 maxTranslateY = Math.min( canvasHeight - newHeight, canvasHeight * 0.5 - newHeight * 0.5 ); 3642 3643 if ( newWidth > canvasWidth ) { 3644 3645 // -> 3646 if ( distanceX > 0 && newOffsetX > minTranslateX ) { 3647 newOffsetX = minTranslateX - 1 + Math.pow( -minTranslateX + currentOffsetX + distanceX, 0.8 ) || 0; 3648 } 3649 3650 // <- 3651 if ( distanceX < 0 && newOffsetX < maxTranslateX ) { 3652 newOffsetX = maxTranslateX + 1 - Math.pow( maxTranslateX - currentOffsetX - distanceX, 0.8 ) || 0; 3653 } 3654 3655 } 3656 3657 if ( newHeight > canvasHeight ) { 3658 3659 // \/ 3660 if ( distanceY > 0 && newOffsetY > minTranslateY ) { 3661 newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8 ) || 0; 3662 } 3663 3664 // /\ 3665 if ( distanceY < 0 && newOffsetY < maxTranslateY ) { 3666 newOffsetY = maxTranslateY + 1 - Math.pow ( maxTranslateY - currentOffsetY - distanceY, 0.8 ) || 0; 3667 } 3668 3669 } 3670 3671 return { 3672 top : newOffsetY, 3673 left : newOffsetX 3674 }; 3675 3676 }; 3677 3678 3679 Guestures.prototype.limitPosition = function( newOffsetX, newOffsetY, newWidth, newHeight ) { 3680 3681 var self = this; 3682 3683 var canvasWidth = self.canvasWidth; 3684 var canvasHeight = self.canvasHeight; 3685 3686 if ( newWidth > canvasWidth ) { 3687 newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; 3688 newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; 3689 3690 } else { 3691 3692 // Center horizontally 3693 newOffsetX = Math.max( 0, canvasWidth / 2 - newWidth / 2 ); 3694 3695 } 3696 3697 if ( newHeight > canvasHeight ) { 3698 newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; 3699 newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; 3700 3701 } else { 3702 3703 // Center vertically 3704 newOffsetY = Math.max( 0, canvasHeight / 2 - newHeight / 2 ); 3705 3706 } 3707 3708 return { 3709 top : newOffsetY, 3710 left : newOffsetX 3711 }; 3712 3713 }; 3714 3715 Guestures.prototype.onZoom = function() { 3716 3717 var self = this; 3718 3719 // Calculate current distance between points to get pinch ratio and new width and height 3720 3721 var currentWidth = self.contentStartPos.width; 3722 var currentHeight = self.contentStartPos.height; 3723 3724 var currentOffsetX = self.contentStartPos.left; 3725 var currentOffsetY = self.contentStartPos.top; 3726 3727 var endDistanceBetweenFingers = distance( self.newPoints[0], self.newPoints[1] ); 3728 3729 var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; 3730 3731 var newWidth = Math.floor( currentWidth * pinchRatio ); 3732 var newHeight = Math.floor( currentHeight * pinchRatio ); 3733 3734 // This is the translation due to pinch-zooming 3735 var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; 3736 var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; 3737 3738 //Point between the two touches 3739 3740 var centerPointEndX = ((self.newPoints[0].x + self.newPoints[1].x) / 2) - $(window).scrollLeft(); 3741 var centerPointEndY = ((self.newPoints[0].y + self.newPoints[1].y) / 2) - $(window).scrollTop(); 3742 3743 // And this is the translation due to translation of the centerpoint 3744 // between the two fingers 3745 3746 var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; 3747 var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; 3748 3749 // The new offset is the old/current one plus the total translation 3750 3751 var newOffsetX = currentOffsetX + ( translateFromZoomingX + translateFromTranslatingX ); 3752 var newOffsetY = currentOffsetY + ( translateFromZoomingY + translateFromTranslatingY ); 3753 3754 var newPos = { 3755 top : newOffsetY, 3756 left : newOffsetX, 3757 scaleX : self.contentStartPos.scaleX * pinchRatio, 3758 scaleY : self.contentStartPos.scaleY * pinchRatio 3759 }; 3760 3761 self.canTap = false;
3762 3763 self.newWidth = newWidth; 3764 self.newHeight = newHeight; 3765 3766 self.contentLastPos = newPos; 3767 3768 if ( self.requestId ) { 3769 cancelAFrame( self.requestId ); 3770 3771 self.requestId = null; 3772 } 3773 3774 self.requestId = requestAFrame(function() { 3775 $.fancybox.setTranslate( self.$content, self.contentLastPos ); 3776 }); 3777 3778 }; 3779 3780 Guestures.prototype.ontouchend = function( e ) { 3781 3782 var self = this; 3783 var dMs = Math.max( (new Date().getTime() ) - self.startTime, 1); 3784 3785 var swiping = self.isSwiping; 3786 var panning = self.isPanning; 3787 var zooming = self.isZooming; 3788 3789 self.endPoints = pointers( e ); 3790 3791 self.$container.removeClass( 'fancybox-controls--isGrabbing' ); 3792 3793 $(document).off( '.fb.touch' ); 3794 3795 if ( self.requestId ) { 3796 cancelAFrame( self.requestId ); 3797 3798 self.requestId = null; 3799 } 3800 3801 self.isSwiping = false; 3802 self.isPanning = false; 3803 self.isZooming = false; 3804 3805 if ( self.canTap ) { 3806 return self.onTap( e ); 3807 } 3808 3809 self.speed = 366; 3810 3811 // Speed in px/ms 3812 self.velocityX = self.distanceX / dMs * 0.5; 3813 self.velocityY = self.distanceY / dMs * 0.5; 3814 3815 self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) ); 3816 3817 if ( panning ) { 3818 self.endPanning(); 3819 3820 } else if ( zooming ) { 3821 self.endZooming(); 3822 3823 } else { 3824 self.endSwiping( swiping ); 3825 } 3826 3827 return; 3828 }; 3829 3830 Guestures.prototype.endSwiping = function( swiping ) { 3831 3832 var self = this; 3833 var ret = false;
3834 3835 self.instance.isSliding = false; 3836 self.sliderLastPos = null; 3837 3838 // Close if swiped vertically / navigate if horizontally 3839 if ( swiping == 'y' && Math.abs( self.distanceY ) > 50 ) { 3840 3841 // Continue vertical movement 3842 $.fancybox.animate( self.instance.current.$slide, { 3843 top : self.sliderStartPos.top + self.distanceY + ( self.velocityY * 150 ), 3844 opacity : 0 3845 }, 150 ); 3846 3847 ret = self.instance.close( true, 300 ); 3848 3849 } else if ( swiping == 'x' && self.distanceX > 50 && self.instance.group.length > 1 ) { 3850 ret = self.instance.previous( self.speedX ); 3851 3852 } else if ( swiping == 'x' && self.distanceX < -50 && self.instance.group.length > 1 ) { 3853 ret = self.instance.next( self.speedX ); 3854 } 3855 3856 if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) { 3857 self.instance.jumpTo( self.instance.current.index, 150 ); 3858 } 3859 3860 self.$container.removeClass( 'fancybox-is-sliding' ); 3861 3862 }; 3863 3864 // Limit panning from edges 3865 // ======================== 3866 3867 Guestures.prototype.endPanning = function() { 3868 3869 var self = this; 3870 var newOffsetX, newOffsetY, newPos; 3871 3872 if ( !self.contentLastPos ) { 3873 return; 3874 } 3875 3876 if ( self.instance.current.opts.touch.momentum === false ) { 3877 newOffsetX = self.contentLastPos.left; 3878 newOffsetY = self.contentLastPos.top; 3879 3880 } else { 3881 3882 // Continue movement 3883 newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed ); 3884 newOffsetY = self.contentLastPos.top + ( self.velocityY * self.speed ); 3885 } 3886 3887 newPos = self.limitPosition( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); 3888 3889 newPos.width = self.contentStartPos.width; 3890 newPos.height = self.contentStartPos.height; 3891 3892 $.fancybox.animate( self.$content, newPos, 330 ); 3893 }; 3894 3895 3896 Guestures.prototype.endZooming = function() { 3897 3898 var self = this; 3899 3900 var current = self.instance.current; 3901 3902 var newOffsetX, newOffsetY, newPos, reset; 3903 3904 var newWidth = self.newWidth; 3905 var newHeight = self.newHeight; 3906 3907 if ( !self.contentLastPos ) { 3908 return; 3909 } 3910 3911 newOffsetX = self.contentLastPos.left; 3912 newOffsetY = self.contentLastPos.top; 3913 3914 reset = { 3915 top : newOffsetY, 3916 left : newOffsetX, 3917 width : newWidth, 3918 height : newHeight, 3919 scaleX : 1, 3920 scaleY : 1 3921 }; 3922 3923 // Reset scalex/scaleY values; this helps for perfomance and does not break animation 3924 $.fancybox.setTranslate( self.$content, reset ); 3925 3926 if ( newWidth < self.canvasWidth && newHeight < self.canvasHeight ) { 3927 self.instance.scaleToFit( 150 ); 3928 3929 } else if ( newWidth > current.width || newHeight > current.height ) { 3930 self.instance.scaleToActual( self.centerPointStartX, self.centerPointStartY, 150 ); 3931 3932 } else { 3933 3934 newPos = self.limitPosition( newOffsetX, newOffsetY, newWidth, newHeight ); 3935 3936 // Switch from scale() to width/height or animation will not work correctly 3937 $.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) ); 3938 3939 $.fancybox.animate( self.$content, newPos, 150 ); 3940 } 3941 3942 }; 3943 3944 Guestures.prototype.onTap = function(e) { 3945 var self = this; 3946 var $target = $( e.target ); 3947 3948 var instance = self.instance; 3949 var current = instance.current; 3950 3951 var endPoints = ( e && pointers( e ) ) || self.startPoints; 3952 3953 var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0; 3954 var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0; 3955 3956 var where; 3957 3958 var process = function ( prefix ) { 3959 3960 var action = current.opts[ prefix ]; 3961 3962 if ( $.isFunction( action ) ) { 3963 action = action.apply( instance, [ current, e ] ); 3964 } 3965 3966 if ( !action) { 3967 return; 3968 } 3969 3970 switch ( action ) { 3971 3972 case "close" : 3973 3974 instance.close( self.startEvent ); 3975 3976 break; 3977 3978 case "toggleControls" : 3979 3980 instance.toggleControls( true ); 3981 3982 break; 3983 3984 case "next" : 3985 3986 instance.next(); 3987 3988 break; 3989 3990 case "nextOrClose" : 3991 3992 if ( instance.group.length > 1 ) { 3993 instance.next(); 3994 3995 } else { 3996 instance.close( self.startEvent ); 3997 } 3998 3999 break; 4000 4001 case "zoom" : 4002 4003 if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) { 4004 4005 if ( instance.canPan() ) { 4006 instance.scaleToFit(); 4007 4008 } else if ( instance.isScaledDown() ) { 4009 instance.scaleToActual( tapX, tapY ); 4010 4011 } else if ( instance.group.length < 2 ) { 4012 instance.close( self.startEvent ); 4013 } 4014 } 4015 4016 break; 4017 } 4018 4019 }; 4020 4021 // Ignore right click 4022 if ( e.originalEvent && e.originalEvent.button == 2 ) { 4023 return; 4024 } 4025 4026 // Skip if current slide is not in the center 4027 if ( instance.isSliding ) { 4028 return; 4029 } 4030 4031 // Skip if clicked on the scrollbar 4032 if ( tapX > $target[0].clientWidth + $target.offset().left ) { 4033 return; 4034 } 4035 4036 // Check where is clicked 4037 if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) { 4038 where = 'Outside'; 4039 4040 } else if ( $target.is( '.fancybox-slide' ) ) { 4041 where = 'Slide'; 4042 4043 } else if ( instance.current.$content && instance.current.$content.has( e.target ).length ) { 4044 where = 'Content'; 4045 4046 } else { 4047 return; 4048 } 4049 4050 // Check if this is a double tap 4051 if ( self.tapped ) { 4052 4053 // Stop previously created single tap 4054 clearTimeout( self.tapped ); 4055 self.tapped = null; 4056 4057 // Skip if distance between taps is too big 4058 if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 || instance.isSliding ) { 4059 return this; 4060 } 4061 4062 // OK, now we assume that this is a double-tap
4063 process( 'dblclick' + where ); 4064 4065 } else { 4066 4067 // Single tap will be processed if user has not clicked second time within 300ms 4068 // or there is no need to wait for double-tap 4069 self.tapX = tapX; 4070 self.tapY = tapY; 4071 4072 if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) { 4073 self.tapped = setTimeout(function() { 4074 self.tapped = null; 4075 4076 process( 'click' + where ); 4077 4078 }, 300); 4079 4080 } else { 4081 process( 'click' + where ); 4082 } 4083 4084 } 4085 4086 return this; 4087 }; 4088 4089 $(document).on('onActivate.fb', function (e, instance) { 4090 if ( instance && !instance.Guestures ) { 4091 instance.Guestures = new Guestures( instance ); 4092 } 4093 }); 4094 4095 $(document).on('beforeClose.fb', function (e, instance) { 4096 if ( instance && instance.Guestures ) { 4097 instance.Guestures.destroy(); 4098 } 4099 }); 4100 4101 4102}(window, document, window.jQuery)); 4103 4104// ========================================================================== 4105// 4106// SlideShow 4107// Enables slideshow functionality 4108// 4109// Example of usage: 4110// $.fancybox.getInstance().SlideShow.start() 4111// 4112// ========================================================================== 4113;(function (document, $) { 4114 'use strict'; 4115 4116 var SlideShow = function( instance ) { 4117 this.instance = instance; 4118 this.init(); 4119 }; 4120 4121 $.extend( SlideShow.prototype, { 4122 timer : null, 4123 isActive : false, 4124 $button : null, 4125 speed : 3000, 4126 4127 init : function() { 4128 var self = this; 4129 4130 self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function() { 4131 self.toggle(); 4132 }); 4133 4134 if ( self.instance.group.length < 2 || !self.instance.group[ self.instance.currIndex ].opts.slideShow ) { 4135 self.$button.hide(); 4136 } 4137 }, 4138 4139 set : function() { 4140 var self = this; 4141 4142 // Check if reached last element 4143 if ( self.instance && self.instance.current && (self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1 )) { 4144 self.timer = setTimeout(function() { 4145 self.instance.next(); 4146 4147 }, self.instance.current.opts.slideShow.speed || self.speed); 4148 4149 } else { 4150 self.stop(); 4151 self.instance.idleSecondsCounter = 0; 4152 self.instance.showControls(); 4153 } 4154 4155 }, 4156 4157 clear : function() { 4158 var self = this; 4159 4160 clearTimeout( self.timer ); 4161 4162 self.timer = null; 4163 }, 4164 4165 start : function() { 4166 var self = this; 4167 var current = self.instance.current; 4168 4169 if ( self.instance && current && ( current.opts.loop || current.index < self.instance.group.length - 1 )) { 4170 4171 self.isActive = true; 4172 4173 self.$button 4174 .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_STOP ) 4175 .addClass( 'fancybox-button--pause' ); 4176 4177 if ( current.isComplete ) { 4178 self.set(); 4179 } 4180 } 4181 }, 4182 4183 stop : function() { 4184 var self = this; 4185 var current = self.instance.current; 4186 4187 self.clear(); 4188 4189 self.$button 4190 .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_START ) 4191 .removeClass( 'fancybox-button--pause' ); 4192 4193 self.isActive = false; 4194 }, 4195 4196 toggle : function() { 4197 var self = this; 4198 4199 if ( self.isActive ) { 4200 self.stop(); 4201 4202 } else { 4203 self.start(); 4204 } 4205 } 4206 4207 }); 4208 4209 $(document).on({ 4210 'onInit.fb' : function(e, instance) { 4211 if ( instance && !instance.SlideShow ) { 4212 instance.SlideShow = new SlideShow( instance ); 4213 } 4214 }, 4215 4216 'beforeShow.fb' : function(e, instance, current, firstRun) { 4217 var SlideShow = instance && instance.SlideShow; 4218 4219 if ( firstRun ) { 4220 4221 if ( SlideShow && current.opts.slideShow.autoStart ) { 4222 SlideShow.start(); 4223 } 4224 4225 } else if ( SlideShow && SlideShow.isActive ) { 4226 SlideShow.clear(); 4227 } 4228 }, 4229 4230 'afterShow.fb' : function(e, instance, current) { 4231 var SlideShow = instance && instance.SlideShow; 4232 4233 if ( SlideShow && SlideShow.isActive ) { 4234 SlideShow.set(); 4235 } 4236 }, 4237 4238 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4239 var SlideShow = instance && instance.SlideShow; 4240 4241 // "P" or Spacebar 4242 if ( SlideShow && current.opts.slideShow && ( keycode === 80 || keycode === 32 ) && !$(document.activeElement).is( 'button,a,input' ) ) { 4243 keypress.preventDefault(); 4244 4245 SlideShow.toggle(); 4246 } 4247 }, 4248 4249 'beforeClose.fb onDeactivate.fb' : function(e, instance) { 4250 var SlideShow = instance && instance.SlideShow; 4251 4252 if ( SlideShow ) { 4253 SlideShow.stop(); 4254 } 4255 } 4256 }); 4257 4258 // Page Visibility API to pause slideshow when window is not active 4259 $(document).on("visibilitychange", function() { 4260 var instance = $.fancybox.getInstance(); 4261 var SlideShow = instance && instance.SlideShow; 4262 4263 if ( SlideShow && SlideShow.isActive ) { 4264 if ( document.hidden ) { 4265 SlideShow.clear(); 4266 4267 } else { 4268 SlideShow.set(); 4269 } 4270 } 4271 }); 4272 4273}(document, window.jQuery)); 4274 4275// ========================================================================== 4276// 4277// FullScreen 4278// Adds fullscreen functionality 4279// 4280// ========================================================================== 4281;(function (document, $) { 4282 'use strict'; 4283 4284 // Collection of methods supported by user browser 4285 var fn = (function () { 4286 4287 var fnMap = [ 4288 [ 4289 'requestFullscreen', 4290 'exitFullscreen', 4291 'fullscreenElement', 4292 'fullscreenEnabled', 4293 'fullscreenchange', 4294 'fullscreenerror' 4295 ], 4296 // new WebKit 4297 [ 4298 'webkitRequestFullscreen', 4299 'webkitExitFullscreen', 4300 'webkitFullscreenElement', 4301 'webkitFullscreenEnabled', 4302 'webkitfullscreenchange', 4303 'webkitfullscreenerror' 4304 4305 ], 4306 // old WebKit (Safari 5.1) 4307 [ 4308 'webkitRequestFullScreen', 4309 'webkitCancelFullScreen', 4310 'webkitCurrentFullScreenElement', 4311 'webkitCancelFullScreen', 4312 'webkitfullscreenchange', 4313 'webkitfullscreenerror' 4314 4315 ], 4316 [ 4317 'mozRequestFullScreen', 4318 'mozCancelFullScreen', 4319 'mozFullScreenElement', 4320 'mozFullScreenEnabled', 4321 'mozfullscreenchange', 4322 'mozfullscreenerror' 4323 ], 4324 [ 4325 'msRequestFullscreen', 4326 'msExitFullscreen', 4327 'msFullscreenElement', 4328 'msFullscreenEnabled', 4329 'MSFullscreenChange', 4330 'MSFullscreenError' 4331 ] 4332 ]; 4333 4334 var val; 4335 var ret = {}; 4336 var i, j; 4337 4338 for ( i = 0; i < fnMap.length; i++ ) { 4339 val = fnMap[ i ]; 4340 4341 if ( val && val[ 1 ] in document ) { 4342 for ( j = 0; j < val.length; j++ ) { 4343 ret[ fnMap[ 0 ][ j ] ] = val[ j ]; 4344 } 4345 4346 return ret; 4347 } 4348 } 4349 4350 return false;
4351 })(); 4352 4353 // If browser does not have Full Screen API, then simply unset default button template and stop 4354 if ( !fn ) { 4355 $.fancybox.defaults.btnTpl.fullScreen = false; 4356 4357 return; 4358 } 4359 4360 var FullScreen = { 4361 request : function ( elem ) { 4362 4363 elem = elem || document.documentElement; 4364 4365 elem[ fn.requestFullscreen ]( elem.ALLOW_KEYBOARD_INPUT ); 4366 4367 }, 4368 exit : function () { 4369 4370 document[ fn.exitFullscreen ](); 4371 4372 }, 4373 toggle : function ( elem ) { 4374 4375 elem = elem || document.documentElement; 4376 4377 if ( this.isFullscreen() ) { 4378 this.exit(); 4379 4380 } else { 4381 this.request( elem ); 4382 } 4383 4384 }, 4385 isFullscreen : function() { 4386 4387 return Boolean( document[ fn.fullscreenElement ] ); 4388 4389 }, 4390 enabled : function() { 4391 4392 return Boolean( document[ fn.fullscreenEnabled ] ); 4393 4394 } 4395 }; 4396 4397 $(document).on({ 4398 'onInit.fb' : function(e, instance) { 4399 var $container; 4400 4401 var $button = instance.$refs.toolbar.find('[data-fancybox-fullscreen]'); 4402 4403 if ( instance && !instance.FullScreen && instance.group[ instance.currIndex ].opts.fullScreen ) { 4404 $container = instance.$refs.container; 4405 4406 $container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function(e) { 4407 4408 e.stopPropagation(); 4409 e.preventDefault(); 4410 4411 FullScreen.toggle( $container[ 0 ] ); 4412 4413 }); 4414 4415 if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) { 4416 FullScreen.request( $container[ 0 ] ); 4417 } 4418 4419 // Expose API 4420 instance.FullScreen = FullScreen; 4421 4422 } else { 4423 $button.hide(); 4424 } 4425 4426 }, 4427 4428 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4429 4430 // "P" or Spacebar 4431 if ( instance && instance.FullScreen && keycode === 70 ) { 4432 keypress.preventDefault(); 4433 4434 instance.FullScreen.toggle( instance.$refs.container[ 0 ] ); 4435 } 4436 4437 }, 4438 4439 'beforeClose.fb' : function( instance ) { 4440 if ( instance && instance.FullScreen ) { 4441 FullScreen.exit(); 4442 } 4443 } 4444 }); 4445 4446 $(document).on(fn.fullscreenchange, function() { 4447 var instance = $.fancybox.getInstance(); 4448 4449 // If image is zooming, then force to stop and reposition properly 4450 if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) { 4451 instance.current.$content.css( 'transition', 'none' ); 4452 4453 instance.isAnimating = false; 4454 4455 instance.update( true, true, 0 ); 4456 } 4457 4458 }); 4459 4460}(document, window.jQuery)); 4461 4462// ========================================================================== 4463// 4464// Thumbs 4465// Displays thumbnails in a grid 4466// 4467// ========================================================================== 4468;(function (document, $) { 4469 'use strict'; 4470 4471 var FancyThumbs = function( instance ) { 4472 this.instance = instance; 4473 this.init(); 4474 }; 4475 4476 $.extend( FancyThumbs.prototype, { 4477 4478 $button : null, 4479 $grid : null, 4480 $list : null, 4481 isVisible : false, 4482 4483 init : function() { 4484 var self = this; 4485 4486 var first = self.instance.group[0], 4487 second = self.instance.group[1]; 4488 4489 self.$button = self.instance.$refs.toolbar.find( '[data-fancybox-thumbs]' ); 4490 4491 if ( self.instance.group.length > 1 && self.instance.group[ self.instance.currIndex ].opts.thumbs && ( 4492 ( first.type == 'image' || first.opts.thumb || first.opts.$thumb ) && 4493 ( second.type == 'image' || second.opts.thumb || second.opts.$thumb ) 4494 )) { 4495 4496 self.$button.on('click', function() { 4497 self.toggle(); 4498 }); 4499 4500 self.isActive = true; 4501 4502 } else { 4503 self.$button.hide(); 4504 4505 self.isActive = false; 4506 } 4507 4508 }, 4509 4510 create : function() { 4511 var instance = this.instance, 4512 list, 4513 src; 4514 4515 this.$grid = $('<div class="fancybox-thumbs"></div>').appendTo( instance.$refs.container ); 4516 4517 list = '<ul>'; 4518 4519 $.each(instance.group, function( i, item ) { 4520 4521 src = item.opts.thumb || ( item.opts.$thumb ? item.opts.$thumb.attr('src') : null ); 4522 4523 if ( !src && item.type === 'image' ) { 4524 src = item.src; 4525 } 4526 4527 if ( src && src.length ) { 4528 list += '<li data-index="' + i + '" tabindex="0" class="fancybox-thumbs-loading"><img data-src="' + src + '" /></li>'; 4529 } 4530 4531 }); 4532 4533 list += '</ul>'; 4534 4535 this.$list = $( list ).appendTo( this.$grid ).on('click', 'li', function() { 4536 instance.jumpTo( $(this).data('index') ); 4537 }); 4538 4539 this.$list.find('img').hide().one('load', function() { 4540 4541 var $parent = $(this).parent().removeClass('fancybox-thumbs-loading'), 4542 thumbWidth = $parent.outerWidth(),
4543 thumbHeight = $parent.outerHeight(), 4544 width, 4545 height, 4546 widthRatio, 4547 heightRatio; 4548 4549 width = this.naturalWidth || this.width; 4550 height = this.naturalHeight || this.height; 4551 4552 //Calculate thumbnail width/height and center it 4553 4554 widthRatio = width / thumbWidth; 4555 heightRatio = height / thumbHeight; 4556 4557 if (widthRatio >= 1 && heightRatio >= 1) { 4558 if (widthRatio > heightRatio) { 4559 width = width / heightRatio; 4560 height = thumbHeight; 4561 4562 } else { 4563 width = thumbWidth; 4564 height = height / widthRatio; 4565 } 4566 } 4567 4568 $(this).css({ 4569 width : Math.floor(width), 4570 height : Math.floor(height), 4571 'margin-top' : Math.min( 0, Math.floor(thumbHeight * 0.3 - height * 0.3 ) ), 4572 'margin-left' : Math.min( 0, Math.floor(thumbWidth * 0.5 - width * 0.5 ) ) 4573 }).show(); 4574 4575 }) 4576 .each(function() { 4577 this.src = $( this ).data( 'src' ); 4578 }); 4579 4580 }, 4581 4582 focus : function() { 4583 4584 if ( this.instance.current ) { 4585 this.$list 4586 .children() 4587 .removeClass('fancybox-thumbs-active') 4588 .filter('[data-index="' + this.instance.current.index + '"]') 4589 .addClass('fancybox-thumbs-active') 4590 .focus(); 4591 } 4592 4593 }, 4594 4595 close : function() { 4596 this.$grid.hide(); 4597 }, 4598 4599 update : function() { 4600 4601 this.instance.$refs.container.toggleClass( 'fancybox-show-thumbs', this.isVisible ); 4602 4603 if ( this.isVisible ) { 4604 4605 if ( !this.$grid ) { 4606 this.create(); 4607 } 4608 4609 this.instance.trigger( 'onThumbsShow' ); 4610 4611 this.focus(); 4612 4613 } else if ( this.$grid ) { 4614 this.instance.trigger( 'onThumbsHide' ); 4615 } 4616 4617 // Update content position 4618 this.instance.update(); 4619 4620 }, 4621 4622 hide : function() { 4623 this.isVisible = false; 4624 this.update(); 4625 }, 4626 4627 show : function() { 4628 this.isVisible = true; 4629 this.update(); 4630 }, 4631 4632 toggle : function() { 4633 this.isVisible = !this.isVisible; 4634 this.update(); 4635 } 4636 4637 }); 4638 4639 $(document).on({ 4640 4641 'onInit.fb' : function(e, instance) { 4642 if ( instance && !instance.Thumbs ) { 4643 instance.Thumbs = new FancyThumbs( instance ); 4644 } 4645 }, 4646 4647 'beforeShow.fb' : function(e, instance, item, firstRun) { 4648 var Thumbs = instance && instance.Thumbs; 4649 4650 if ( !Thumbs || !Thumbs.isActive ) { 4651 return; 4652 } 4653 4654 if ( item.modal ) { 4655 Thumbs.$button.hide(); 4656 4657 Thumbs.hide(); 4658 4659 return; 4660 } 4661 4662 if ( firstRun && instance.opts.thumbs.autoStart === true ) { 4663 Thumbs.show(); 4664 } 4665 4666 if ( Thumbs.isVisible ) { 4667 Thumbs.focus(); 4668 } 4669 }, 4670 4671 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4672 var Thumbs = instance && instance.Thumbs; 4673 4674 // "G" 4675 if ( Thumbs && Thumbs.isActive && keycode === 71 ) { 4676 keypress.preventDefault(); 4677 4678 Thumbs.toggle(); 4679 } 4680 }, 4681 4682 'beforeClose.fb' : function( e, instance ) { 4683 var Thumbs = instance && instance.Thumbs; 4684 4685 if ( Thumbs && Thumbs.isVisible && instance.opts.thumbs.hideOnClose !== false ) { 4686 Thumbs.close(); 4687 } 4688 } 4689 4690 }); 4691 4692}(document, window.jQuery)); 4693 4694// ========================================================================== 4695// 4696// Hash 4697// Enables linking to each modal 4698// 4699// ========================================================================== 4700;(function (document, window, $) { 4701 'use strict'; 4702 4703 // Simple $.escapeSelector polyfill (for jQuery prior v3) 4704 if ( !$.escapeSelector ) { 4705 $.escapeSelector = function( sel ) { 4706 var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; 4707 var fcssescape = function( ch, asCodePoint ) { 4708 if ( asCodePoint ) { 4709 // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER 4710 if ( ch === "\0" ) { 4711 return "\uFFFD"; 4712 } 4713 4714 // Control characters and (dependent upon position) numbers get escaped as code points 4715 return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; 4716 } 4717 4718 // Other potentially-special ASCII characters get backslash-escaped 4719 return "\\" + ch; 4720 }; 4721 4722 return ( sel + "" ).replace( rcssescape, fcssescape ); 4723 }; 4724 } 4725 4726 // Variable containing last hash value set by fancyBox 4727 // It will be used to determine if fancyBox needs to close after hash change is detected 4728 var currentHash = null; 4729 4730 // Throtlling the history change 4731 var timerID = null; 4732 4733 // Get info about gallery name and current index from url 4734 function parseUrl() { 4735 var hash = window.location.hash.substr( 1 ); 4736 var rez = hash.split( '-' ); 4737 var index = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1; 4738 var gallery = rez.join( '-' ); 4739 4740 // Index is starting from 1 4741 if ( index < 1 ) { 4742 index = 1; 4743 } 4744 4745 return { 4746 hash : hash, 4747 index : index, 4748 gallery : gallery 4749 }; 4750 } 4751 4752 // Trigger click evnt on links to open new fancyBox instance 4753 function triggerFromUrl( url ) { 4754 var $el; 4755 4756 if ( url.gallery !== '' ) { 4757 4758 // If we can find element matching 'data-fancybox' atribute, then trigger click event for that .. 4759 $el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 ); 4760 4761 if ( $el.length ) { 4762 $el.trigger( 'click' ); 4763 4764 } else { 4765 4766 // .. if not, try finding element by ID 4767 $( "#" + $.escapeSelector( url.gallery ) + "" ).trigger( 'click' ); 4768 4769 } 4770 4771 } 4772 } 4773 4774 // Get gallery name from current instance 4775 function getGallery( instance ) { 4776 var opts; 4777 4778 if ( !instance ) { 4779 return false;
4780 } 4781 4782 opts = instance.current ? instance.current.opts : instance.opts; 4783 4784 return opts.$orig ? opts.$orig.data( 'fancybox' ) : ( opts.hash || '' ); 4785 } 4786 4787 // Star when DOM becomes ready 4788 $(function() { 4789 4790 // Small delay is used to allow other scripts to process "dom ready" event 4791 setTimeout(function() { 4792 4793 // Check if this module is not disabled 4794 if ( $.fancybox.defaults.hash === false ) { 4795 return; 4796 } 4797 4798 // Update hash when opening/closing fancyBox 4799 $(document).on({ 4800 'onInit.fb' : function( e, instance ) { 4801 var url, gallery; 4802 4803 if ( instance.group[ instance.currIndex ].opts.hash === false ) { 4804 return; 4805 } 4806 4807 url = parseUrl(); 4808 gallery = getGallery( instance ); 4809 4810 // Make sure gallery start index matches index from hash 4811 if ( gallery && url.gallery && gallery == url.gallery ) { 4812 instance.currIndex = url.index - 1; 4813 } 4814 4815 }, 4816 4817 'beforeShow.fb' : function( e, instance, current, firstRun ) { 4818 var gallery; 4819 4820 if ( current.opts.hash === false ) { 4821 return; 4822 } 4823 4824 gallery = getGallery( instance ); 4825 4826 // Update window hash 4827 if ( gallery && gallery !== '' ) { 4828 4829 if ( window.location.hash.indexOf( gallery ) < 0 ) { 4830 instance.opts.origHash = window.location.hash; 4831 } 4832 4833 currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' ); 4834 4835 if ( 'replaceState' in window.history ) { 4836 if ( timerID ) { 4837 clearTimeout( timerID ); 4838 } 4839 4840 timerID = setTimeout(function() { 4841 window.history[ firstRun ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' + currentHash ); 4842 4843 timerID = null; 4844 4845 }, 300); 4846 4847 } else { 4848 window.location.hash = currentHash; 4849 } 4850 4851 } 4852 4853 }, 4854 4855 'beforeClose.fb' : function( e, instance, current ) { 4856 var gallery, origHash; 4857 4858 if ( timerID ) { 4859 clearTimeout( timerID ); 4860 } 4861 4862 if ( current.opts.hash === false ) { 4863 return; 4864 } 4865 4866 gallery = getGallery( instance ); 4867 origHash = instance && instance.opts.origHash ? instance.opts.origHash : ''; 4868 4869 // Remove hash from location bar 4870 if ( gallery && gallery !== '' ) { 4871 4872 if ( 'replaceState' in history ) { 4873 window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash ); 4874 4875 } else { 4876 window.location.hash = origHash; 4877 4878 // Keep original scroll position 4879 $( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft ); 4880 } 4881 } 4882 4883 currentHash = null; 4884 } 4885 }); 4886 4887 // Check if need to close after url has changed 4888 $(window).on('hashchange.fb', function() { 4889 var url = parseUrl(); 4890 4891 if ( $.fancybox.getInstance() ) { 4892 if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) { 4893 currentHash = null; 4894 4895 $.fancybox.close(); 4896 } 4897 4898 } else if ( url.gallery !== '' ) { 4899 triggerFromUrl( url ); 4900 } 4901 }); 4902 4903 // If navigating away from current page 4904 $(window).one('unload.fb popstate.fb', function() { 4905 $.fancybox.getInstance( 'close', true, 0 ); 4906 }); 4907 4908 // Check current hash and trigger click event on matching element to start fancyBox, if needed 4909 triggerFromUrl( parseUrl() ); 4910 4911 }, 50); 4912 4913 }); 4914 4915 4916}(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.