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