1;(function (window, document, $, undefined) { 2 'use strict'; 3 4 // If there's no jQuery, fancyBox can't work 5 // ========================================= 6 7 if ( !$ ) { 8 return; 9 } 10 11 // Check if fancyBox is already initialized 12 // ======================================== 13 14 if ( $.fn.fancybox ) { 15 16 $.error('fancyBox already initialized'); 17 18 return; 19 } 20 21 // Private default settings 22 // ======================== 23 24 var defaults = { 25 26 // Enable infinite gallery navigation 27 loop : false, 28 29 // Space around image, ignored if zoomed-in or viewport smaller than 800px 30 margin : [44, 0], 31 32 // Horizontal space between slides 33 gutter : 50, 34 35 // Enable keyboard navigation 36 keyboard : true, 37 38 // Should display navigation arrows at the screen edges 39 arrows : true, 40 41 // Should display infobar (counter and arrows at the top) 42 infobar : false, 43 44 // Should display toolbar (buttons at the top) 45 toolbar : true, 46 47 // What buttons should appear in the top right corner. 48 // Buttons will be created using templates from `btnTpl` option 49 // and they will be placed into toolbar (class="fancybox-toolbar"` element) 50 buttons : [ 51 'slideShow', 52 'fullScreen', 53 'thumbs', 54 'close' 55 ], 56 57 // Detect "idle" time in seconds 58 idleTime : 4, 59 60 // Should display buttons at top right corner of the content 61 // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax' 62 // Use template from `btnTpl.smallBtn` for customization 63 smallBtn : 'auto', 64 65 // Disable right-click and use simple image protection for images 66 protect : false, 67 68 // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc 69 modal : false, 70 71 image : { 72 73 // Wait for images to load before displaying 74 // Requires predefined image dimensions 75 // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found 76 preload : "auto", 77 78 }, 79 80 ajax : { 81 82 // Object containing settings for ajax request 83 settings : { 84 85 // This helps to indicate that request comes from the modal 86 // Feel free to change naming 87 data : { 88 fancybox : true 89 } 90 } 91 92 }, 93 94 iframe : { 95 96 // Iframe template 97 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>', 98 99 // Preload iframe before displaying it 100 // This allows to calculate iframe content width and height 101 // (note: Due to "Same Origin Policy", you can't get cross domain data). 102 preload : true, 103 104 // Custom CSS styling for iframe wrapping element 105 // You can use this to set custom iframe dimensions 106 css : {}, 107 108 // Iframe tag attributes 109 attr : { 110 scrolling : 'auto' 111 } 112 113 }, 114 115 // Open/close animation type 116 // Possible values: 117 // false - disable 118 // "zoom" - zoom images from/to thumbnail 119 // "fade" 120 // "zoom-in-out" 121 // 122 animationEffect : "zoom", 123 124 // Duration in ms for open/close animation 125 animationDuration : 366, 126 127 // Should image change opacity while zooming 128 // If opacity is 'auto', then opacity will be changed if image and thumbnail have different aspect ratios 129 zoomOpacity : 'auto', 130 131 // Transition effect between slides 132 // 133 // Possible values: 134 // false - disable 135 // "fade' 136 // "slide' 137 // "circular' 138 // "tube' 139 // "zoom-in-out' 140 // "rotate' 141 // 142 transitionEffect : "fade", 143 144 // Duration in ms for transition animation 145 transitionDuration : 366, 146 147 // Custom CSS class for slide element 148 slideClass : '', 149 150 // Custom CSS class for layout 151 baseClass : '', 152 153 // Base template for layout 154 baseTpl : 155 '<div class="fancybox-container" role="dialog" tabindex="-1">' + 156 '<div class="fancybox-bg"></div>' + 157 '<div class="fancybox-inner">' + 158 '<div class="fancybox-infobar">' + 159 '<button data-fancybox-prev title="{{PREV}}" class="fancybox-button fancybox-button--left"></button>' + 160 '<div class="fancybox-infobar__body">' + 161 '<span data-fancybox-index></span> / <span data-fancybox-count></span>' + 162 '</div>' + 163 '<button data-fancybox-next title="{{NEXT}}" class="fancybox-button fancybox-button--right"></button>' + 164 '</div>' + 165 '<div class="fancybox-toolbar">' + 166 '{{BUTTONS}}' + 167 '</div>' + 168 '<div class="fancybox-navigation">' + 169 '<button data-fancybox-prev title="{{PREV}}" class="fancybox-arrow fancybox-arrow--left" />' + 170 '<button data-fancybox-next title="{{NEXT}}" class="fancybox-arrow fancybox-arrow--right" />' + 171 '</div>' + 172 '<div class="fancybox-stage"></div>' + 173 '<div class="fancybox-caption-wrap">' + 174 '<div class="fancybox-caption"></div>' + 175 '</div>' + 176 '</div>' + 177 '</div>', 178 179 // Loading indicator template 180 spinnerTpl : '<div class="fancybox-loading"></div>', 181 182 // Error message template 183 errorTpl : '<div class="fancybox-error"><p>{{ERROR}}<p></div>', 184 185 btnTpl : { 186 slideShow : '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}"></button>', 187 fullScreen : '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fullscreen" title="{{FULL_SCREEN}}"></button>', 188 thumbs : '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}"></button>', 189 close : '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}"></button>', 190 191 // This small close button will be appended to your html/inline/ajax content by default, 192 // if "smallBtn" option is not set to false 193 smallBtn : '<button data-fancybox-close class="fancybox-close-small" title="{{CLOSE}}"></button>' 194 }, 195 196 // Container is injected into this element 197 parentEl : 'body', 198 199 200 // Focus handling 201 // ============== 202 203 // Try to focus on the first focusable element after opening 204 autoFocus : true, 205 206 // Put focus back to active element after closing 207 backFocus : true, 208
209 // Do not let user to focus on element outside modal content 210 trapFocus : true, 211 212 213 // Module specific options 214 // ======================= 215 216 fullScreen : { 217 autoStart : false, 218 }, 219 220 touch : { 221 vertical : true, // Allow to drag content vertically 222 momentum : true // Continue movement after releasing mouse/touch when panning 223 }, 224 225 // Hash value when initializing manually, 226 // set `false` to disable hash change 227 hash : null, 228 229 // Customize or add new media types 230 // Example: 231 /* 232 media : { 233 youtube : { 234 params : { 235 autoplay : 0 236 } 237 } 238 } 239 */ 240 media : {}, 241 242 slideShow : { 243 autoStart : false, 244 speed : 4000 245 }, 246 247 thumbs : { 248 autoStart : false, // Display thumbnails on opening 249 hideOnClose : true // Hide thumbnail grid when closing animation starts 250 }, 251 252 // Callbacks 253 //========== 254 255 // See Documentation/API/Events for more information 256 // Example: 257 /* 258 afterShow: function( instance, current ) { 259 console.info( 'Clicked element:' ); 260 console.info( current.opts.$orig ); 261 } 262 */ 263 264 onInit : $.noop, // When instance has been initialized 265 266 beforeLoad : $.noop, // Before the content of a slide is being loaded 267 afterLoad : $.noop, // When the content of a slide is done loading 268 269 beforeShow : $.noop, // Before open animation starts 270 afterShow : $.noop, // When content is done loading and animating 271 272 beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close. 273 afterClose : $.noop, // After instance has been closed 274 275 onActivate : $.noop, // When instance is brought to front 276 onDeactivate : $.noop, // When other instance has been activated 277 278 279 // Interaction 280 // =========== 281 282 // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, 283 // each option can be string or method that returns value. 284 // 285 // Possible values: 286 // "close" - close instance 287 // "next" - move to next gallery item 288 // "nextOrClose" - move to next gallery item or close if gallery has only one item 289 // "toggleControls" - show/hide controls 290 // "zoom" - zoom image (if loaded) 291 // false - do nothing 292 293 // Clicked on the content 294 clickContent : function( current, event ) { 295 return current.type === 'image' ? 'zoom' : false; 296 }, 297 298 // Clicked on the slide 299 clickSlide : 'close', 300 301 // Clicked on the background (backdrop) element 302 clickOutside : 'close', 303 304 // Same as previous two, but for double click 305 dblclickContent : false, 306 dblclickSlide : false, 307 dblclickOutside : false, 308 309 310 // Custom options when mobile device is detected 311 // ============================================= 312 313 mobile : { 314 clickContent : function( current, event ) { 315 return current.type === 'image' ? 'toggleControls' : false; 316 }, 317 clickSlide : function( current, event ) { 318 return current.type === 'image' ? 'toggleControls' : "close"; 319 }, 320 dblclickContent : function( current, event ) { 321 return current.type === 'image' ? 'zoom' : false; 322 }, 323 dblclickSlide : function( current, event ) { 324 return current.type === 'image' ? 'zoom' : false; 325 } 326 }, 327 328 329 // Internationalization 330 // ============ 331 332 lang : 'en', 333 i18n : { 334 'en' : { 335 CLOSE : 'Close', 336 NEXT : 'Next', 337 PREV : 'Previous', 338 ERROR : 'The requested content cannot be loaded. <br/> Please try again later.', 339 PLAY_START : 'Start slideshow', 340 PLAY_STOP : 'Pause slideshow', 341 FULL_SCREEN : 'Full screen', 342 THUMBS : 'Thumbnails' 343 }, 344 'de' : { 345 CLOSE : 'Schliessen', 346 NEXT : 'Weiter', 347 PREV : 'Zurück', 348 ERROR : 'Die angeforderten Daten konnten nicht geladen werden. <br/>
348 Bitte versuchen Sie es später nochmal.', 349 PLAY_START : 'Diaschau starten', 350 PLAY_STOP : 'Diaschau beenden', 351 FULL_SCREEN : 'Vollbild', 352 THUMBS : 'Vorschaubilder' 353 } 354 } 355 356 }; 357 358 // Few useful variables and methods 359 // ================================ 360 361 var $W = $(window); 362 var $D = $(document); 363 364 var called = 0; 365 366 367 // Check if an object is a jQuery object and not a native JavaScript object 368 // ======================================================================== 369 370 var isQuery = function ( obj ) { 371 return obj && obj.hasOwnProperty && obj instanceof $; 372 }; 373 374 375 // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" 376 // =============================================================================== 377 378 var requestAFrame = (function () { 379 return window.requestAnimationFrame || 380 window.webkitRequestAnimationFrame || 381 window.mozRequestAnimationFrame || 382 window.oRequestAnimationFrame || 383 // if all else fails, use setTimeout 384 function (callback) { 385 return window.setTimeout(callback, 1000 / 60); 386 }; 387 })(); 388 389 390 // Detect the supported transition-end event property name 391 // ======================================================= 392 393 var transitionEnd = (function () { 394 var t, el = document.createElement("fakeelement"); 395 396 var transitions = { 397 "transition" : "transitionend", 398 "OTransition" : "oTransitionEnd", 399 "MozTransition" : "transitionend", 400 "WebkitTransition": "webkitTransitionEnd" 401 }; 402 403 for (t in transitions) { 404 if (el.style[t] !== undefined){ 405 return transitions[t]; 406 } 407 } 408 })(); 409 410 411 // Force redraw on an element. 412 // This helps in cases where the browser doesn't redraw an updated element properly. 413 // ================================================================================= 414 415 var forceRedraw = function( $el ) { 416 return ( $el && $el.length && $el[0].offsetHeight ); 417 }; 418 419 420 // Class definition 421 // ================ 422 423 var FancyBox = function( content, opts, index ) { 424 var self = this; 425 426 self.opts = $.extend( true, { index : index }, defaults, opts || {} ); 427 428 // Exclude buttons option from deep merging 429 if ( opts && $.isArray( opts.buttons ) ) { 430 self.opts.buttons = opts.buttons; 431 } 432 433 self.id = self.opts.id || ++called; 434 self.group = []; 435 436 self.currIndex = parseInt( self.opts.index, 10 ) || 0; 437 self.prevIndex = null; 438 439 self.prevPos = null; 440 self.currPos = 0; 441 442 self.firstRun = null; 443 444 // Create group elements from original item collection 445 self.createGroup( content ); 446 447 if ( !self.group.length ) { 448 return; 449 } 450 451 // Save last active element and current scroll position 452 self.$lastFocus = $(document.activeElement).blur(); 453 454 // Collection of gallery objects 455 self.slides = {}; 456 457 self.init( content ); 458 459 }; 460 461 $.extend(FancyBox.prototype, { 462 463 // Create DOM structure 464 // ==================== 465 466 init : function() { 467 var self = this; 468 469 var testWidth, $container, buttonStr; 470 471 var firstItemOpts = self.group[ self.currIndex ].opts; 472 473 self.scrollTop = $D.scrollTop(); 474 self.scrollLeft = $D.scrollLeft(); 475 476 477 // Hide scrollbars 478 // =============== 479 480 if ( !$.fancybox.getInstance() && !$.fancybox.isMobile && $( 'body' ).css('overflow') !== 'hidden' ) { 481 testWidth = $( 'body' ).width(); 482 483 $( 'html' ).addClass( 'fancybox-enabled' ); 484 485 // Compare body width after applying "overflow: hidden" 486 testWidth = $( 'body' ).width() - testWidth; 487 488 // If width has changed - compensate missing scrollbars by adding right margin 489 if ( testWidth > 1 ) { 490 $( 'head' ).append( '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar, .fancybox-enabled body { margin-right: ' + testWidth + 'px; }</style>' ); 491 } 492 } 493 494 495 // Build html markup and set references 496 // ==================================== 497 498 // Build html code for buttons and insert into main template 499 buttonStr = ''; 500 501 $.each( firstItemOpts.buttons, function( index, value ) { 502 buttonStr += ( firstItemOpts.btnTpl[ value ] || '' ); 503 }); 504 505 // Create markup from base template, it will be initially hidden to 506 // avoid unnecessary work like painting while initializing is not complete 507 $container = $( self.translate( self, firstItemOpts.baseTpl.replace( '\{\{BUTTONS\}\}', buttonStr ) ) ) 508 .addClass( 'fancybox-is-hidden' ) 509 .attr('id', 'fancybox-container-' + self.id) 510 .addClass( firstItemOpts.baseClass ) 511 .data( 'FancyBox', self ) 512 .prependTo( firstItemOpts.parentEl ); 513 514 // Create object holding references to jQuery wrapped nodes 515 self.$refs = { 516 container : $container 517 }; 518 519 [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption' ].forEach(function(item) { 520 self.$refs[ item ] = $container.find( '.fancybox-' + item ); 521 }); 522 523 // Check for redundant elements 524 if ( !firstItemOpts.arrows || self.group.length < 2 ) { 525 $container.find('.fancybox-navigation').remove(); 526 } 527 528 if ( !firstItemOpts.infobar ) { 529 self.$refs.infobar.remove(); 530 } 531 532 if ( !firstItemOpts.toolbar ) { 533 self.$refs.toolbar.remove(); 534 } 535 536 self.trigger( 'onInit' ); 537 538 // Bring to front and enable events 539 self.activate(); 540 541 // Build slides, load and reveal content 542 self.jumpTo( self.currIndex ); 543 }, 544 545 546 // Simple i18n support - replaces object keys found in template 547 // with corresponding values 548 // ============================================================ 549 550 translate : function( obj, str ) { 551 var arr = obj.opts.i18n[ obj.opts.lang ]; 552 553 return str.replace(/\{\{(\w+)\}\}/g, function(match, n) { 554 var value = arr[n]; 555 556 if ( value === undefined ) { 557 return match; 558 } 559 560 return value; 561 }); 562 }, 563 564 // Create array of gally item objects 565 // Check if each object has valid type and content 566 // =============================================== 567 568 createGroup : function ( content ) { 569 var self = this; 570 var items = $.makeArray( content ); 571 572 $.each(items, function( i, item ) { 573 var obj = {}, 574 opts = {}, 575 data = [], 576 $item, 577 type, 578 src, 579 srcParts; 580 581 // Step 1 - Make sure we have an object 582 // ==================================== 583 584 if ( $.isPlainObject( item ) ) { 585 586 // We probably have manual usage here, something like 587 // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) 588 589 obj = item; 590 opts = item.opts || item; 591 592 } else if ( $.type( item ) === 'object' && $( item ).length ) { 593 594 // Here we propbably have jQuery collection returned by some selector 595 596 $item = $( item ); 597 data = $item.data(); 598 599 opts = 'options' in data ? data.options : {}; 600 opts = $.type( opts ) === 'object' ? opts : {}; 601 602 obj.src = 'src' in data ? data.src : ( opts.src || $item.attr( 'href' ) ); 603
604 [ 'width', 'height', 'thumb', 'type', 'filter' ].forEach(function(item) { 605 if ( item in data ) { 606 opts[ item ] = data[ item ]; 607 } 608 }); 609 610 if ( 'srcset' in data ) { 611 opts.image = { srcset : data.srcset }; 612 } 613 614 opts.$orig = $item; 615 616 if ( !obj.type && !obj.src ) { 617 obj.type = 'inline'; 618 obj.src = item; 619 } 620 621 } else { 622 623 // Assume we have a simple html code, for example: 624 // $.fancybox.open( '<div><h1>Hi!</h1></div>' ); 625 626 obj = { 627 type : 'html', 628 src : item + '' 629 }; 630 631 } 632 633 // Each gallery object has full collection of options 634 obj.opts = $.extend( true, {}, self.opts, opts ); 635 636 if ( $.fancybox.isMobile ) { 637 obj.opts = $.extend( true, {}, obj.opts, obj.opts.mobile ); 638 } 639 640 641 // Step 2 - Make sure we have content type, if not - try to guess 642 // ============================================================== 643 644 type = obj.type || obj.opts.type; 645 src = obj.src || ''; 646 647 if ( !type && src ) { 648 if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) { 649 type = 'image'; 650 651 } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) { 652 type = 'pdf'; 653 654 } else if ( src.charAt(0) === '#' ) { 655 type = 'inline'; 656 } 657 } 658 659 obj.type = type; 660 661 662 // Step 3 - Some adjustments 663 // ========================= 664 665 obj.index = self.group.length; 666 667 // Check if $orig and $thumb objects exist 668 if ( obj.opts.$orig && !obj.opts.$orig.length ) { 669 delete obj.opts.$orig; 670 } 671 672 if ( !obj.opts.$thumb && obj.opts.$orig ) { 673 obj.opts.$thumb = obj.opts.$orig.find( 'img:first' ); 674 } 675 676 if ( obj.opts.$thumb && !obj.opts.$thumb.length ) { 677 delete obj.opts.$thumb; 678 } 679 680 // Caption is a "special" option, it can be passed as a method 681 if ( $.type( obj.opts.caption ) === 'function' ) { 682 obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] ); 683 684 } else if ( 'caption' in data ) { 685 obj.opts.caption = data.caption; 686 } 687 688 // Make sure we have caption as a string 689 obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + ''; 690 691 // Check if url contains "filter" used to filter the content 692 // Example: "ajax.html #something" 693 if ( type === 'ajax' ) { 694 srcParts = src.split(/\s+/, 2); 695 696 if ( srcParts.length > 1 ) { 697 obj.src = srcParts.shift(); 698 699 obj.opts.filter = srcParts.shift(); 700 } 701 } 702 703 if ( obj.opts.smallBtn == 'auto' ) { 704 705 if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) { 706 obj.opts.toolbar = false; 707 obj.opts.smallBtn = true; 708 709 } else { 710 obj.opts.smallBtn = false; 711 } 712 713 } 714 715 // If the type is "pdf", then simply load file into iframe 716 if ( type === 'pdf' ) { 717 obj.type = 'iframe'; 718 719 obj.opts.iframe.preload = false; 720 } 721 722 // Hide all buttons and disable interactivity for modal items 723 if ( obj.opts.modal ) { 724 725 obj.opts = $.extend(true, obj.opts, { 726 // Remove buttons 727 infobar : 0, 728 toolbar : 0, 729 730 smallBtn : 0, 731 732 // Disable keyboard navigation 733 keyboard : 0, 734 735 // Disable some modules 736 slideShow : 0, 737 fullScreen : 0, 738 thumbs : 0, 739 touch : 0, 740 741 // Disable click event handlers 742 clickContent : false, 743 clickSlide : false, 744 clickOutside : false, 745 dblclickContent : false, 746 dblclickSlide : false, 747 dblclickOutside : false 748 }); 749 750 } 751 752 // Step 4 - Add processed object to group 753 // ====================================== 754 755 self.group.push( obj ); 756 757 }); 758 759 }, 760 761 762 // Attach an event handler functions for: 763 // - navigation buttons 764 // - browser scrolling, resizing; 765 // - focusing 766 // - keyboard 767 // - detect idle 768 // ====================================== 769 770 addEvents : function() { 771 var self = this; 772 773 self.removeEvents(); 774 775 // Make navigation elements clickable 776 self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) { 777 e.stopPropagation(); 778 e.preventDefault(); 779 780 self.close( e ); 781 782 }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) { 783 e.stopPropagation(); 784 e.preventDefault(); 785 786 self.previous(); 787 788 }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) { 789 e.stopPropagation(); 790 e.preventDefault(); 791 792 self.next(); 793 794 }); 795 796 797 // Handle page scrolling and browser resizing 798 $W.on('orientationchange.fb resize.fb', function(e) { 799 800 if ( e && e.originalEvent && e.originalEvent.type === "resize" ) { 801 802 requestAFrame(function() {
803 self.update(); 804 }); 805 806 } else { 807 808 self.$refs.stage.hide(); 809 810 setTimeout(function() { 811 self.$refs.stage.show(); 812 813 self.update(); 814 }, 500); 815 816 } 817 818 }); 819 820 // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal 821 // (a.k.a. "escaping the modal") 822 $D.on('focusin.fb', function(e) { 823 var instance = $.fancybox ? $.fancybox.getInstance() : null; 824 825 if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) { 826 return; 827 } 828 829 if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) { 830 e.stopPropagation(); 831 832 instance.focus(); 833 834 // Sometimes page gets scrolled, set it back 835 $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); 836 } 837 }); 838 839 840 // Enable keyboard navigation 841 $D.on('keydown.fb', function (e) { 842 var current = self.current, 843 keycode = e.keyCode || e.which; 844 845 if ( !current || !current.opts.keyboard ) { 846 return; 847 } 848 849 if ( $(e.target).is('input') || $(e.target).is('textarea') ) { 850 return; 851 } 852 853 // Backspace and Esc keys 854 if ( keycode === 8 || keycode === 27 ) { 855 e.preventDefault(); 856 857 self.close( e ); 858 859 return; 860 } 861 862 // Left arrow and Up arrow 863 if ( keycode === 37 || keycode === 38 ) { 864 e.preventDefault(); 865 866 self.previous(); 867 868 return; 869 } 870 871 // Righ arrow and Down arrow 872 if ( keycode === 39 || keycode === 40 ) { 873 e.preventDefault(); 874 875 self.next(); 876 877 return; 878 } 879 880 self.trigger('afterKeydown', e, keycode); 881 }); 882 883 884 // Hide controls after some inactivity period 885 if ( self.group[ self.currIndex ].opts.idleTime ) { 886 self.idleSecondsCounter = 0; 887 888 $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() { 889 self.idleSecondsCounter = 0; 890 891 if ( self.isIdle ) { 892 self.showControls(); 893 } 894 895 self.isIdle = false; 896 }); 897 898 self.idleInterval = window.setInterval(function() { 899 900 self.idleSecondsCounter++; 901 902 if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime ) { 903 self.isIdle = true; 904 self.idleSecondsCounter = 0; 905 906 self.hideControls(); 907 } 908 909 }, 1000); 910 } 911 912 }, 913 914 915 // Remove events added by the core 916 // =============================== 917 918 removeEvents : function () { 919 var self = this; 920 921 $W.off( 'orientationchange.fb resize.fb' ); 922 $D.off( 'focusin.fb keydown.fb .fb-idle' ); 923 924 this.$refs.container.off( '.fb-close .fb-prev .fb-next' ); 925 926 if ( self.idleInterval ) { 927 window.clearInterval( self.idleInterval ); 928 929 self.idleInterval = null; 930 } 931 }, 932 933 934 // Change to previous gallery item 935 // =============================== 936 937 previous : function( duration ) { 938 return this.jumpTo( this.currPos - 1, duration ); 939 }, 940 941 942 // Change to next gallery item 943 // =========================== 944 945 next : function( duration ) { 946 return this.jumpTo( this.currPos + 1, duration ); 947 }, 948 949 950 // Switch to selected gallery item 951 // =============================== 952 953 jumpTo : function ( pos, duration, slide ) { 954 var self = this, 955 firstRun, 956 loop, 957 current, 958 previous, 959 canvasWidth, 960 currentPos, 961 transitionProps; 962 963 var groupLen = self.group.length; 964 965 if ( self.isSliding || self.isClosing || ( self.isAnimating && self.firstRun ) ) { 966 return; 967 } 968 969 pos = parseInt( pos, 10 ); 970 loop = self.current ? self.current.opts.loop : self.opts.loop; 971 972 if ( !loop && ( pos < 0 || pos >= groupLen ) ) { 973 return false;
974 } 975 976 firstRun = self.firstRun = ( self.firstRun === null ); 977 978 if ( groupLen < 2 && !firstRun && !!self.isSliding ) { 979 return; 980 } 981 982 previous = self.current; 983 984 self.prevIndex = self.currIndex; 985 self.prevPos = self.currPos; 986 987 // Create slides 988 current = self.createSlide( pos ); 989 990 if ( groupLen > 1 ) { 991 if ( loop || current.index > 0 ) { 992 self.createSlide( pos - 1 ); 993 } 994 995 if ( loop || current.index < groupLen - 1 ) { 996 self.createSlide( pos + 1 ); 997 } 998 } 999 1000 self.current = current; 1001 self.currIndex = current.index; 1002 self.currPos = current.pos; 1003 1004 self.trigger( 'beforeShow', firstRun ); 1005 1006 self.updateControls(); 1007 1008 currentPos = $.fancybox.getTranslate( current.$slide ); 1009 1010 current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' ); 1011 current.forcedDuration = undefined; 1012 1013 if ( $.isNumeric( duration ) ) { 1014 current.forcedDuration = duration; 1015 } else { 1016 duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ]; 1017 } 1018 1019 duration = parseInt( duration, 10 ); 1020 1021 // Fresh start - reveal container, current slide and start loading content 1022 if ( firstRun ) { 1023 1024 if ( current.opts.animationEffect && duration ) { 1025 self.$refs.container.css( 'transition-duration', duration + 'ms' ); 1026 } 1027 1028 self.$refs.container.removeClass( 'fancybox-is-hidden' ); 1029 1030 forceRedraw( self.$refs.container ); 1031 1032 self.$refs.container.addClass( 'fancybox-is-open' ); 1033 1034 // Make first slide visible (to display loading icon, if needed) 1035 current.$slide.addClass( 'fancybox-slide--current' ); 1036 1037 self.loadSlide( current ); 1038 1039 self.preload(); 1040 1041 return; 1042 } 1043 1044 // Clean up 1045 $.each(self.slides, function( index, slide ) { 1046 $.fancybox.stop( slide.$slide ); 1047 }); 1048 1049 // Make current that slide is visible even if content is still loading 1050 current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' ); 1051 1052 // If slides have been dragged, animate them to correct position 1053 if ( current.isMoved ) { 1054 canvasWidth = Math.round( current.$slide.width() ); 1055 1056 $.each(self.slides, function( index, slide ) { 1057 var pos = slide.pos - current.pos; 1058 1059 $.fancybox.animate( slide.$slide, { 1060 top : 0, 1061 left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ) 1062 }, duration, function() { 1063 1064 slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' ); 1065 1066 if ( slide.pos === self.currPos ) { 1067 current.isMoved = false; 1068 1069 self.complete(); 1070 } 1071 }); 1072 }); 1073 1074 } else { 1075 self.$refs.stage.children().removeAttr( 'style' ); 1076 } 1077 1078 // Start transition that reveals current content 1079 // or wait when it will be loaded 1080 1081 if ( current.isLoaded ) { 1082 self.revealContent( current ); 1083 1084 } else { 1085 self.loadSlide( current ); 1086 } 1087 1088 self.preload(); 1089 1090 if ( previous.pos === current.pos ) { 1091 return; 1092 } 1093 1094 // Handle previous slide 1095 // ===================== 1096 1097 transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' ); 1098 1099 previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ); 1100 1101 previous.isComplete = false;
1102 1103 if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) { 1104 return; 1105 } 1106 1107 if ( current.isMoved ) { 1108 previous.$slide.addClass( transitionProps ); 1109 1110 } else { 1111 1112 transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect; 1113 1114 $.fancybox.animate( previous.$slide, transitionProps, duration, function() { 1115 previous.$slide.removeClass( transitionProps ).removeAttr( 'style' ); 1116 }); 1117 1118 } 1119 1120 }, 1121 1122 1123 // Create new "slide" element 1124 // These are gallery items that are actually added to DOM 1125 // ======================================================= 1126 1127 createSlide : function( pos ) { 1128 1129 var self = this; 1130 var $slide; 1131 var index; 1132 1133 index = pos % self.group.length; 1134 index = index < 0 ? self.group.length + index : index; 1135 1136 if ( !self.slides[ pos ] && self.group[ index ] ) { 1137 $slide = $('<div class="fancybox-slide"></div>').appendTo( self.$refs.stage ); 1138 1139 self.slides[ pos ] = $.extend( true, {}, self.group[ index ], { 1140 pos : pos, 1141 $slide : $slide, 1142 isLoaded : false, 1143 }); 1144 1145 self.updateSlide( self.slides[ pos ] ); 1146 } 1147 1148 return self.slides[ pos ]; 1149 }, 1150 1151 1152 // Scale image to the actual size of the image 1153 // =========================================== 1154 1155 scaleToActual : function( x, y, duration ) { 1156 1157 var self = this; 1158 1159 var current = self.current; 1160 var $what = current.$content; 1161 1162 var imgPos, posX, posY, scaleX, scaleY; 1163 1164 var canvasWidth = parseInt( current.$slide.width(), 10 ); 1165 var canvasHeight = parseInt( current.$slide.height(), 10 ); 1166 1167 var newImgWidth = current.width; 1168 var newImgHeight = current.height; 1169 1170 if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating) { 1171 return; 1172 } 1173 1174 $.fancybox.stop( $what ); 1175 1176 self.isAnimating = true; 1177 1178 x = x === undefined ? canvasWidth * 0.5 : x; 1179 y = y === undefined ? canvasHeight * 0.5 : y; 1180 1181 imgPos = $.fancybox.getTranslate( $what ); 1182 1183 scaleX = newImgWidth / imgPos.width; 1184 scaleY = newImgHeight / imgPos.height; 1185 1186 // Get center position for original image 1187 posX = ( canvasWidth * 0.5 - newImgWidth * 0.5 ); 1188 posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 ); 1189 1190 // Make sure image does not move away from edges 1191 if ( newImgWidth > canvasWidth ) { 1192 posX = imgPos.left * scaleX - ( ( x * scaleX ) - x ); 1193 1194 if ( posX > 0 ) { 1195 posX = 0; 1196 } 1197 1198 if ( posX < canvasWidth - newImgWidth ) { 1199 posX = canvasWidth - newImgWidth; 1200 } 1201 } 1202 1203 if ( newImgHeight > canvasHeight) { 1204 posY = imgPos.top * scaleY - ( ( y * scaleY ) - y ); 1205 1206 if ( posY > 0 ) { 1207 posY = 0; 1208 } 1209 1210 if ( posY < canvasHeight - newImgHeight ) { 1211 posY = canvasHeight - newImgHeight; 1212 } 1213 } 1214 1215 self.updateCursor( newImgWidth, newImgHeight ); 1216 1217 $.fancybox.animate( $what, { 1218 top : posY, 1219 left : posX, 1220 scaleX : scaleX, 1221 scaleY : scaleY 1222 }, duration || 330, function() { 1223 self.isAnimating = false; 1224 }); 1225 1226 // Stop slideshow 1227 if ( self.SlideShow && self.SlideShow.isActive ) { 1228 self.SlideShow.stop(); 1229 } 1230 }, 1231 1232 1233 // Scale image to fit inside parent element 1234 // ======================================== 1235 1236 scaleToFit : function( duration ) { 1237 1238 var self = this; 1239 1240 var current = self.current; 1241 var $what = current.$content; 1242 var end; 1243 1244 if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) { 1245 return; 1246 } 1247 1248 $.fancybox.stop( $what ); 1249 1250 self.isAnimating = true; 1251 1252 end = self.getFitPos( current ); 1253 1254 self.updateCursor( end.width, end.height ); 1255 1256 $.fancybox.animate( $what, { 1257 top : end.top, 1258 left : end.left, 1259 scaleX : end.width / $what.width(), 1260 scaleY : end.height / $what.height() 1261 }, duration || 330, function() { 1262 self.isAnimating = false;
1263 }); 1264 1265 }, 1266 1267 // Calculate image size to fit inside viewport 1268 // =========================================== 1269 1270 getFitPos : function( slide ) { 1271 var self = this; 1272 var $what = slide.$content; 1273 1274 var imgWidth = slide.width; 1275 var imgHeight = slide.height; 1276 1277 var margin = slide.opts.margin; 1278 1279 var canvasWidth, canvasHeight, minRatio, width, height; 1280 1281 if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) { 1282 return false; 1283 } 1284 1285 // Convert "margin to CSS style: [ top, right, bottom, left ] 1286 if ( $.type( margin ) === "number" ) { 1287 margin = [ margin, margin ]; 1288 } 1289 1290 if ( margin.length == 2 ) { 1291 margin = [ margin[0], margin[1], margin[0], margin[1] ]; 1292 } 1293 1294 if ( $W.width() < 800 ) { 1295 margin = [ 0, 0, 0, 0 ]; 1296 } 1297 1298 // We can not use $slide width here, because it can have different diemensions while in transiton 1299 canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] ); 1300 canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] ); 1301 1302 minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight ); 1303 1304 width = Math.floor( minRatio * imgWidth ); 1305 height = Math.floor( minRatio * imgHeight ); 1306 1307 // Use floor rounding to make sure it really fits 1308 return { 1309 top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ], 1310 left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ], 1311 width : width, 1312 height : height 1313 }; 1314 1315 }, 1316 1317 1318 // Update position and content of all slides 1319 // ========================================= 1320 1321 update : function() { 1322 1323 var self = this; 1324 1325 $.each( self.slides, function( key, slide ) { 1326 self.updateSlide( slide ); 1327 }); 1328 1329 }, 1330 1331 1332 // Update slide position and scale content to fit 1333 // ============================================== 1334 1335 updateSlide : function( slide ) { 1336 1337 var self = this; 1338 var $what = slide.$content; 1339 1340 if ( $what && ( slide.width || slide.height ) ) { 1341 $.fancybox.stop( $what ); 1342 1343 $.fancybox.setTranslate( $what, self.getFitPos( slide ) ); 1344 1345 if ( slide.pos === self.currPos ) { 1346 self.updateCursor(); 1347 } 1348 } 1349 1350 slide.$slide.trigger( 'refresh' ); 1351 1352 self.trigger( 'onUpdate', slide ); 1353 1354 }, 1355 1356 // Update cursor style depending if content can be zoomed 1357 // ====================================================== 1358 1359 updateCursor : function( nextWidth, nextHeight ) { 1360 1361 var self = this; 1362 var isScaledDown; 1363 1364 var $container = self.$refs.container.removeClass('fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut'); 1365 1366 if ( !self.current || self.isClosing ) { 1367 return; 1368 } 1369 1370 if ( self.isZoomable() ) { 1371 1372 $container.addClass( 'fancybox-is-zoomable' ); 1373 1374 if ( nextWidth !== undefined && nextHeight !== undefined ) { 1375 isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height; 1376 1377 } else { 1378 isScaledDown = self.isScaledDown(); 1379 } 1380 1381 if ( isScaledDown ) { 1382 1383 // If image is scaled down, then, obviously, it can be zoomed to full size 1384 $container.addClass('fancybox-can-zoomIn'); 1385 1386 } else { 1387 1388 if ( self.current.opts.touch ) { 1389 1390 // If image size ir largen than available available and touch module is not disable, 1391 // then user can do panning 1392 $container.addClass('fancybox-can-drag'); 1393 1394 } else { 1395 $container.addClass('fancybox-can-zoomOut'); 1396 } 1397 1398 } 1399 1400 } else if ( self.current.opts.touch ) { 1401 $container.addClass('fancybox-can-drag'); 1402 } 1403 1404 }, 1405 1406 1407 // Check if current slide is zoomable 1408 // ================================== 1409 1410 isZoomable : function() { 1411 1412 var self = this; 1413 1414 var current = self.current; 1415 var fitPos; 1416 1417 if ( !current || self.isClosing ) { 1418 return; 1419 } 1420 1421 // Assume that slide is zoomable if 1422 // - image is loaded successfuly 1423 // - click action is "zoom" 1424 // - actual size of the image is smaller than available area 1425 if ( current.type === 'image' && current.isLoaded && !current.hasError && 1426 ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) ) 1427 ) { 1428 1429 fitPos = self.getFitPos( current ); 1430 1431 if ( current.width > fitPos.width || current.height > fitPos.height ) { 1432 return true; 1433 } 1434 1435 } 1436 1437 return false;
1438 1439 }, 1440 1441 1442 // Check if current image dimensions are smaller than actual 1443 // ========================================================= 1444 1445 isScaledDown : function() { 1446 1447 var self = this; 1448 1449 var current = self.current; 1450 var $what = current.$content; 1451 1452 var rez = false; 1453 1454 if ( $what ) { 1455 rez = $.fancybox.getTranslate( $what ); 1456 rez = rez.width < current.width || rez.height < current.height; 1457 } 1458 1459 return rez; 1460 1461 }, 1462 1463 1464 // Check if image dimensions exceed parent element 1465 // =============================================== 1466 1467 canPan : function() { 1468 1469 var self = this; 1470 1471 var current = self.current; 1472 var $what = current.$content; 1473 1474 var rez = false; 1475 1476 if ( $what ) { 1477 rez = self.getFitPos( current ); 1478 rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1; 1479 1480 } 1481 1482 return rez; 1483 1484 }, 1485 1486 1487 // Load content into the slide 1488 // =========================== 1489 1490 loadSlide : function( slide ) { 1491 1492 var self = this, type, $slide; 1493 var ajaxLoad; 1494 1495 if ( slide.isLoading ) { 1496 return; 1497 } 1498 1499 if ( slide.isLoaded ) { 1500 return; 1501 } 1502 1503 slide.isLoading = true; 1504 1505 self.trigger( 'beforeLoad', slide ); 1506 1507 type = slide.type; 1508 $slide = slide.$slide; 1509 1510 $slide 1511 .off( 'refresh' ) 1512 .trigger( 'onReset' ) 1513 .addClass( 'fancybox-slide--' + ( type || 'unknown' ) ) 1514 .addClass( slide.opts.slideClass ); 1515 1516 // Create content depending on the type 1517 1518 switch ( type ) { 1519 1520 case 'image': 1521 1522 self.setImage( slide ); 1523 1524 break; 1525 1526 case 'iframe': 1527 1528 self.setIframe( slide ); 1529 1530 break; 1531 1532 case 'html': 1533 1534 self.setContent( slide, slide.src || slide.content ); 1535 1536 break; 1537 1538 case 'inline': 1539 1540 if ( $( slide.src ).length ) { 1541 self.setContent( slide, $( slide.src ) ); 1542 1543 } else { 1544 self.setError( slide ); 1545 } 1546 1547 break; 1548 1549 case 'ajax': 1550 1551 self.showLoading( slide ); 1552 1553 ajaxLoad = $.ajax( $.extend( {}, slide.opts.ajax.settings, { 1554 url : slide.src, 1555 success : function ( data, textStatus ) { 1556 1557 if ( textStatus === 'success' ) { 1558 self.setContent( slide, data ); 1559 } 1560 1561 }, 1562 error : function ( jqXHR, textStatus ) { 1563 1564 if ( jqXHR && textStatus !== 'abort' ) { 1565 self.setError( slide ); 1566 } 1567 1568 } 1569 })); 1570 1571 $slide.one( 'onReset', function () { 1572 ajaxLoad.abort(); 1573 }); 1574 1575 break; 1576 1577 default: 1578 1579 self.setError( slide ); 1580 1581 break; 1582 1583 } 1584 1585 return true; 1586 1587 }, 1588 1589 1590 // Use thumbnail image, if possible 1591 // ================================ 1592 1593 setImage : function( slide ) { 1594 1595 var self = this; 1596 var srcset = slide.opts.image.srcset; 1597 1598 var found, temp, pxRatio, windowWidth; 1599 1600 // If we have "srcset", then we need to find matching "src" value. 1601 // This is necessary, because when you set an src attribute, the browser will preload the image 1602 // before any javascript or even CSS is applied. 1603 if ( srcset ) { 1604 pxRatio = window.devicePixelRatio || 1; 1605 windowWidth = window.innerWidth * pxRatio; 1606 1607 temp = srcset.split(',').map(function ( el ) { 1608 var ret = {}; 1609
1610 el.trim().split(/\s+/).forEach(function ( el, i ) { 1611 var value = parseInt( el.substring(0, el.length - 1), 10 ); 1612 1613 if ( i === 0 ) { 1614 return ( ret.url = el ); 1615 } 1616 1617 if ( value ) { 1618 ret.value = value; 1619 ret.postfix = el[ el.length - 1 ]; 1620 } 1621 1622 }); 1623 1624 return ret; 1625 }); 1626 1627 // Sort by value 1628 temp.sort(function (a, b) { 1629 return a.value - b.value; 1630 }); 1631 1632 // Ok, now we have an array of all srcset values 1633 for ( var j = 0; j < temp.length; j++ ) { 1634 var el = temp[ j ]; 1635 1636 if ( ( el.postfix === 'w' && el.value >= windowWidth ) || ( el.postfix === 'x' && el.value >= pxRatio ) ) { 1637 found = el; 1638 break; 1639 } 1640 } 1641 1642 // If not found, take the last one 1643 if ( !found && temp.length ) { 1644 found = temp[ temp.length - 1 ]; 1645 } 1646 1647 if ( found ) { 1648 slide.src = found.url; 1649 1650 // If we have default width/height values, we can calculate height for matching source 1651 if ( slide.width && slide.height && found.postfix == 'w' ) { 1652 slide.height = ( slide.width / slide.height ) * found.value; 1653 slide.width = found.value; 1654 } 1655 } 1656 } 1657 1658 // This will be wrapper containing both ghost and actual image 1659 slide.$content = $('<div class="fancybox-image-wrap"></div>') 1660 .addClass( 'fancybox-is-hidden' ) 1661 .appendTo( slide.$slide ); 1662 1663 1664 // If we have a thumbnail, we can display it while actual image is loading 1665 // Users will not stare at black screen and actual image will appear gradually 1666 if ( slide.opts.preload !== false && slide.opts.width && slide.opts.height && ( slide.opts.thumb || slide.opts.$thumb ) ) { 1667 1668 slide.width = slide.opts.width; 1669 slide.height = slide.opts.height; 1670 1671 slide.$ghost = $('<img />') 1672 .one('error', function() { 1673 1674 $(this).remove(); 1675 1676 slide.$ghost = null; 1677 1678 self.setBigImage( slide ); 1679 1680 }) 1681 .one('load', function() { 1682 1683 self.afterLoad( slide ); 1684 1685 self.setBigImage( slide ); 1686 1687 }) 1688 .addClass( 'fancybox-image' ) 1689 .appendTo( slide.$content ) 1690 .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) ); 1691 1692 } else { 1693 1694 self.setBigImage( slide ); 1695 1696 } 1697 1698 }, 1699 1700 1701 // Create full-size image 1702 // ====================== 1703 1704 setBigImage : function ( slide ) { 1705 var self = this; 1706 var $img = $('<img />'); 1707 1708 slide.$image = $img 1709 .one('error', function() { 1710 1711 self.setError( slide ); 1712 1713 }) 1714 .one('load', function() { 1715 1716 // Clear timeout that checks if loading icon needs to be displayed 1717 clearTimeout( slide.timouts ); 1718 1719 slide.timouts = null; 1720 1721 if ( self.isClosing ) { 1722 return; 1723 } 1724 1725 slide.width = this.naturalWidth; 1726 slide.height = this.naturalHeight; 1727 1728 if ( slide.opts.image.srcset ) { 1729 $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset ); 1730 } 1731 1732 self.hideLoading( slide ); 1733 1734 if ( slide.$ghost ) { 1735 1736 slide.timouts = setTimeout(function() { 1737 slide.timouts = null; 1738 1739 slide.$ghost.hide(); 1740 1741 }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) ); 1742 1743 } else { 1744 self.afterLoad( slide ); 1745 } 1746 1747 }) 1748 .addClass( 'fancybox-image' ) 1749 .attr('src', slide.src) 1750 .appendTo( slide.$content ); 1751 1752 if ( $img[0].complete ) { 1753 $img.trigger( 'load' ); 1754 1755 } else if( $img[0].error ) { 1756 $img.trigger( 'error' ); 1757 1758 } else { 1759 1760 slide.timouts = setTimeout(function() { 1761 if ( !$img[0].complete && !slide.hasError ) { 1762 self.showLoading( slide ); 1763 } 1764 1765 }, 100); 1766 1767 } 1768 1769 }, 1770 1771 1772 // Create iframe wrapper, iframe and bindings 1773 // ========================================== 1774 1775 setIframe : function( slide ) { 1776 var self = this, 1777 opts = slide.opts.iframe, 1778 $slide = slide.$slide, 1779 $iframe; 1780 1781 slide.$content = $('<div class="fancybox-content' + ( opts.preload ? ' fancybox-is-hidden' : '' ) + '"></div>') 1782 .css( opts.css ) 1783 .appendTo( $slide ); 1784 1785 $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) ) 1786 .attr( opts.attr ) 1787 .appendTo( slide.$content ); 1788 1789 if ( opts.preload ) { 1790 1791 self.showLoading( slide ); 1792 1793 // Unfortunately, it is not always possible to determine if iframe is successfully loaded 1794 // (due to browser security policy) 1795 1796 $iframe.on('load.fb error.fb', function(e) { 1797 this.isReady = 1; 1798 1799 slide.$slide.trigger( 'refresh' ); 1800 1801 self.afterLoad( slide ); 1802 }); 1803 1804 // Recalculate iframe content size 1805 // =============================== 1806 1807 $slide.on('refresh.fb', function() { 1808 var $wrap = slide.$content, 1809 $contents, 1810 $body, 1811 scrollWidth, 1812 frameWidth, 1813 frameHeight; 1814 1815 if ( $iframe[0].isReady !== 1 ) { 1816 return; 1817 } 1818 1819 // Check if content is accessible, 1820 // it will fail if frame is not with the same origin 1821 1822 try { 1823 $contents = $iframe.contents(); 1824 $body = $contents.find('body'); 1825 1826 } catch (ignore) {} 1827 1828 // Calculate dimensions for the wrapper 1829 if ( $body && $body.length && !( opts.css.width !== undefined && opts.css.height !== undefined ) ) { 1830 1831 scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth; 1832 1833 frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) );
1834 frameHeight = Math.ceil( $body.outerHeight(true) ); 1835 1836 // Resize wrapper to fit iframe content 1837 $wrap.css({ 1838 'width' : opts.css.width === undefined ? frameWidth + ( $wrap.outerWidth() - $wrap.innerWidth() ) : opts.css.width, 1839 'height' : opts.css.height === undefined ? frameHeight + ( $wrap.outerHeight() - $wrap.innerHeight() ) : opts.css.height 1840 }); 1841 1842 } 1843 1844 $wrap.removeClass( 'fancybox-is-hidden' ); 1845 1846 }); 1847 1848 } else { 1849 1850 this.afterLoad( slide ); 1851 1852 } 1853 1854 $iframe.attr( 'src', slide.src ); 1855 1856 if ( slide.opts.smallBtn === true ) { 1857 slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) ); 1858 } 1859 1860 // Remove iframe if closing or changing gallery item 1861 $slide.one( 'onReset', function () { 1862 1863 // This helps IE not to throw errors when closing 1864 try { 1865 1866 $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' ); 1867 1868 } catch ( ignore ) {} 1869 1870 $( this ).empty(); 1871 1872 slide.isLoaded = false; 1873 1874 }); 1875 1876 }, 1877 1878 1879 // Wrap and append content to the slide 1880 // ====================================== 1881 1882 setContent : function ( slide, content ) { 1883 1884 var self = this; 1885 1886 if ( self.isClosing ) { 1887 return; 1888 } 1889 1890 self.hideLoading( slide ); 1891 1892 slide.$slide.empty(); 1893 1894 if ( isQuery( content ) && content.parent().length ) { 1895 1896 // If content is a jQuery object, then it will be moved to the slide. 1897 // The placeholder is created so we will know where to put it back. 1898 // If user is navigating gallery fast, then the content might be already inside fancyBox 1899 // ===================================================================================== 1900 1901 // Make sure content is not already moved to fancyBox 1902 content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' ); 1903 1904 // Create temporary element marking original place of the content 1905 slide.$placeholder = $( '<div></div>' ).hide().insertAfter( content ); 1906 1907 // Make sure content is visible 1908 content.css('display', 'inline-block'); 1909 1910 } else if ( !slide.hasError ) { 1911 1912 // If content is just a plain text, try to convert it to html 1913 if ( $.type( content ) === 'string' ) { 1914 content = $('<div>').append( $.trim( content ) ).contents(); 1915 1916 // If we have text node, then add wrapping element to make vertical alignment work 1917 if ( content[0].nodeType === 3 ) { 1918 content = $('<div>').html( content ); 1919 } 1920 } 1921 1922 // If "filter" option is provided, then filter content 1923 if ( slide.opts.filter ) { 1924 content = $('<div>').html( content ).find( slide.opts.filter ); 1925 } 1926 1927 } 1928 1929 slide.$slide.one('onReset', function () { 1930 1931 // Put content back 1932 if ( slide.$placeholder ) { 1933 slide.$placeholder.after( content.hide() ).remove(); 1934 1935 slide.$placeholder = null; 1936 } 1937 1938 // Remove custom close button 1939 if ( slide.$smallBtn ) { 1940 slide.$smallBtn.remove(); 1941 1942 slide.$smallBtn = null; 1943 } 1944 1945 // Remove content and mark slide as not loaded 1946 if ( !slide.hasError ) { 1947 $(this).empty(); 1948 1949 slide.isLoaded = false; 1950 } 1951 1952 }); 1953 1954 slide.$content = $( content ).appendTo( slide.$slide ); 1955 1956 if ( slide.opts.smallBtn && !slide.$smallBtn ) { 1957 slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content ); 1958 } 1959 1960 this.afterLoad( slide ); 1961 }, 1962 1963 // Display error message 1964 // ===================== 1965 1966 setError : function ( slide ) { 1967 1968 slide.hasError = true; 1969 1970 slide.$slide.removeClass( 'fancybox-slide--' + slide.type ); 1971 1972 this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) ); 1973 1974 }, 1975 1976 1977 // Show loading icon inside the slide 1978 // ================================== 1979 1980 showLoading : function( slide ) { 1981 1982 var self = this; 1983 1984 slide = slide || self.current; 1985 1986 if ( slide && !slide.$spinner ) { 1987 slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide ); 1988 } 1989 1990 }, 1991 1992 // Remove loading icon from the slide 1993 // ================================== 1994 1995 hideLoading : function( slide ) { 1996 1997 var self = this; 1998 1999 slide = slide || self.current; 2000 2001 if ( slide && slide.$spinner ) { 2002 slide.$spinner.remove(); 2003 2004 delete slide.$spinner; 2005 } 2006 2007 }, 2008 2009 2010 // Adjustments after slide content has been loaded 2011 // =============================================== 2012 2013 afterLoad : function( slide ) { 2014 2015 var self = this; 2016 2017 if ( self.isClosing ) { 2018 return; 2019 } 2020 2021 slide.isLoading = false;
2022 slide.isLoaded = true; 2023 2024 self.trigger( 'afterLoad', slide ); 2025 2026 self.hideLoading( slide ); 2027 2028 if ( slide.opts.protect && slide.$content && !slide.hasError ) { 2029 2030 // Disable right click 2031 slide.$content.on( 'contextmenu.fb', function( e ) { 2032 if ( e.button == 2 ) { 2033 e.preventDefault(); 2034 } 2035 2036 return true; 2037 }); 2038 2039 // Add fake element on top of the image 2040 // This makes a bit harder for user to select image 2041 if ( slide.type === 'image' ) { 2042 $( '<div class="fancybox-spaceball"></div>' ).appendTo( slide.$content ); 2043 } 2044 2045 } 2046 2047 self.revealContent( slide ); 2048 2049 }, 2050 2051 2052 // Make content visible 2053 // This method is called right after content has been loaded or 2054 // user navigates gallery and transition should start 2055 // ============================================================ 2056 2057 revealContent : function( slide ) { 2058 2059 var self = this; 2060 var $slide = slide.$slide; 2061 2062 var effect, effectClassName, duration, opacity, end, start = false;
2063 2064 effect = slide.opts[ self.firstRun ? 'animationEffect' : 'transitionEffect' ]; 2065 duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ]; 2066 2067 duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 ); 2068 2069 if ( slide.isMoved || slide.pos !== self.currPos || !duration ) { 2070 effect = false; 2071 } 2072 2073 // Check if can zoom 2074 if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) { 2075 effect = 'fade'; 2076 } 2077 2078 2079 // Zoom animation 2080 // ============== 2081 2082 if ( effect === 'zoom' ) { 2083 end = self.getFitPos( slide ); 2084 2085 end.scaleX = Math.round( (end.width / start.width) * 100 ) / 100; 2086 end.scaleY = Math.round( (end.height / start.height) * 100 ) / 100; 2087 2088 delete end.width; 2089 delete end.height; 2090 2091 // Check if we need to animate opacity 2092 opacity = slide.opts.zoomOpacity; 2093 2094 if ( opacity == 'auto' ) { 2095 opacity = Math.abs( slide.width / slide.height - start.width / start.height ) > 0.1; 2096 } 2097 2098 if ( opacity ) { 2099 start.opacity = 0.1; 2100 end.opacity = 1; 2101 } 2102 2103 // Draw image at start position 2104 $.fancybox.setTranslate( slide.$content.removeClass( 'fancybox-is-hidden' ), start ); 2105 2106 forceRedraw( slide.$content ); 2107 2108 // Start animation 2109 $.fancybox.animate( slide.$content, end, duration, function() { 2110 self.complete(); 2111 }); 2112 2113 return; 2114 } 2115 2116 2117 self.updateSlide( slide ); 2118 2119 2120 // Simply show content 2121 // =================== 2122 2123 if ( !effect ) { 2124 forceRedraw( $slide ); 2125 2126 slide.$content.removeClass( 'fancybox-is-hidden' ); 2127 2128 if ( slide.pos === self.currPos ) { 2129 self.complete(); 2130 } 2131 2132 return; 2133 } 2134 2135 $.fancybox.stop( $slide ); 2136 2137 effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos > self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect; 2138 2139 $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName ); 2140 2141 slide.$content.removeClass( 'fancybox-is-hidden' ); 2142 2143 //Force reflow for CSS3 transitions 2144 forceRedraw( $slide ); 2145 2146 $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) { 2147 $slide.removeClass( effectClassName ).removeAttr( 'style' ); 2148 2149 if ( slide.pos === self.currPos ) { 2150 self.complete(); 2151 } 2152 2153 }, true); 2154 2155 }, 2156 2157 2158 // Check if we can and have to zoom from thumbnail 2159 //================================================ 2160 2161 getThumbPos : function( slide ) { 2162 2163 var self = this; 2164 var rez = false; 2165 2166 // Check if element is inside the viewport by at least 1 pixel 2167 var isElementVisible = function( $el ) { 2168 var element = $el[0]; 2169 2170 var elementRect = element.getBoundingClientRect(); 2171 var parentRects = []; 2172 2173 var visibleInAllParents; 2174 2175 while ( element.parentElement !== null ) { 2176 if ( $(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto' ) { 2177 parentRects.push(element.parentElement.getBoundingClientRect()); 2178 } 2179 2180 element = element.parentElement; 2181 } 2182 2183 visibleInAllParents = parentRects.every(function(parentRect){ 2184 var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left); 2185 var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top); 2186 2187 return visiblePixelX > 0 && visiblePixelY > 0; 2188 }); 2189 2190 return visibleInAllParents && 2191 elementRect.bottom > 0 && elementRect.right > 0 && 2192 elementRect.left < $(window).width() && elementRect.top < $(window).height(); 2193 }; 2194 2195 var $thumb = slide.opts.$thumb; 2196 var thumbPos = $thumb ? $thumb.offset() : 0; 2197 var slidePos; 2198 2199 if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) { 2200 slidePos = self.$refs.stage.offset(); 2201 2202 rez = { 2203 top : thumbPos.top - slidePos.top + parseFloat( $thumb.css( "border-top-width" ) || 0 ), 2204 left : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ), 2205 width : $thumb.width(), 2206 height : $thumb.height(), 2207 scaleX : 1, 2208 scaleY : 1 2209 }; 2210 } 2211 2212 return rez; 2213 }, 2214 2215 2216 // Final adjustments after current gallery item is moved to position 2217 // and it`s content is loaded 2218 // ================================================================== 2219 2220 complete : function() { 2221 2222 var self = this; 2223 2224 var current = self.current; 2225 var slides = {}; 2226 2227 if ( current.isMoved || !current.isLoaded || current.isComplete ) { 2228 return; 2229 } 2230 2231 current.isComplete = true; 2232 2233 current.$slide.siblings().trigger( 'onReset' ); 2234 2235 // Trigger any CSS3 transiton inside the slide 2236 forceRedraw( current.$slide ); 2237 2238 current.$slide.addClass( 'fancybox-slide--complete' ); 2239 2240 // Remove unnecessary slides 2241 $.each( self.slides, function( key, slide ) { 2242 if ( slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1 ) { 2243 slides[ slide.pos ] = slide; 2244 2245 } else if ( slide ) { 2246 2247 $.fancybox.stop( slide.$slide ); 2248 2249 slide.$slide.unbind().remove(); 2250 } 2251 }); 2252 2253 self.slides = slides; 2254
2255 self.updateCursor(); 2256 2257 self.trigger( 'afterShow' ); 2258 2259 // Try to focus on the first focusable element 2260 if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) { 2261 self.focus(); 2262 } 2263 2264 }, 2265 2266 2267 // Preload next and previous slides 2268 // ================================ 2269 2270 preload : function() { 2271 var self = this; 2272 var next, prev; 2273 2274 if ( self.group.length < 2 ) { 2275 return; 2276 } 2277 2278 next = self.slides[ self.currPos + 1 ]; 2279 prev = self.slides[ self.currPos - 1 ]; 2280 2281 if ( next && next.type === 'image' ) { 2282 self.loadSlide( next ); 2283 } 2284 2285 if ( prev && prev.type === 'image' ) { 2286 self.loadSlide( prev ); 2287 } 2288 2289 }, 2290 2291 2292 // Try to find and focus on the first focusable element 2293 // ==================================================== 2294 2295 focus : function() { 2296 var current = this.current; 2297 var $el; 2298 2299 if ( this.isClosing ) { 2300 return; 2301 } 2302 2303 // Skip for images and iframes 2304 $el = current && current.isComplete ? current.$slide.find('button,:input,[tabindex],a').filter(':not([disabled]):visible:first') : null; 2305 $el = $el && $el.length ? $el : this.$refs.container; 2306 2307 $el.focus(); 2308 }, 2309 2310 2311 // Activates current instance - brings container to the front and enables keyboard, 2312 // notifies other instances about deactivating 2313 // ================================================================================= 2314 2315 activate : function () { 2316 var self = this; 2317 2318 // Deactivate all instances 2319 $( '.fancybox-container' ).each(function () { 2320 var instance = $(this).data( 'FancyBox' ); 2321 2322 // Skip self and closing instances 2323 if (instance && instance.uid !== self.uid && !instance.isClosing) { 2324 instance.trigger( 'onDeactivate' ); 2325 } 2326 2327 }); 2328 2329 if ( self.current ) { 2330 if ( self.$refs.container.index() > 0 ) { 2331 self.$refs.container.prependTo( document.body ); 2332 } 2333 2334 self.updateControls(); 2335 } 2336 2337 self.trigger( 'onActivate' ); 2338 2339 self.addEvents(); 2340 2341 }, 2342 2343 2344 // Start closing procedure 2345 // This will start "zoom-out" animation if needed and clean everything up afterwards 2346 // ================================================================================= 2347 2348 close : function( e, d ) { 2349 2350 var self = this; 2351 var current = self.current; 2352 2353 var effect, duration; 2354 var $what, opacity, start, end; 2355 2356 var done = function() { 2357 self.cleanUp( e ); 2358 }; 2359 2360 if ( self.isClosing ) { 2361 return false; 2362 } 2363 2364 self.isClosing = true; 2365 2366 // If beforeClose callback prevents closing, make sure content is centered 2367 if ( self.trigger( 'beforeClose', e ) === false ) { 2368 self.isClosing = false; 2369 2370 requestAFrame(function() { 2371 self.update(); 2372 }); 2373 2374 return false; 2375 } 2376 2377 // Remove all events 2378 // If there are multiple instances, they will be set again by "activate" method 2379 self.removeEvents(); 2380 2381 if ( current.timouts ) { 2382 clearTimeout( current.timouts ); 2383 } 2384 2385 $what = current.$content; 2386 effect = current.opts.animationEffect; 2387 duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 ); 2388 2389 // Remove other slides 2390 current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' ); 2391 2392 current.$slide.siblings().trigger( 'onReset' ).remove(); 2393 2394 // Trigger animations 2395 if ( duration ) { 2396 self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' ); 2397 } 2398 2399 // Clean up 2400 self.hideLoading( current ); 2401 2402 self.hideControls(); 2403
2404 self.updateCursor(); 2405 2406 // Check if possible to zoom-out 2407 if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) { 2408 effect = 'fade'; 2409 } 2410 2411 if ( effect === 'zoom' ) { 2412 $.fancybox.stop( $what ); 2413 2414 start = $.fancybox.getTranslate( $what ); 2415 2416 start.width = start.width * start.scaleX; 2417 start.height = start.height * start.scaleY; 2418 2419 // Check if we need to animate opacity 2420 opacity = current.opts.zoomOpacity; 2421 2422 if ( opacity == 'auto' ) { 2423 opacity = Math.abs( current.width / current.height - end.width / end.height ) > 0.1; 2424 } 2425 2426 if ( opacity ) { 2427 end.opacity = 0; 2428 } 2429 2430 start.scaleX = start.width / end.width; 2431 start.scaleY = start.height / end.height; 2432 2433 start.width = end.width; 2434 start.height = end.height; 2435 2436 $.fancybox.setTranslate( current.$content, start ); 2437 2438 $.fancybox.animate( current.$content, end, duration, done ); 2439 2440 return true; 2441 } 2442 2443 if ( effect && duration ) { 2444 2445 // If skip animation 2446 if ( e === true ) { 2447 setTimeout( done, duration ); 2448 2449 } else { 2450 $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done ); 2451 } 2452 2453 } else { 2454 done(); 2455 } 2456 2457 return true; 2458 }, 2459 2460 2461 // Final adjustments after removing the instance 2462 // ============================================= 2463 2464 cleanUp : function( e ) { 2465 var self = this, 2466 instance; 2467 2468 self.current.$slide.trigger( 'onReset' ); 2469 2470 self.$refs.container.empty().remove(); 2471 2472 self.trigger( 'afterClose', e ); 2473 2474 // Place back focus 2475 if ( self.$lastFocus && !!self.current.opts.backFocus ) { 2476 self.$lastFocus.focus(); 2477 } 2478 2479 self.current = null; 2480 2481 // Check if there are other instances 2482 instance = $.fancybox.getInstance(); 2483 2484 if ( instance ) { 2485 instance.activate(); 2486 2487 } else { 2488 2489 $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); 2490 2491 $( 'html' ).removeClass( 'fancybox-enabled' ); 2492 2493 $( '#fancybox-style-noscroll' ).remove(); 2494 } 2495 2496 }, 2497 2498 2499 // Call callback and trigger an event 2500 // ================================== 2501 2502 trigger : function( name, slide ) { 2503 var args = Array.prototype.slice.call(arguments, 1), 2504 self = this, 2505 obj = slide && slide.opts ? slide : self.current, 2506 rez; 2507 2508 if ( obj ) { 2509 args.unshift( obj ); 2510 2511 } else { 2512 obj = self; 2513 } 2514 2515 args.unshift( self ); 2516 2517 if ( $.isFunction( obj.opts[ name ] ) ) { 2518 rez = obj.opts[ name ].apply( obj, args ); 2519 } 2520 2521 if ( rez === false ) { 2522 return rez; 2523 } 2524 2525 if ( name === 'afterClose' ) { 2526 $D.trigger( name + '.fb', args ); 2527 2528 } else { 2529 self.$refs.container.trigger( name + '.fb', args ); 2530 } 2531 2532 }, 2533 2534 2535 // Update infobar values, navigation button states and reveal caption 2536 // ================================================================== 2537 2538 updateControls : function ( force ) { 2539 2540 var self = this; 2541 2542 var current = self.current; 2543 var index = current.index; 2544 var opts = current.opts; 2545 var caption = opts.caption; 2546 var $caption = self.$refs.caption; 2547 2548 // Recalculate content dimensions 2549 current.$slide.trigger( 'refresh' ); 2550 2551 self.$caption = caption && caption.length ? $caption.html( caption ) : null; 2552
2553 if ( !self.isHiddenControls ) { 2554 self.showControls(); 2555 } 2556 2557 // Update info and navigation elements 2558 $('[data-fancybox-count]').html( self.group.length ); 2559 $('[data-fancybox-index]').html( index + 1 ); 2560 2561 $('[data-fancybox-prev]').prop('disabled', ( !opts.loop && index <= 0 ) ); 2562 $('[data-fancybox-next]').prop('disabled', ( !opts.loop && index >= self.group.length - 1 ) ); 2563 2564 }, 2565 2566 // Hide toolbar and caption 2567 // ======================== 2568 2569 hideControls : function () { 2570 2571 this.isHiddenControls = true; 2572 2573 this.$refs.container.removeClass('fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav'); 2574 2575 }, 2576 2577 showControls : function() { 2578 2579 var self = this; 2580 var opts = self.current ? self.current.opts : self.opts; 2581 var $container = self.$refs.container; 2582 2583 self.isHiddenControls = false; 2584 self.idleSecondsCounter = 0; 2585 2586 $container 2587 .toggleClass('fancybox-show-toolbar', !!( opts.toolbar && opts.buttons ) ) 2588 .toggleClass('fancybox-show-infobar', !!( opts.infobar && self.group.length > 1 ) ) 2589 .toggleClass('fancybox-show-nav', !!( opts.arrows && self.group.length > 1 ) ) 2590 .toggleClass('fancybox-is-modal', !!opts.modal ); 2591 2592 if ( self.$caption ) { 2593 $container.addClass( 'fancybox-show-caption '); 2594 2595 } else { 2596 $container.removeClass( 'fancybox-show-caption' ); 2597 } 2598 2599 }, 2600 2601 2602 // Toggle toolbar and caption 2603 // ========================== 2604 2605 toggleControls : function() { 2606 2607 if ( this.isHiddenControls ) { 2608 this.showControls(); 2609 2610 } else { 2611 this.hideControls(); 2612 } 2613 2614 }, 2615 2616 2617 }); 2618 2619 2620 $.fancybox = { 2621 2622 version : "{fancybox-version}", 2623 defaults : defaults, 2624 2625 2626 // Get current instance and execute a command. 2627 // 2628 // Examples of usage: 2629 // 2630 // $instance = $.fancybox.getInstance(); 2631 // $.fancybox.getInstance().jumpTo( 1 ); 2632 // $.fancybox.getInstance( 'jumpTo', 1 ); 2633 // $.fancybox.getInstance( function() { 2634 // console.info( this.currIndex ); 2635 // }); 2636 // ====================================================== 2637 2638 getInstance : function ( command ) { 2639 var instance = $('.fancybox-container:not(".fancybox-is-closing"):first').data( 'FancyBox' ); 2640 var args = Array.prototype.slice.call(arguments, 1); 2641 2642 if ( instance instanceof FancyBox ) { 2643 2644 if ( $.type( command ) === 'string' ) { 2645 instance[ command ].apply( instance, args ); 2646 2647 } else if ( $.type( command ) === 'function' ) { 2648 command.apply( instance, args ); 2649 2650 } 2651 2652 return instance; 2653 } 2654 2655 return false; 2656 2657 }, 2658 2659 2660 // Create new instance 2661 // =================== 2662 2663 open : function ( items, opts, index ) { 2664 return new FancyBox( items, opts, index ); 2665 }, 2666 2667 2668 // Close current or all instances 2669 // ============================== 2670 2671 close : function ( all ) { 2672 var instance = this.getInstance(); 2673 2674 if ( instance ) { 2675 instance.close(); 2676 2677 // Try to find and close next instance 2678 2679 if ( all === true ) { 2680 this.close(); 2681 } 2682 } 2683 2684 }, 2685 2686 // Close instances and unbind all events 2687 // ============================== 2688 2689 destroy : function() { 2690 2691 this.close( true ); 2692 2693 $D.off( 'click.fb-start' ); 2694 2695 }, 2696 2697 2698 // Try to detect mobile devices 2699 // ============================ 2700 2701 isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent), 2702 2703 2704 // Detect if 'translate3d' support is available 2705 // ============================================ 2706 2707 use3d : (function() { 2708 var div = document.createElement('div'); 2709 2710 return window.getComputedStyle && window.getComputedStyle( div ).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11); 2711 }()), 2712 2713 2714 // Helper function to get current visual state of an element 2715 // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] 2716 // ===================================================================== 2717 2718 getTranslate : function( $el ) { 2719 var matrix; 2720 2721 if ( !$el || !$el.length ) { 2722 return false;
2723 } 2724 2725 matrix = $el.eq( 0 ).css('transform'); 2726 2727 if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) { 2728 matrix = matrix.split('(')[1]; 2729 matrix = matrix.split(')')[0]; 2730 matrix = matrix.split(','); 2731 } else { 2732 matrix = []; 2733 } 2734 2735 if ( matrix.length ) { 2736 2737 // If IE 2738 if ( matrix.length > 10 ) { 2739 matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ]; 2740 2741 } else { 2742 matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]]; 2743 } 2744 2745 matrix = matrix.map(parseFloat); 2746 2747 } else { 2748 matrix = [ 0, 0, 1, 1 ]; 2749 2750 var transRegex = /\.*translate\((.*)px,(.*)px\)/i; 2751 var transRez = transRegex.exec( $el.eq( 0 ).attr('style') ); 2752 2753 if ( transRez ) { 2754 matrix[ 0 ] = parseFloat( transRez[2] ); 2755 matrix[ 1 ] = parseFloat( transRez[1] ); 2756 } 2757 } 2758 2759 return { 2760 top : matrix[ 0 ], 2761 left : matrix[ 1 ], 2762 scaleX : matrix[ 2 ], 2763 scaleY : matrix[ 3 ], 2764 opacity : parseFloat( $el.css('opacity') ), 2765 width : $el.width(), 2766 height : $el.height() 2767 }; 2768 2769 }, 2770 2771 2772 // Shortcut for setting "translate3d" properties for element 2773 // Can set be used to set opacity, too 2774 // ======================================================== 2775 2776 setTranslate : function( $el, props ) { 2777 var str = ''; 2778 var css = {}; 2779 2780 if ( !$el || !props ) { 2781 return; 2782 } 2783 2784 if ( props.left !== undefined || props.top !== undefined ) { 2785 str = ( props.left === undefined ? $el.position().left : props.left ) + 'px, ' + ( props.top === undefined ? $el.position().top : props.top ) + 'px'; 2786 2787 if ( this.use3d ) { 2788 str = 'translate3d(' + str + ', 0px)'; 2789 2790 } else { 2791 str = 'translate(' + str + ')'; 2792 } 2793 } 2794 2795 if ( props.scaleX !== undefined && props.scaleY !== undefined ) { 2796 str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')'; 2797 } 2798 2799 if ( str.length ) { 2800 css.transform = str; 2801 } 2802 2803 if ( props.opacity !== undefined ) { 2804 css.opacity = props.opacity; 2805 } 2806 2807 if ( props.width !== undefined ) { 2808 css.width = props.width; 2809 } 2810 2811 if ( props.height !== undefined ) { 2812 css.height = props.height; 2813 } 2814 2815 return $el.css( css ); 2816 }, 2817 2818 2819 // Simple CSS transition handler 2820 // ============================= 2821 2822 animate : function ( $el, to, duration, callback, leaveAnimationName ) { 2823 var event = transitionEnd || 'transitionend'; 2824 2825 if ( $.isFunction( duration ) ) { 2826 callback = duration; 2827 duration = null; 2828 } 2829 2830 if ( !$.isPlainObject( to ) ) { 2831 $el.removeAttr('style'); 2832 } 2833 2834 $el.on( event, function(e) { 2835 2836 // Skip events from child elements and z-index change 2837 if ( e && e.originalEvent && ( !$el.is( e.originalEvent.target ) || e.originalEvent.propertyName == 'z-index' ) ) { 2838 return; 2839 } 2840 2841 $el.off( event ); 2842 2843 if ( $.isPlainObject( to ) ) { 2844 2845 if ( to.scaleX !== undefined && to.scaleY !== undefined ) { 2846 $el.css( 'transition-duration', '0ms' ); 2847 2848 to.width = $el.width() * to.scaleX; 2849 to.height = $el.height() * to.scaleY; 2850 2851 to.scaleX = 1; 2852 to.scaleY = 1; 2853 2854 $.fancybox.setTranslate( $el, to ); 2855 } 2856 2857 } else if ( leaveAnimationName !== true ) { 2858 $el.removeClass( to ); 2859 } 2860 2861 if ( $.isFunction( callback ) ) { 2862 callback( e ); 2863 } 2864 2865 }); 2866 2867 if ( $.isNumeric( duration ) ) { 2868 $el.css( 'transition-duration', duration + 'ms' ); 2869 } 2870 2871 if ( $.isPlainObject( to ) ) { 2872 $.fancybox.setTranslate( $el, to ); 2873 2874 } else { 2875 $el.addClass( to ); 2876 } 2877 2878 $el.data("timer", setTimeout(function() { 2879 $el.trigger( 'transitionend' ); 2880 }, duration + 16)); 2881 2882 }, 2883 2884 stop : function( $el ) { 2885 clearTimeout( $el.data("timer") ); 2886 2887 $el.off( transitionEnd ); 2888 } 2889 2890 }; 2891 2892 2893 // Default click handler for "fancyboxed" links 2894 // ============================================ 2895 2896 function _run( e ) { 2897 var target = e.currentTarget, 2898 opts = e.data ? e.data.options : {}, 2899 items = opts.selector ? $( opts.selector ) : ( e.data ? e.data.items : [] ), 2900 value = $(target).attr( 'data-fancybox' ) || '', 2901 index = 0, 2902 active = $.fancybox.getInstance(); 2903 2904 e.preventDefault(); 2905 e.stopPropagation(); 2906 2907 // Avoid opening multiple times 2908 if ( active && active.current.opts.$orig.is( target ) ) { 2909 return; 2910 } 2911 2912 // Get all related items and find index for clicked one 2913 if ( value ) { 2914 items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' ); 2915 index = items.index( target ); 2916 2917 // Sometimes current item can not be found 2918 // (for example, when slider clones items) 2919 if ( index < 0 ) { 2920 index = 0; 2921 } 2922 2923 } else { 2924 items = [ target ]; 2925 } 2926 2927 $.fancybox.open( items, opts, index ); 2928 } 2929 2930 2931 // Create a jQuery plugin 2932 // ====================== 2933 2934 $.fn.fancybox = function (options) { 2935 var selector; 2936 2937 options = options || {}; 2938 selector = options.selector || false; 2939 2940 if ( selector ) { 2941 2942 $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, { 2943 options : options 2944 }, _run ); 2945 2946 } else { 2947 2948 this.off( 'click.fb-start' ).on( 'click.fb-start', { 2949 items : this, 2950 options : options 2951 }, _run); 2952 2953 } 2954 2955 return this; 2956 }; 2957 2958 2959 // Self initializing plugin 2960 // ======================== 2961 2962 $D.on( 'click.fb-start', '[data-fancybox]', _run ); 2963 2964}( window, document, 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.