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