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