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