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