vendor: 96,599 bytes, lines 1-3425
1(function (window, document, $, undefined) { 2 "use strict"; 3 4 window.console = window.console || { 5 info: function (stuff) {} 6 }; 7 8 // If there's no jQuery, fancyBox can't work 9 // ========================================= 10 11 if (!$) { 12 return; 13 } 14 15 // Check if fancyBox is already initialized 16 // ======================================== 17 18 if ($.fn.fancybox) { 19 console.info("fancyBox already initialized"); 20 21 return; 22 } 23 24 // Private default settings 25 // ======================== 26 27 var defaults = { 28 // Close existing modals 29 // Set this to false if you do not need to stack multiple instances 30 closeExisting: false, 31 32 // Enable infinite gallery navigation 33 loop: false, 34 35 // Horizontal space between slides 36 gutter: 50, 37 38 // Enable keyboard navigation 39 keyboard: true, 40 41 // Should allow caption to overlap the content 42 preventCaptionOverlap: true, 43 44 // Should display navigation arrows at the screen edges 45 arrows: true, 46 47 // Should display counter at the top left corner 48 infobar: true, 49 50 // Should display close button (using `btnTpl.smallBtn` template) over the content 51 // Can be true, false, "auto" 52 // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items 53 smallBtn: "auto", 54 55 // Should display toolbar (buttons at the top) 56 // Can be true, false, "auto" 57 // If "auto" - will be automatically hidden if "smallBtn" is enabled 58 toolbar: "auto", 59 60 // What buttons should appear in the top right corner. 61 // Buttons will be created using templates from `btnTpl` option 62 // and they will be placed into toolbar (class="fancybox-toolbar"` element) 63 buttons: [ 64 "zoom", 65 //"share", 66 "slideShow", 67 //"fullScreen", 68 //"download", 69 "thumbs", 70 "close" 71 ], 72 73 // Detect "idle" time in seconds 74 idleTime: 3, 75 76 // Disable right-click and use simple image protection for images 77 protect: false, 78 79 // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc 80 modal: false, 81 82 image: { 83 // Wait for images to load before displaying 84 // true - wait for image to load and then display; 85 // false - display thumbnail and load the full-sized image over top, 86 // requires predefined image dimensions (`data-width` and `data-height` attributes) 87 preload: false 88 }, 89 90 ajax: { 91 // Object containing settings for ajax request 92 settings: { 93 // This helps to indicate that request comes from the modal 94 // Feel free to change naming 95 data: { 96 fancybox: true 97 } 98 } 99 }, 100 101 iframe: { 102 // Iframe template 103 tpl: '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" allowfullscreen="allowfullscreen" allow="autoplay; fullscreen" src=""></iframe>', 104 105 // Preload iframe before displaying it 106 // This allows to calculate iframe content width and height 107 // (note: Due to "Same Origin Policy", you can't get cross domain data). 108 preload: true, 109 110 // Custom CSS styling for iframe wrapping element 111 // You can use this to set custom iframe dimensions 112 css: {}, 113 114 // Iframe tag attributes 115 attr: { 116 scrolling: "auto" 117 } 118 }, 119 120 // For HTML5 video only 121 video: { 122 tpl: '<video class="fancybox-video" controls controlsList="nodownload" poster="{{poster}}">' + 123 '<source src="{{src}}" type="{{format}}" />' + 124 'Sorry, your browser doesn\'t support embedded videos, <a href="{{src}}">download</a> and watch with your favorite video player!' + 125 "</video>", 126 format: "", // custom video format 127 autoStart: true 128 }, 129 130 // Default content type if cannot be detected automatically 131 defaultType: "image", 132 133 // Open/close animation type 134 // Possible values: 135 // false - disable 136 // "zoom" - zoom images from/to thumbnail 137 // "fade" 138 // "zoom-in-out" 139 // 140 animationEffect: "zoom", 141 142 // Duration in ms for open/close animation 143 animationDuration: 366, 144 145 // Should image change opacity while zooming 146 // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios 147 zoomOpacity: "auto", 148 149 // Transition effect between slides 150 // 151 // Possible values: 152 // false - disable 153 // "fade' 154 // "slide' 155 // "circular' 156 // "tube' 157 // "zoom-in-out' 158 // "rotate' 159 // 160 transitionEffect: "fade", 161 162 // Duration in ms for transition animation 163 transitionDuration: 366, 164 165 // Custom CSS class for slide element 166 slideClass: "", 167 168 // Custom CSS class for layout 169 baseClass: "", 170 171 // Base template for layout 172 baseTpl: '<div class="fancybox-container" role="dialog" tabindex="-1">' + 173 '<div class="fancybox-bg"></div>' + 174 '<div class="fancybox-inner">' + 175 '<div class="fancybox-infobar"><span data-fancybox-index></span> / <span data-fancybox-count></span></div>' + 176 '<div class="fancybox-toolbar">{{buttons}}</div>' + 177 '<div class="fancybox-navigation">{{arrows}}</div>' + 178 '<div class="fancybox-stage"></div>' + 179 '<div class="fancybox-caption"><div class="fancybox-caption__body"></div></div>' + 180 "</div>" + 181 "</div>", 182 183 // Loading indicator template 184 spinnerTpl: '<div class="fancybox-loading"></div>', 185 186 // Error message template 187 errorTpl: '<div class="fancybox-error"><p>{{ERROR}}</p></div>', 188 189 btnTpl: { 190 download: '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}" href="javascript:;">' + 191 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><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>' + 192 "</a>", 193 194 zoom: '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' + 195 '<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>' + 196 "</button>", 197 198 close: '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' + 199 '<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>' + 200 "</button>", 201 202 // Arrows 203 arrowLeft: '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' + 204 '<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>' + 205 "</button>", 206 207 arrowRight: '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' + 208 '<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>' + 209 "</button>", 210 211 // This small close button will be appended to your html/inline/ajax content by default, 212 // if "smallBtn" option is not set to false 213 smallBtn: '<button type="button" data-fancybox-close class="fancybox-button fancybox-close-small" title="{{CLOSE}}">' + 214 '<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>' + 215 "</button>" 216 }, 217 218 // Container is injected into this element 219 parentEl: "body", 220 221 // Hide browser vertical scrollbars; use at your own risk 222 hideScrollbar: true, 223 224 // Focus handling 225 // ============== 226 227 // Try to focus on the first focusable element after opening 228 autoFocus: true, 229 230 // Put focus back to active element after closing 231 backFocus: true, 232 233 // Do not let user to focus on element outside modal content 234 trapFocus: true, 235 236 // Module specific options 237 // ======================= 238 239 fullScreen: { 240 autoStart: false 241 }, 242 243 // Set `touch: false` to disable panning/swiping 244 touch: { 245 vertical: true, // Allow to drag content vertically 246 momentum: true // Continue movement after releasing mouse/touch when panning 247 }, 248 249 // Hash value when initializing manually, 250 // set `false` to disable hash change 251 hash: null, 252 253 // Customize or add new media types 254 // Example: 255 /* 256 media : { 257 youtube : { 258 params : { 259 autoplay : 0 260 } 261 } 262 } 263 */ 264 media: {}, 265 266 slideShow: { 267 autoStart: false, 268 speed: 3000 269 }, 270 271 thumbs: { 272 autoStart: false, // Display thumbnails on opening 273 hideOnClose: true, // Hide thumbnail grid when closing animation starts 274 parentEl: ".fancybox-container", // Container is injected into this element 275 axis: "y" // Vertical (y) or horizontal (x) scrolling 276 }, 277 278 // Use mousewheel to navigate gallery 279 // If 'auto' - enabled for images only 280 wheel: "auto", 281 282 // Callbacks 283 //========== 284 285 // See Documentation/API/Events for more information 286 // Example: 287 /* 288 afterShow: function( instance, current ) { 289 console.info( 'Clicked element:' ); 290 console.info( current.opts.$orig ); 291 } 292 */ 293 294 onInit: $.noop, // When instance has been initialized 295 296 beforeLoad: $.noop, // Before the content of a slide is being loaded 297 afterLoad: $.noop, // When the content of a slide is done loading 298 299 beforeShow: $.noop, // Before open animation starts 300 afterShow: $.noop, // When content is done loading and animating 301 302 beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close. 303 afterClose: $.noop, // After instance has been closed 304 305 onActivate: $.noop, // When instance is brought to front 306 onDeactivate: $.noop, // When other instance has been activated 307 308 // Interaction 309 // =========== 310 311 // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, 312 // each option can be string or method that returns value. 313 // 314 // Possible values: 315 // "close" - close instance 316 // "next" - move to next gallery item 317 // "nextOrClose" - move to next gallery item or close if gallery has only one item 318 // "toggleControls" - show/hide controls 319 // "zoom" - zoom image (if loaded) 320 // false - do nothing 321 322 // Clicked on the content 323 clickContent: function (current, event) { 324 return current.type === "image" ? "zoom" : false; 325 }, 326 327 // Clicked on the slide 328 clickSlide: "close", 329 330 // Clicked on the background (backdrop) element; 331 // if you have not changed the layout, then most likely you need to use `clickSlide` option 332 clickOutside: "close", 333 334 // Same as previous two, but for double click 335 dblclickContent: false, 336 dblclickSlide: false, 337 dblclickOutside: false, 338 339 // Custom options when mobile device is detected 340 // ============================================= 341 342 mobile: { 343 preventCaptionOverlap: false, 344 idleTime: false, 345 clickContent: function (current, event) { 346 return current.type === "image" ? "toggleControls" : false; 347 }, 348 clickSlide: function (current, event) { 349 return current.type === "image" ? "toggleControls" : "close"; 350 }, 351 dblclickContent: function (current, event) { 352 return current.type === "image" ? "zoom" : false; 353 }, 354 dblclickSlide: function (current, event) { 355 return current.type === "image" ? "zoom" : false; 356 } 357 }, 358 359 // Internationalization 360 // ==================== 361 362 lang: "en", 363 i18n: { 364 en: { 365 CLOSE: "Close", 366 NEXT: "Next", 367 PREV: "Previous", 368 ERROR: "The requested content cannot be loaded. <br/> Please try again later.", 369 PLAY_START: "Start slideshow", 370 PLAY_STOP: "Pause slideshow", 371 FULL_SCREEN: "Full screen", 372 THUMBS: "Thumbnails", 373 DOWNLOAD: "Download", 374 SHARE: "Share", 375 ZOOM: "Zoom" 376 }, 377 de: { 378 CLOSE: "Schließen", 379 NEXT: "Weiter", 380 PREV: "Zurück", 381 ERROR: "Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es später nochmal.", 382 PLAY_START: "Diaschau starten", 383 PLAY_STOP: "Diaschau beenden", 384 FULL_SCREEN: "Vollbild", 385 THUMBS: "Vorschaubilder", 386 DOWNLOAD: "Herunterladen", 387 SHARE: "Teilen", 388 ZOOM: "Vergrößern" 389 } 390 } 391 }; 392 393 // Few useful variables and methods 394 // ================================ 395 396 var $W = $(window); 397 var $D = $(document); 398 399 var called = 0; 400 401 // Check if an object is a jQuery object and not a native JavaScript object 402 // ======================================================================== 403 var isQuery = function (obj) { 404 return obj && obj.hasOwnProperty && obj instanceof $; 405 }; 406 407 // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" 408 // =============================================================================== 409 var requestAFrame = (function () { 410 return ( 411 window.requestAnimationFrame || 412 window.webkitRequestAnimationFrame || 413 window.mozRequestAnimationFrame || 414 window.oRequestAnimationFrame || 415 // if all else fails, use setTimeout 416 function (callback) { 417 return window.setTimeout(callback, 1000 / 60); 418 } 419 ); 420 })(); 421 422 var cancelAFrame = (function () { 423 return ( 424 window.cancelAnimationFrame || 425 window.webkitCancelAnimationFrame || 426 window.mozCancelAnimationFrame || 427 window.oCancelAnimationFrame || 428 function (id) { 429 window.clearTimeout(id); 430 } 431 ); 432 })(); 433 434 // Detect the supported transition-end event property name 435 // ======================================================= 436 var transitionEnd = (function () { 437 var el = document.createElement("fakeelement"), 438 t; 439 440 var transitions = { 441 transition: "transitionend", 442 OTransition: "oTransitionEnd", 443 MozTransition: "transitionend", 444 WebkitTransition: "webkitTransitionEnd" 445 }; 446 447 for (t in transitions) { 448 if (el.style[t] !== undefined) { 449 return transitions[t]; 450 } 451 } 452 453 return "transitionend"; 454 })(); 455 456 // Force redraw on an element. 457 // This helps in cases where the browser doesn't redraw an updated element properly 458 // ================================================================================ 459 var forceRedraw = function ($el) { 460 return $el && $el.length && $el[0].offsetHeight; 461 }; 462 463 // Exclude array (`buttons`) options from deep merging 464 // =================================================== 465 var mergeOpts = function (opts1, opts2) { 466 var rez = $.extend(true, {}, opts1, opts2); 467 468 $.each(opts2, function (key, value) { 469 if ($.isArray(value)) { 470 rez[key] = value; 471 } 472 }); 473 474 return rez; 475 }; 476 477 // How much of an element is visible in viewport 478 // ============================================= 479 480 var inViewport = function (elem) { 481 var elemCenter, rez; 482 483 if (!elem || elem.ownerDocument !== document) { 484 return false; 485 } 486 487 $(".fancybox-container").css("pointer-events", "none"); 488 489 elemCenter = { 490 x: elem.getBoundingClientRect().left + elem.offsetWidth / 2, 491 y: elem.getBoundingClientRect().top + elem.offsetHeight / 2 492 }; 493 494 rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem; 495 496 $(".fancybox-container").css("pointer-events", ""); 497 498 return rez; 499 }; 500 501 // Class definition 502 // ================ 503 504 var FancyBox = function (content, opts, index) { 505 var self = this; 506 507 self.opts = mergeOpts({ 508 index: index 509 }, $.fancybox.defaults); 510 511 if ($.isPlainObject(opts)) { 512 self.opts = mergeOpts(self.opts, opts); 513 } 514 515 if ($.fancybox.isMobile) { 516 self.opts = mergeOpts(self.opts, self.opts.mobile); 517 } 518 519 self.id = self.opts.id || ++called; 520 521 self.currIndex = parseInt(self.opts.index, 10) || 0; 522 self.prevIndex = null; 523 524 self.prevPos = null; 525 self.currPos = 0; 526 527 self.firstRun = true; 528 529 // All group items 530 self.group = []; 531 532 // Existing slides (for current, next and previous gallery items) 533 self.slides = {}; 534 535 // Create group elements 536 self.addContent(content); 537 538 if (!self.group.length) { 539 return; 540 } 541 542 self.init(); 543 }; 544 545 $.extend(FancyBox.prototype, { 546 // Create DOM structure 547 // ==================== 548 549 init: function () { 550 var self = this, 551 firstItem = self.group[self.currIndex], 552 firstItemOpts = firstItem.opts, 553 $container, 554 buttonStr; 555 556 if (firstItemOpts.closeExisting) { 557 $.fancybox.close(true); 558 } 559 560 // Hide scrollbars 561 // =============== 562 563 $("body").addClass("fancybox-active"); 564 565 if ( 566 !$.fancybox.getInstance() && 567 firstItemOpts.hideScrollbar !== false && 568 !$.fancybox.isMobile && 569 document.body.scrollHeight > window.innerHeight 570 ) { 571 $("head").append( 572 '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar{margin-right:' + 573 (window.innerWidth - document.documentElement.clientWidth) + 574 "px;}</style>" 575 ); 576 577 $("body").addClass("compensate-for-scrollbar"); 578 } 579 580 // Build html markup and set references 581 // ==================================== 582 583 // Build html code for buttons and insert into main template 584 buttonStr = ""; 585 586 $.each(firstItemOpts.buttons, function (index, value) { 587 buttonStr += firstItemOpts.btnTpl[value] || ""; 588 }); 589 590 // Create markup from base template, it will be initially hidden to 591 // avoid unnecessary work like painting while initializing is not complete 592 $container = $( 593 self.translate( 594 self, 595 firstItemOpts.baseTpl 596 .replace("{{buttons}}", buttonStr) 597 .replace("{{arrows}}", firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight) 598 ) 599 ) 600 .attr("id", "fancybox-container-" + self.id) 601 .addClass(firstItemOpts.baseClass) 602 .data("FancyBox", self) 603 .appendTo(firstItemOpts.parentEl); 604 605 // Create object holding references to jQuery wrapped nodes 606 self.$refs = { 607 container: $container 608 }; 609 610 ["bg", "inner", "infobar", "toolbar", "stage", "caption", "navigation"].forEach(function (item) { 611 self.$refs[item] = $container.find(".fancybox-" + item); 612 }); 613 614 self.trigger("onInit"); 615 616 // Enable events, deactive previous instances 617 self.activate(); 618 619 // Build slides, load and reveal content 620 self.jumpTo(self.currIndex); 621 }, 622 623 // Simple i18n support - replaces object keys found in template 624 // with corresponding values 625 // ============================================================ 626 627 translate: function (obj, str) { 628 var arr = obj.opts.i18n[obj.opts.lang] || obj.opts.i18n.en; 629 630 return str.replace(/\{\{(\w+)\}\}/g, function (match, n) { 631 return arr[n] === undefined ? match : arr[n]; 632 }); 633 }, 634 635 // Populate current group with fresh content 636 // Check if each object has valid type and content 637 // =============================================== 638 639 addContent: function (content) { 640 var self = this, 641 items = $.makeArray(content), 642 thumbs; 643 644 $.each(items, function (i, item) { 645 var obj = {}, 646 opts = {}, 647 $item, 648 type, 649 found, 650 src, 651 srcParts; 652 653 // Step 1 - Make sure we have an object 654 // ==================================== 655 656 if ($.isPlainObject(item)) { 657 // We probably have manual usage here, something like 658 // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) 659 660 obj = item; 661 opts = item.opts || item; 662 } else if ($.type(item) === "object" && $(item).length) { 663 // Here we probably have jQuery collection returned by some selector 664 $item = $(item); 665 666 // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'` 667 opts = $item.data() || {}; 668 opts = $.extend(true, {}, opts, opts.options); 669 670 // Here we store clicked element 671 opts.$orig = $item; 672 673 obj.src = self.opts.src || opts.src || $item.attr("href"); 674 675 // Assume that simple syntax is used, for example: 676 // `$.fancybox.open( $("#test"), {} );` 677 if (!obj.type && !obj.src) { 678 obj.type = "inline"; 679 obj.src = item; 680 } 681 } else { 682 // Assume we have a simple html code, for example: 683 // $.fancybox.open( '<div><h1>Hi!</h1></div>' ); 684 obj = { 685 type: "html", 686 src: item + "" 687 }; 688 } 689 690 // Each gallery object has full collection of options 691 obj.opts = $.extend(true, {}, self.opts, opts); 692 693 // Do not merge buttons array 694 if ($.isArray(opts.buttons)) { 695 obj.opts.buttons = opts.buttons; 696 } 697 698 if ($.fancybox.isMobile && obj.opts.mobile) { 699 obj.opts = mergeOpts(obj.opts, obj.opts.mobile); 700 } 701 702 // Step 2 - Make sure we have content type, if not - try to guess 703 // ============================================================== 704 705 type = obj.type || obj.opts.type; 706 src = obj.src || ""; 707 708 if (!type && src) { 709 if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) { 710 type = "video"; 711 712 if (!obj.opts.video.format) { 713 obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]); 714 } 715 } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) { 716 type = "image"; 717 } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) { 718 type = "iframe"; 719 obj = $.extend(true, obj, { 720 contentType: "pdf", 721 opts: { 722 iframe: { 723 preload: false 724 } 725 } 726 }); 727 } else if (src.charAt(0) === "#") { 728 type = "inline"; 729 } 730 } 731 732 if (type) { 733 obj.type = type; 734 } else { 735 self.trigger("objectNeedsType", obj); 736 } 737 738 if (!obj.contentType) { 739 obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type; 740 } 741 742 // Step 3 - Some adjustments 743 // ========================= 744 745 obj.index = self.group.length; 746 747 if (obj.opts.smallBtn == "auto") { 748 obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1; 749 } 750 751 if (obj.opts.toolbar === "auto") { 752 obj.opts.toolbar = !obj.opts.smallBtn; 753 } 754 755 // Find thumbnail image, check if exists and if is in the viewport 756 obj.$thumb = obj.opts.$thumb || null; 757 758 if (obj.opts.$trigger && obj.index === self.opts.index) { 759 obj.$thumb = obj.opts.$trigger.find("img:first"); 760 761 if (obj.$thumb.length) { 762 obj.opts.$orig = obj.opts.$trigger; 763 } 764 } 765 766 if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) { 767 obj.$thumb = obj.opts.$orig.find("img:first"); 768 } 769 770 if (obj.$thumb && !obj.$thumb.length) { 771 obj.$thumb = null; 772 } 773 774 obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null); 775 776 // "caption" is a "special" option, it can be used to customize caption per gallery item 777 if ($.type(obj.opts.caption) === "function") { 778 obj.opts.caption = obj.opts.caption.apply(item, [self, obj]); 779 } 780 781 if ($.type(self.opts.caption) === "function") { 782 obj.opts.caption = self.opts.caption.apply(item, [self, obj]); 783 } 784 785 // Make sure we have caption as a string or jQuery object 786 if (!(obj.opts.caption instanceof $)) { 787 obj.opts.caption = obj.opts.caption === undefined ? "" : obj.opts.caption + ""; 788 } 789 790 // Check if url contains "filter" used to filter the content 791 // Example: "ajax.html #something" 792 if (obj.type === "ajax") { 793 srcParts = src.split(/\s+/, 2); 794 795 if (srcParts.length > 1) { 796 obj.src = srcParts.shift(); 797 798 obj.opts.filter = srcParts.shift(); 799 } 800 } 801 802 // Hide all buttons and disable interactivity for modal items 803 if (obj.opts.modal) { 804 obj.opts = $.extend(true, obj.opts, { 805 trapFocus: true, 806 // Remove buttons 807 infobar: 0, 808 toolbar: 0, 809 810 smallBtn: 0, 811 812 // Disable keyboard navigation 813 keyboard: 0, 814 815 // Disable some modules 816 slideShow: 0, 817 fullScreen: 0, 818 thumbs: 0, 819 touch: 0, 820 821 // Disable click event handlers 822 clickContent: false, 823 clickSlide: false, 824 clickOutside: false, 825 dblclickContent: false, 826 dblclickSlide: false, 827 dblclickOutside: false 828 }); 829 } 830 831 // Step 4 - Add processed object to group 832 // ====================================== 833 834 self.group.push(obj); 835 }); 836 837 // Update controls if gallery is already opened 838 if (Object.keys(self.slides).length) { 839 self.updateControls(); 840 841 // Update thumbnails, if needed 842 thumbs = self.Thumbs; 843 844 if (thumbs && thumbs.isActive) { 845 thumbs.create(); 846 847 thumbs.focus(); 848 } 849 } 850 }, 851 852 // Attach an event handler functions for: 853 // - navigation buttons 854 // - browser scrolling, resizing; 855 // - focusing 856 // - keyboard 857 // - detecting inactivity 858 // ====================================== 859 860 addEvents: function () { 861 var self = this; 862 863 self.removeEvents(); 864 865 // Make navigation elements clickable 866 // ================================== 867 868 self.$refs.container 869 .on("click.fb-close", "[data-fancybox-close]", function (e) { 870 e.stopPropagation(); 871 e.preventDefault(); 872 873 self.close(e); 874 }) 875 .on("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function (e) { 876 e.stopPropagation(); 877 e.preventDefault(); 878 879 self.previous(); 880 }) 881 .on("touchstart.fb-next click.fb-next", "[data-fancybox-next]", function (e) { 882 e.stopPropagation(); 883 e.preventDefault(); 884 885 self.next(); 886 }) 887 .on("click.fb", "[data-fancybox-zoom]", function (e) { 888 // Click handler for zoom button 889 self[self.isScaledDown() ? "scaleToActual" : "scaleToFit"](); 890 }); 891 892 // Handle page scrolling and browser resizing 893 // ========================================== 894 895 $W.on("orientationchange.fb resize.fb", function (e) { 896 if (e && e.originalEvent && e.originalEvent.type === "resize") { 897 if (self.requestId) { 898 cancelAFrame(self.requestId); 899 } 900 901 self.requestId = requestAFrame(function () { 902 self.update(e); 903 }); 904 } else { 905 if (self.current && self.current.type === "iframe") { 906 self.$refs.stage.hide(); 907 } 908 909 setTimeout( 910 function () { 911 self.$refs.stage.show(); 912 913 self.update(e); 914 }, 915 $.fancybox.isMobile ? 600 : 250 916 ); 917 } 918 }); 919 920 $D.on("keydown.fb", function (e) { 921 var instance = $.fancybox ? $.fancybox.getInstance() : null, 922 current = instance.current, 923 keycode = e.keyCode || e.which; 924 925 // Trap keyboard focus inside of the modal 926 // ======================================= 927 928 if (keycode == 9) { 929 if (current.opts.trapFocus) { 930 self.focus(e); 931 } 932 933 return; 934 } 935 936 // Enable keyboard navigation 937 // ========================== 938 939 if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio,select")) { 940 return; 941 } 942 943 // Backspace and Esc keys 944 if (keycode === 8 || keycode === 27) { 945 e.preventDefault(); 946 947 self.close(e); 948 949 return; 950 } 951 952 // Left arrow and Up arrow 953 if (keycode === 37 || keycode === 38) { 954 e.preventDefault(); 955 956 self.previous(); 957 958 return; 959 } 960 961 // Righ arrow and Down arrow 962 if (keycode === 39 || keycode === 40) { 963 e.preventDefault(); 964 965 self.next(); 966 967 return; 968 } 969 970 self.trigger("afterKeydown", e, keycode); 971 }); 972 973 // Hide controls after some inactivity period 974 if (self.group[self.currIndex].opts.idleTime) { 975 self.idleSecondsCounter = 0; 976 977 $D.on( 978 "mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle", 979 function (e) { 980 self.idleSecondsCounter = 0; 981 982 if (self.isIdle) { 983 self.showControls(); 984 } 985 986 self.isIdle = false; 987 } 988 ); 989 990 self.idleInterval = window.setInterval(function () { 991 self.idleSecondsCounter++; 992 993 if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) { 994 self.isIdle = true; 995 self.idleSecondsCounter = 0; 996 997 self.hideControls(); 998 } 999 }, 1000); 1000 } 1001 }, 1002 1003 // Remove events added by the core 1004 // =============================== 1005 1006 removeEvents: function () { 1007 var self = this; 1008 1009 $W.off("orientationchange.fb resize.fb"); 1010 $D.off("keydown.fb .fb-idle"); 1011 1012 this.$refs.container.off(".fb-close .fb-prev .fb-next"); 1013 1014 if (self.idleInterval) { 1015 window.clearInterval(self.idleInterval); 1016 1017 self.idleInterval = null; 1018 } 1019 }, 1020 1021 // Change to previous gallery item 1022 // =============================== 1023 1024 previous: function (duration) { 1025 return this.jumpTo(this.currPos - 1, duration); 1026 }, 1027 1028 // Change to next gallery item 1029 // =========================== 1030 1031 next: function (duration) { 1032 return this.jumpTo(this.currPos + 1, duration); 1033 }, 1034 1035 // Switch to selected gallery item 1036 // =============================== 1037 1038 jumpTo: function (pos, duration) { 1039 var self = this, 1040 groupLen = self.group.length, 1041 firstRun, 1042 isMoved, 1043 loop, 1044 current, 1045 previous, 1046 slidePos, 1047 stagePos, 1048 prop, 1049 diff; 1050 1051 if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { 1052 return; 1053 } 1054 1055 // Should loop? 1056 pos = parseInt(pos, 10); 1057 loop = self.current ? self.current.opts.loop : self.opts.loop; 1058 1059 if (!loop && (pos < 0 || pos >= groupLen)) { 1060 return false; 1061 } 1062 1063 // Check if opening for the first time; this helps to speed things up 1064 firstRun = self.firstRun = !Object.keys(self.slides).length; 1065 1066 // Create slides 1067 previous = self.current; 1068 1069 self.prevIndex = self.currIndex; 1070 self.prevPos = self.currPos; 1071 1072 current = self.createSlide(pos); 1073 1074 if (groupLen > 1) { 1075 if (loop || current.index < groupLen - 1) { 1076 self.createSlide(pos + 1); 1077 } 1078 1079 if (loop || current.index > 0) { 1080 self.createSlide(pos - 1); 1081 } 1082 } 1083 1084 self.current = current; 1085 self.currIndex = current.index; 1086 self.currPos = current.pos; 1087 1088 self.trigger("beforeShow", firstRun); 1089 1090 self.updateControls(); 1091 1092 // Validate duration length 1093 current.forcedDuration = undefined; 1094 1095 if ($.isNumeric(duration)) { 1096 current.forcedDuration = duration; 1097 } else { 1098 duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"]; 1099 } 1100 1101 duration = parseInt(duration, 10); 1102 1103 // Check if user has swiped the slides or if still animating 1104 isMoved = self.isMoved(current); 1105 1106 // Make sure current slide is visible 1107 current.$slide.addClass("fancybox-slide--current"); 1108 1109 // Fresh start - reveal container, current slide and start loading content 1110 if (firstRun) { 1111 if (current.opts.animationEffect && duration) { 1112 self.$refs.container.css("transition-duration", duration + "ms"); 1113 } 1114 1115 self.$refs.container.addClass("fancybox-is-open").trigger("focus"); 1116 1117 // Attempt to load content into slide 1118 // This will later call `afterLoad` -> `revealContent` 1119 self.loadSlide(current); 1120 1121 self.preload("image"); 1122 1123 return; 1124 } 1125 1126 // Get actual slide/stage positions (before cleaning up) 1127 slidePos = $.fancybox.getTranslate(previous.$slide); 1128 stagePos = $.fancybox.getTranslate(self.$refs.stage); 1129 1130 // Clean up all slides 1131 $.each(self.slides, function (index, slide) { 1132 $.fancybox.stop(slide.$slide, true); 1133 }); 1134 1135 if (previous.pos !== current.pos) { 1136 previous.isComplete = false; 1137 } 1138 1139 previous.$slide.removeClass("fancybox-slide--complete fancybox-slide--current"); 1140 1141 // If slides are out of place, then animate them to correct position 1142 if (isMoved) { 1143 // Calculate horizontal swipe distance 1144 diff = slidePos.left - (previous.pos * slidePos.width + previous.pos * previous.opts.gutter); 1145 1146 $.each(self.slides, function (index, slide) { 1147 slide.$slide.removeClass("fancybox-animated").removeClass(function (index, className) { 1148 return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" "); 1149 }); 1150 1151 // Make sure that each slide is in equal distance 1152 // This is mostly needed for freshly added slides, because they are not yet positioned 1153 var leftPos = slide.pos * slidePos.width + slide.pos * slide.opts.gutter; 1154 1155 $.fancybox.setTranslate(slide.$slide, { 1156 top: 0, 1157 left: leftPos - stagePos.left + diff 1158 }); 1159 1160 if (slide.pos !== current.pos) { 1161 slide.$slide.addClass("fancybox-slide--" + (slide.pos > current.pos ? "next" : "previous")); 1162 } 1163 1164 // Redraw to make sure that transition will start 1165 forceRedraw(slide.$slide); 1166 1167 // Animate the slide 1168 $.fancybox.animate( 1169 slide.$slide, { 1170 top: 0, 1171 left: (slide.pos - current.pos) * slidePos.width + (slide.pos - current.pos) * slide.opts.gutter 1172 }, 1173 duration, 1174 function () { 1175 slide.$slide 1176 .css({ 1177 transform: "", 1178 opacity: "" 1179 }) 1180 .removeClass("fancybox-slide--next fancybox-slide--previous"); 1181 1182 if (slide.pos === self.currPos) { 1183 self.complete(); 1184 } 1185 } 1186 ); 1187 }); 1188 } else if (duration && current.opts.transitionEffect) { 1189 // Set transition effect for previously active slide 1190 prop = "fancybox-animated fancybox-fx-" + current.opts.transitionEffect; 1191 1192 previous.$slide.addClass("fancybox-slide--" + (previous.pos > current.pos ? "next" : "previous")); 1193 1194 $.fancybox.animate( 1195 previous.$slide, 1196 prop, 1197 duration, 1198 function () { 1199 previous.$slide.removeClass(prop).removeClass("fancybox-slide--next fancybox-slide--previous"); 1200 }, 1201 false 1202 ); 1203 } 1204 1205 if (current.isLoaded) { 1206 self.revealContent(current); 1207 } else { 1208 self.loadSlide(current); 1209 } 1210 1211 self.preload("image"); 1212 }, 1213 1214 // Create new "slide" element 1215 // These are gallery items that are actually added to DOM 1216 // ======================================================= 1217 1218 createSlide: function (pos) { 1219 var self = this, 1220 $slide, 1221 index; 1222 1223 index = pos % self.group.length; 1224 index = index < 0 ? self.group.length + index : index; 1225 1226 if (!self.slides[pos] && self.group[index]) { 1227 $slide = $('<div class="fancybox-slide"></div>').appendTo(self.$refs.stage); 1228 1229 self.slides[pos] = $.extend(true, {}, self.group[index], { 1230 pos: pos, 1231 $slide: $slide, 1232 isLoaded: false 1233 }); 1234 1235 self.updateSlide(self.slides[pos]); 1236 } 1237 1238 return self.slides[pos]; 1239 }, 1240 1241 // Scale image to the actual size of the image; 1242 // x and y values should be relative to the slide 1243 // ============================================== 1244 1245 scaleToActual: function (x, y, duration) { 1246 var self = this, 1247 current = self.current, 1248 $content = current.$content, 1249 canvasWidth = $.fancybox.getTranslate(current.$slide).width, 1250 canvasHeight = $.fancybox.getTranslate(current.$slide).height, 1251 newImgWidth = current.width, 1252 newImgHeight = current.height, 1253 imgPos, 1254 posX, 1255 posY, 1256 scaleX, 1257 scaleY; 1258 1259 if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { 1260 return; 1261 } 1262 1263 self.isAnimating = true; 1264 1265 $.fancybox.stop($content); 1266 1267 x = x === undefined ? canvasWidth * 0.5 : x; 1268 y = y === undefined ? canvasHeight * 0.5 : y; 1269 1270 imgPos = $.fancybox.getTranslate($content); 1271 1272 imgPos.top -= $.fancybox.getTranslate(current.$slide).top; 1273 imgPos.left -= $.fancybox.getTranslate(current.$slide).left; 1274 1275 scaleX = newImgWidth / imgPos.width; 1276 scaleY = newImgHeight / imgPos.height; 1277 1278 // Get center position for original image 1279 posX = canvasWidth * 0.5 - newImgWidth * 0.5; 1280 posY = canvasHeight * 0.5 - newImgHeight * 0.5; 1281 1282 // Make sure image does not move away from edges 1283 if (newImgWidth > canvasWidth) { 1284 posX = imgPos.left * scaleX - (x * scaleX - x); 1285 1286 if (posX > 0) { 1287 posX = 0; 1288 } 1289 1290 if (posX < canvasWidth - newImgWidth) { 1291 posX = canvasWidth - newImgWidth; 1292 } 1293 } 1294 1295 if (newImgHeight > canvasHeight) { 1296 posY = imgPos.top * scaleY - (y * scaleY - y); 1297 1298 if (posY > 0) { 1299 posY = 0; 1300 } 1301 1302 if (posY < canvasHeight - newImgHeight) { 1303 posY = canvasHeight - newImgHeight; 1304 } 1305 } 1306 1307 self.updateCursor(newImgWidth, newImgHeight); 1308 1309 $.fancybox.animate( 1310 $content, { 1311 top: posY, 1312 left: posX, 1313 scaleX: scaleX, 1314 scaleY: scaleY 1315 }, 1316 duration || 366, 1317 function () { 1318 self.isAnimating = false; 1319 } 1320 ); 1321 1322 // Stop slideshow 1323 if (self.SlideShow && self.SlideShow.isActive) { 1324 self.SlideShow.stop(); 1325 } 1326 }, 1327 1328 // Scale image to fit inside parent element 1329 // ======================================== 1330 1331 scaleToFit: function (duration) { 1332 var self = this, 1333 current = self.current, 1334 $content = current.$content, 1335 end; 1336 1337 if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { 1338 return; 1339 } 1340 1341 self.isAnimating = true; 1342 1343 $.fancybox.stop($content); 1344 1345 end = self.getFitPos(current); 1346 1347 self.updateCursor(end.width, end.height); 1348 1349 $.fancybox.animate( 1350 $content, { 1351 top: end.top, 1352 left: end.left, 1353 scaleX: end.width / $content.width(), 1354 scaleY: end.height / $content.height() 1355 }, 1356 duration || 366, 1357 function () { 1358 self.isAnimating = false; 1359 } 1360 ); 1361 }, 1362 1363 // Calculate image size to fit inside viewport 1364 // =========================================== 1365 1366 getFitPos: function (slide) { 1367 var self = this, 1368 $content = slide.$content, 1369 $slide = slide.$slide, 1370 width = slide.width || slide.opts.width, 1371 height = slide.height || slide.opts.height, 1372 maxWidth, 1373 maxHeight, 1374 minRatio, 1375 aspectRatio, 1376 rez = {}; 1377 1378 if (!slide.isLoaded || !$content || !$content.length) { 1379 return false; 1380 } 1381 1382 maxWidth = $.fancybox.getTranslate(self.$refs.stage).width; 1383 maxHeight = $.fancybox.getTranslate(self.$refs.stage).height; 1384 1385 maxWidth -= 1386 parseFloat($slide.css("paddingLeft")) + 1387 parseFloat($slide.css("paddingRight")) + 1388 parseFloat($content.css("marginLeft")) + 1389 parseFloat($content.css("marginRight")); 1390 1391 maxHeight -= 1392 parseFloat($slide.css("paddingTop")) + 1393 parseFloat($slide.css("paddingBottom")) + 1394 parseFloat($content.css("marginTop")) + 1395 parseFloat($content.css("marginBottom")); 1396 1397 if (!width || !height) { 1398 width = maxWidth; 1399 height = maxHeight; 1400 } 1401 1402 minRatio = Math.min(1, maxWidth / width, maxHeight / height); 1403 1404 width = minRatio * width; 1405 height = minRatio * height; 1406 1407 // Adjust width/height to precisely fit into container 1408 if (width > maxWidth - 0.5) { 1409 width = maxWidth; 1410 } 1411 1412 if (height > maxHeight - 0.5) { 1413 height = maxHeight; 1414 } 1415 1416 if (slide.type === "image") { 1417 rez.top = Math.floor((maxHeight - height) * 0.5) + parseFloat($slide.css("paddingTop")); 1418 rez.left = Math.floor((maxWidth - width) * 0.5) + parseFloat($slide.css("paddingLeft")); 1419 } else if (slide.contentType === "video") { 1420 // Force aspect ratio for the video 1421 // "I say the whole world must learn of our peaceful ways… by force!" 1422 aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9; 1423 1424 if (height > width / aspectRatio) { 1425 height = width / aspectRatio; 1426 } else if (width > height * aspectRatio) { 1427 width = height * aspectRatio; 1428 } 1429 } 1430 1431 rez.width = width; 1432 rez.height = height; 1433 1434 return rez; 1435 }, 1436 1437 // Update content size and position for all slides 1438 // ============================================== 1439 1440 update: function (e) { 1441 var self = this; 1442 1443 $.each(self.slides, function (key, slide) { 1444 self.updateSlide(slide, e); 1445 }); 1446 }, 1447 1448 // Update slide content position and size 1449 // ====================================== 1450 1451 updateSlide: function (slide, e) { 1452 var self = this, 1453 $content = slide && slide.$content, 1454 width = slide.width || slide.opts.width, 1455 height = slide.height || slide.opts.height, 1456 $slide = slide.$slide; 1457 1458 // First, prevent caption overlap, if needed 1459 self.adjustCaption(slide); 1460 1461 // Then resize content to fit inside the slide 1462 if ($content && (width || height || slide.contentType === "video") && !slide.hasError) { 1463 $.fancybox.stop($content); 1464 1465 $.fancybox.setTranslate($content, self.getFitPos(slide)); 1466 1467 if (slide.pos === self.currPos) { 1468 self.isAnimating = false; 1469 1470 self.updateCursor(); 1471 } 1472 } 1473 1474 // Then some adjustments 1475 self.adjustLayout(slide); 1476 1477 if ($slide.length) { 1478 $slide.trigger("refresh"); 1479 1480 if (slide.pos === self.currPos) { 1481 self.$refs.toolbar 1482 .add(self.$refs.navigation.find(".fancybox-button--arrow_right")) 1483 .toggleClass("compensate-for-scrollbar", $slide.get(0).scrollHeight > $slide.get(0).clientHeight); 1484 } 1485 } 1486 1487 self.trigger("onUpdate", slide, e); 1488 }, 1489 1490 // Horizontally center slide 1491 // ========================= 1492 1493 centerSlide: function (duration) { 1494 var self = this, 1495 current = self.current, 1496 $slide = current.$slide; 1497 1498 if (self.isClosing || !current) { 1499 return; 1500 } 1501 1502 $slide.siblings().css({ 1503 transform: "", 1504 opacity: "" 1505 }); 1506 1507 $slide 1508 .parent() 1509 .children() 1510 .removeClass("fancybox-slide--previous fancybox-slide--next"); 1511 1512 $.fancybox.animate( 1513 $slide, { 1514 top: 0, 1515 left: 0, 1516 opacity: 1 1517 }, 1518 duration === undefined ? 0 : duration, 1519 function () { 1520 // Clean up 1521 $slide.css({ 1522 transform: "", 1523 opacity: "" 1524 }); 1525 1526 if (!current.isComplete) { 1527 self.complete(); 1528 } 1529 }, 1530 false 1531 ); 1532 }, 1533 1534 // Check if current slide is moved (swiped) 1535 // ======================================== 1536 1537 isMoved: function (slide) { 1538 var current = slide || this.current, 1539 slidePos, 1540 stagePos; 1541 1542 if (!current) { 1543 return false; 1544 } 1545 1546 stagePos = $.fancybox.getTranslate(this.$refs.stage); 1547 slidePos = $.fancybox.getTranslate(current.$slide); 1548 1549 return ( 1550 !current.$slide.hasClass("fancybox-animated") && 1551 (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5) 1552 ); 1553 }, 1554 1555 // Update cursor style depending if content can be zoomed 1556 // ====================================================== 1557 1558 updateCursor: function (nextWidth, nextHeight) { 1559 var self = this, 1560 current = self.current, 1561 $container = self.$refs.container, 1562 canPan, 1563 isZoomable; 1564 1565 if (!current || self.isClosing || !self.Guestures) { 1566 return; 1567 } 1568 1569 $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"); 1570 1571 canPan = self.canPan(nextWidth, nextHeight); 1572 1573 isZoomable = canPan ? true : self.isZoomable(); 1574 1575 $container.toggleClass("fancybox-is-zoomable", isZoomable); 1576 1577 $("[data-fancybox-zoom]").prop("disabled", !isZoomable); 1578 1579 if (canPan) { 1580 $container.addClass("fancybox-can-pan"); 1581 } else if ( 1582 isZoomable && 1583 (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) == "zoom")) 1584 ) { 1585 $container.addClass("fancybox-can-zoomIn"); 1586 } else if (current.opts.touch && (current.opts.touch.vertical || self.group.length > 1) && current.contentType !== "video") { 1587 $container.addClass("fancybox-can-swipe"); 1588 } 1589 }, 1590 1591 // Check if current slide is zoomable 1592 // ================================== 1593 1594 isZoomable: function () { 1595 var self = this, 1596 current = self.current, 1597 fitPos; 1598 1599 // Assume that slide is zoomable if: 1600 // - image is still loading 1601 // - actual size of the image is smaller than available area 1602 if (current && !self.isClosing && current.type === "image" && !current.hasError) { 1603 if (!current.isLoaded) { 1604 return true; 1605 } 1606 1607 fitPos = self.getFitPos(current); 1608 1609 if (fitPos && (current.width > fitPos.width || current.height > fitPos.height)) { 1610 return true; 1611 } 1612 } 1613 1614 return false; 1615 }, 1616 1617 // Check if current image dimensions are smaller than actual 1618 // ========================================================= 1619 1620 isScaledDown: function (nextWidth, nextHeight) { 1621 var self = this, 1622 rez = false, 1623 current = self.current, 1624 $content = current.$content; 1625 1626 if (nextWidth !== undefined && nextHeight !== undefined) { 1627 rez = nextWidth < current.width && nextHeight < current.height; 1628 } else if ($content) { 1629 rez = $.fancybox.getTranslate($content); 1630 rez = rez.width < current.width && rez.height < current.height; 1631 } 1632 1633 return rez; 1634 }, 1635 1636 // Check if image dimensions exceed parent element 1637 // =============================================== 1638 1639 canPan: function (nextWidth, nextHeight) { 1640 var self = this, 1641 current = self.current, 1642 pos = null, 1643 rez = false; 1644 1645 if (current.type === "image" && (current.isComplete || (nextWidth && nextHeight)) && !current.hasError) { 1646 rez = self.getFitPos(current); 1647 1648 if (nextWidth !== undefined && nextHeight !== undefined) { 1649 pos = { 1650 width: nextWidth, 1651 height: nextHeight 1652 }; 1653 } else if (current.isComplete) { 1654 pos = $.fancybox.getTranslate(current.$content); 1655 } 1656 1657 if (pos && rez) { 1658 rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5; 1659 } 1660 } 1661 1662 return rez; 1663 }, 1664 1665 // Load content into the slide 1666 // =========================== 1667 1668 loadSlide: function (slide) { 1669 var self = this, 1670 type, 1671 $slide, 1672 ajaxLoad; 1673 1674 if (slide.isLoading || slide.isLoaded) { 1675 return; 1676 } 1677 1678 slide.isLoading = true; 1679 1680 if (self.trigger("beforeLoad", slide) === false) { 1681 slide.isLoading = false; 1682 1683 return false; 1684 } 1685 1686 type = slide.type; 1687 $slide = slide.$slide; 1688 1689 $slide 1690 .off("refresh") 1691 .trigger("onReset") 1692 .addClass(slide.opts.slideClass); 1693 1694 // Create content depending on the type 1695 switch (type) { 1696 case "image": 1697 self.setImage(slide); 1698 1699 break; 1700 1701 case "iframe": 1702 self.setIframe(slide); 1703 1704 break; 1705 1706 case "html": 1707 self.setContent(slide, slide.src || slide.content); 1708 1709 break; 1710 1711 case "video": 1712 self.setContent( 1713 slide, 1714 slide.opts.video.tpl 1715 .replace(/\{\{src\}\}/gi, slide.src) 1716 .replace("{{format}}", slide.opts.videoFormat || slide.opts.video.format || "") 1717 .replace("{{poster}}", slide.thumb || "") 1718 ); 1719 1720 break; 1721 1722 case "inline": 1723 if ($(slide.src).length) { 1724 self.setContent(slide, $(slide.src)); 1725 } else { 1726 self.setError(slide); 1727 } 1728 1729 break; 1730 1731 case "ajax": 1732 self.showLoading(slide); 1733 1734 ajaxLoad = $.ajax( 1735 $.extend({}, slide.opts.ajax.settings, { 1736 url: slide.src, 1737 success: function (data, textStatus) { 1738 if (textStatus === "success") { 1739 self.setContent(slide, data); 1740 } 1741 }, 1742 error: function (jqXHR, textStatus) { 1743 if (jqXHR && textStatus !== "abort") { 1744 self.setError(slide); 1745 } 1746 } 1747 }) 1748 ); 1749 1750 $slide.one("onReset", function () { 1751 ajaxLoad.abort(); 1752 }); 1753 1754 break; 1755 1756 default: 1757 self.setError(slide); 1758 1759 break; 1760 } 1761 1762 return true; 1763 }, 1764 1765 // Use thumbnail image, if possible 1766 // ================================ 1767 1768 setImage: function (slide) { 1769 var self = this, 1770 ghost; 1771 1772 // Check if need to show loading icon 1773 setTimeout(function () { 1774 var $img = slide.$image; 1775 1776 if (!self.isClosing && slide.isLoading && (!$img || !$img.length || !$img[0].complete) && !slide.hasError) { 1777 self.showLoading(slide); 1778 } 1779 }, 50); 1780 1781 //Check if image has srcset 1782 self.checkSrcset(slide); 1783 1784 // This will be wrapper containing both ghost and actual image 1785 slide.$content = $('<div class="fancybox-content"></div>') 1786 .addClass("fancybox-is-hidden") 1787 .appendTo(slide.$slide.addClass("fancybox-slide--image")); 1788 1789 // If we have a thumbnail, we can display it while actual image is loading 1790 // Users will not stare at black screen and actual image will appear gradually 1791 if (slide.opts.preload !== false && slide.opts.width && slide.opts.height && slide.thumb) { 1792 slide.width = slide.opts.width; 1793 slide.height = slide.opts.height; 1794 1795 ghost = document.createElement("img"); 1796 1797 ghost.onerror = function () { 1798 $(this).remove(); 1799 1800 slide.$ghost = null; 1801 }; 1802 1803 ghost.onload = function () { 1804 self.afterLoad(slide); 1805 }; 1806 1807 slide.$ghost = $(ghost) 1808 .addClass("fancybox-image") 1809 .appendTo(slide.$content) 1810 .attr("src", slide.thumb); 1811 } 1812 1813 // Start loading actual image 1814 self.setBigImage(slide); 1815 }, 1816 1817 // Check if image has srcset and get the source 1818 // ============================================ 1819 checkSrcset: function (slide) { 1820 var srcset = slide.opts.srcset || slide.opts.image.srcset, 1821 found, 1822 temp, 1823 pxRatio, 1824 windowWidth; 1825 1826 // If we have "srcset", then we need to find first matching "src" value. 1827 // This is necessary, because when you set an src attribute, the browser will preload the image 1828 // before any javascript or even CSS is applied. 1829 if (srcset) { 1830 pxRatio = window.devicePixelRatio || 1; 1831 windowWidth = window.innerWidth * pxRatio; 1832 1833 temp = srcset.split(",").map(function (el) { 1834 var ret = {}; 1835 1836 el.trim() 1837 .split(/\s+/) 1838 .forEach(function (el, i) { 1839 var value = parseInt(el.substring(0, el.length - 1), 10); 1840 1841 if (i === 0) { 1842 return (ret.url = el); 1843 } 1844 1845 if (value) { 1846 ret.value = value; 1847 ret.postfix = el[el.length - 1]; 1848 } 1849 }); 1850 1851 return ret; 1852 }); 1853 1854 // Sort by value 1855 temp.sort(function (a, b) { 1856 return a.value - b.value; 1857 }); 1858 1859 // Ok, now we have an array of all srcset values 1860 for (var j = 0; j < temp.length; j++) { 1861 var el = temp[j]; 1862 1863 if ((el.postfix === "w" && el.value >= windowWidth) || (el.postfix === "x" && el.value >= pxRatio)) { 1864 found = el; 1865 break; 1866 } 1867 } 1868 1869 // If not found, take the last one 1870 if (!found && temp.length) { 1871 found = temp[temp.length - 1]; 1872 } 1873 1874 if (found) { 1875 slide.src = found.url; 1876 1877 // If we have default width/height values, we can calculate height for matching source 1878 if (slide.width && slide.height && found.postfix == "w") { 1879 slide.height = (slide.width / slide.height) * found.value; 1880 slide.width = found.value; 1881 } 1882 1883 slide.opts.srcset = srcset; 1884 } 1885 } 1886 }, 1887 1888 // Create full-size image 1889 // ====================== 1890 1891 setBigImage: function (slide) { 1892 var self = this, 1893 img = document.createElement("img"), 1894 $img = $(img); 1895 1896 slide.$image = $img 1897 .one("error", function () { 1898 self.setError(slide); 1899 }) 1900 .one("load", function () { 1901 var sizes; 1902 1903 if (!slide.$ghost) { 1904 self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight); 1905 1906 self.afterLoad(slide); 1907 } 1908 1909 if (self.isClosing) { 1910 return; 1911 } 1912 1913 if (slide.opts.srcset) { 1914 sizes = slide.opts.sizes; 1915 1916 if (!sizes || sizes === "auto") { 1917 sizes = 1918 (slide.width / slide.height > 1 && $W.width() / $W.height() > 1 ? "100" : Math.round((slide.width / slide.height) * 100)) + 1919 "vw"; 1920 } 1921 1922 $img.attr("sizes", sizes).attr("srcset", slide.opts.srcset); 1923 } 1924 1925 // Hide temporary image after some delay 1926 if (slide.$ghost) { 1927 setTimeout(function () { 1928 if (slide.$ghost && !self.isClosing) { 1929 slide.$ghost.hide(); 1930 } 1931 }, Math.min(300, Math.max(1000, slide.height / 1600))); 1932 } 1933 1934 self.hideLoading(slide); 1935 }) 1936 .addClass("fancybox-image") 1937 .attr("src", slide.src) 1938 .appendTo(slide.$content); 1939 1940 if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) { 1941 $img.trigger("load"); 1942 } else if (img.error) { 1943 $img.trigger("error"); 1944 } 1945 }, 1946 1947 // Computes the slide size from image size and maxWidth/maxHeight 1948 // ============================================================== 1949 1950 resolveImageSlideSize: function (slide, imgWidth, imgHeight) { 1951 var maxWidth = parseInt(slide.opts.width, 10), 1952 maxHeight = parseInt(slide.opts.height, 10); 1953 1954 // Sets the default values from the image 1955 slide.width = imgWidth; 1956 slide.height = imgHeight; 1957 1958 if (maxWidth > 0) { 1959 slide.width = maxWidth; 1960 slide.height = Math.floor((maxWidth * imgHeight) / imgWidth); 1961 } 1962 1963 if (maxHeight > 0) { 1964 slide.width = Math.floor((maxHeight * imgWidth) / imgHeight); 1965 slide.height = maxHeight; 1966 } 1967 }, 1968 1969 // Create iframe wrapper, iframe and bindings 1970 // ========================================== 1971 1972 setIframe: function (slide) { 1973 var self = this, 1974 opts = slide.opts.iframe, 1975 $slide = slide.$slide, 1976 $iframe; 1977 1978 slide.$content = $('<div class="fancybox-content' + (opts.preload ? " fancybox-is-hidden" : "") + '"></div>') 1979 .css(opts.css) 1980 .appendTo($slide); 1981 1982 $slide.addClass("fancybox-slide--" + slide.contentType); 1983 1984 slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime())) 1985 .attr(opts.attr) 1986 .appendTo(slide.$content); 1987 1988 if (opts.preload) { 1989 self.showLoading(slide); 1990 1991 // Unfortunately, it is not always possible to determine if iframe is successfully loaded 1992 // (due to browser security policy) 1993 1994 $iframe.on("load.fb error.fb", function (e) { 1995 this.isReady = 1; 1996 1997 slide.$slide.trigger("refresh"); 1998 1999 self.afterLoad(slide); 2000 }); 2001 2002 // Recalculate iframe content size 2003 // =============================== 2004 2005 $slide.on("refresh.fb", function () { 2006 var $content = slide.$content, 2007 frameWidth = opts.css.width, 2008 frameHeight = opts.css.height, 2009 $contents, 2010 $body; 2011 2012 if ($iframe[0].isReady !== 1) { 2013 return; 2014 } 2015 2016 try { 2017 $contents = $iframe.contents(); 2018 $body = $contents.find("body"); 2019 } catch (ignore) {} 2020 2021 // Calculate content dimensions, if it is accessible 2022 if ($body && $body.length && $body.children().length) { 2023 // Avoid scrolling to top (if multiple instances) 2024 $slide.css("overflow", "visible"); 2025 2026 $content.css({ 2027 width: "100%", 2028 "max-width": "100%", 2029 height: "9999px" 2030 }); 2031 2032 if (frameWidth === undefined) { 2033 frameWidth = Math.ceil(Math.max($body[0].clientWidth, $body.outerWidth(true))); 2034 } 2035 2036 $content.css("width", frameWidth ? frameWidth : "").css("max-width", ""); 2037 2038 if (frameHeight === undefined) { 2039 frameHeight = Math.ceil(Math.max($body[0].clientHeight, $body.outerHeight(true))); 2040 } 2041 2042 $content.css("height", frameHeight ? frameHeight : ""); 2043 2044 $slide.css("overflow", "auto"); 2045 } 2046 2047 $content.removeClass("fancybox-is-hidden"); 2048 }); 2049 } else { 2050 self.afterLoad(slide); 2051 } 2052 2053 $iframe.attr("src", slide.src); 2054 2055 // Remove iframe if closing or changing gallery item 2056 $slide.one("onReset", function () { 2057 // This helps IE not to throw errors when closing 2058 try { 2059 $(this) 2060 .find("iframe") 2061 .hide() 2062 .unbind() 2063 .attr("src", "//about:blank"); 2064 } catch (ignore) {} 2065 2066 $(this) 2067 .off("refresh.fb") 2068 .empty(); 2069 2070 slide.isLoaded = false; 2071 slide.isRevealed = false; 2072 }); 2073 }, 2074 2075 // Wrap and append content to the slide 2076 // ====================================== 2077 2078 setContent: function (slide, content) { 2079 var self = this; 2080 2081 if (self.isClosing) { 2082 return; 2083 } 2084 2085 self.hideLoading(slide); 2086 2087 if (slide.$content) { 2088 $.fancybox.stop(slide.$content); 2089 } 2090 2091 slide.$slide.empty(); 2092 2093 // If content is a jQuery object, then it will be moved to the slide. 2094 // The placeholder is created so we will know where to put it back. 2095 if (isQuery(content) && content.parent().length) { 2096 // Make sure content is not already moved to fancyBox 2097 if (content.hasClass("fancybox-content") || content.parent().hasClass("fancybox-content")) { 2098 content.parents(".fancybox-slide").trigger("onReset"); 2099 } 2100 2101 // Create temporary element marking original place of the content 2102 slide.$placeholder = $("<div>") 2103 .hide() 2104 .insertAfter(content); 2105 2106 // Make sure content is visible 2107 content.css("display", "inline-block"); 2108 } else if (!slide.hasError) { 2109 // If content is just a plain text, try to convert it to html 2110 if ($.type(content) === "string") { 2111 content = $("<div>") 2112 .append($.trim(content)) 2113 .contents(); 2114 } 2115 2116 // If "filter" option is provided, then filter content 2117 if (slide.opts.filter) { 2118 content = $("<div>") 2119 .html(content) 2120 .find(slide.opts.filter); 2121 } 2122 } 2123 2124 slide.$slide.one("onReset", function () { 2125 // Pause all html5 video/audio 2126 $(this) 2127 .find("video,audio") 2128 .trigger("pause"); 2129 2130 // Put content back 2131 if (slide.$placeholder) { 2132 slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove(); 2133 2134 slide.$placeholder = null; 2135 } 2136 2137 // Remove custom close button 2138 if (slide.$smallBtn) { 2139 slide.$smallBtn.remove(); 2140 2141 slide.$smallBtn = null; 2142 } 2143 2144 // Remove content and mark slide as not loaded 2145 if (!slide.hasError) { 2146 $(this).empty(); 2147 2148 slide.isLoaded = false; 2149 slide.isRevealed = false; 2150 } 2151 }); 2152 2153 $(content).appendTo(slide.$slide); 2154 2155 if ($(content).is("video,audio")) { 2156 $(content).addClass("fancybox-video"); 2157 2158 $(content).wrap("<div></div>"); 2159 2160 slide.contentType = "video"; 2161 2162 slide.opts.width = slide.opts.width || $(content).attr("width"); 2163 slide.opts.height = slide.opts.height || $(content).attr("height"); 2164 } 2165 2166 slide.$content = slide.$slide 2167 .children() 2168 .filter("div,form,main,video,audio,article,.fancybox-content") 2169 .first(); 2170 2171 slide.$content.siblings().hide(); 2172 2173 // Re-check if there is a valid content 2174 // (in some cases, ajax response can contain various elements or plain text) 2175 if (!slide.$content.length) { 2176 slide.$content = slide.$slide 2177 .wrapInner("<div></div>") 2178 .children() 2179 .first(); 2180 } 2181 2182 slide.$content.addClass("fancybox-content"); 2183 2184 slide.$slide.addClass("fancybox-slide--" + slide.contentType); 2185 2186 self.afterLoad(slide); 2187 }, 2188 2189 // Display error message 2190 // ===================== 2191 2192 setError: function (slide) { 2193 slide.hasError = true; 2194 2195 slide.$slide 2196 .trigger("onReset") 2197 .removeClass("fancybox-slide--" + slide.contentType) 2198 .addClass("fancybox-slide--error"); 2199 2200 slide.contentType = "html"; 2201 2202 this.setContent(slide, this.translate(slide, slide.opts.errorTpl)); 2203 2204 if (slide.pos === this.currPos) { 2205 this.isAnimating = false; 2206 } 2207 }, 2208 2209 // Show loading icon inside the slide 2210 // ================================== 2211 2212 showLoading: function (slide) { 2213 var self = this; 2214 2215 slide = slide || self.current; 2216 2217 if (slide && !slide.$spinner) { 2218 slide.$spinner = $(self.translate(self, self.opts.spinnerTpl)) 2219 .appendTo(slide.$slide) 2220 .hide() 2221 .fadeIn("fast"); 2222 } 2223 }, 2224 2225 // Remove loading icon from the slide 2226 // ================================== 2227 2228 hideLoading: function (slide) { 2229 var self = this; 2230 2231 slide = slide || self.current; 2232 2233 if (slide && slide.$spinner) { 2234 slide.$spinner.stop().remove(); 2235 2236 delete slide.$spinner; 2237 } 2238 }, 2239 2240 // Adjustments after slide content has been loaded 2241 // =============================================== 2242 2243 afterLoad: function (slide) { 2244 var self = this; 2245 2246 if (self.isClosing) { 2247 return; 2248 } 2249 2250 slide.isLoading = false; 2251 slide.isLoaded = true; 2252 2253 self.trigger("afterLoad", slide); 2254 2255 self.hideLoading(slide); 2256 2257 // Add small close button 2258 if (slide.opts.smallBtn && (!slide.$smallBtn || !slide.$smallBtn.length)) { 2259 slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).appendTo(slide.$content); 2260 } 2261 2262 // Disable right click 2263 if (slide.opts.protect && slide.$content && !slide.hasError) { 2264 slide.$content.on("contextmenu.fb", function (e) { 2265 if (e.button == 2) { 2266 e.preventDefault(); 2267 } 2268 2269 return true; 2270 }); 2271 2272 // Add fake element on top of the image 2273 // This makes a bit harder for user to select image 2274 if (slide.type === "image") { 2275 $('<div class="fancybox-spaceball"></div>').appendTo(slide.$content); 2276 } 2277 } 2278 2279 self.adjustCaption(slide); 2280 2281 self.adjustLayout(slide); 2282 2283 if (slide.pos === self.currPos) { 2284 self.updateCursor(); 2285 } 2286 2287 self.revealContent(slide); 2288 }, 2289 2290 // Prevent caption overlap, 2291 // fix css inconsistency across browsers 2292 // ===================================== 2293 2294 adjustCaption: function (slide) { 2295 var self = this, 2296 current = slide || self.current, 2297 caption = current.opts.caption, 2298 preventOverlap = current.opts.preventCaptionOverlap, 2299 $caption = self.$refs.caption, 2300 $clone, 2301 captionH = false; 2302 2303 $caption.toggleClass("fancybox-caption--separate", preventOverlap); 2304 2305 if (preventOverlap && caption && caption.length) { 2306 if (current.pos !== self.currPos) { 2307 $clone = $caption.clone().appendTo($caption.parent()); 2308 2309 $clone 2310 .children() 2311 .eq(0) 2312 .empty() 2313 .html(caption); 2314 2315 captionH = $clone.outerHeight(true); 2316 2317 $clone.empty().remove(); 2318 } else if (self.$caption) { 2319 captionH = self.$caption.outerHeight(true); 2320 } 2321 2322 current.$slide.css("padding-bottom", captionH || ""); 2323 } 2324 }, 2325 2326 // Simple hack to fix inconsistency across browsers, described here (affects Edge, too): 2327 // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 2328 // ==================================================================================== 2329 2330 adjustLayout: function (slide) { 2331 var self = this, 2332 current = slide || self.current, 2333 scrollHeight, 2334 marginBottom, 2335 inlinePadding, 2336 actualPadding; 2337 2338 if (current.isLoaded && current.opts.disableLayoutFix !== true) { 2339 current.$content.css("margin-bottom", ""); 2340 2341 // If we would always set margin-bottom for the content, 2342 // then it would potentially break vertical align 2343 if (current.$content.outerHeight() > current.$slide.height() + 0.5) { 2344 inlinePadding = current.$slide[0].style["padding-bottom"]; 2345 actualPadding = current.$slide.css("padding-bottom"); 2346 2347 if (parseFloat(actualPadding) > 0) { 2348 scrollHeight = current.$slide[0].scrollHeight; 2349 2350 current.$slide.css("padding-bottom", 0); 2351 2352 if (Math.abs(scrollHeight - current.$slide[0].scrollHeight) < 1) { 2353 marginBottom = actualPadding; 2354 } 2355 2356 current.$slide.css("padding-bottom", inlinePadding); 2357 } 2358 } 2359 2360 current.$content.css("margin-bottom", marginBottom); 2361 } 2362 }, 2363 2364 // Make content visible 2365 // This method is called right after content has been loaded or 2366 // user navigates gallery and transition should start 2367 // ============================================================ 2368 2369 revealContent: function (slide) { 2370 var self = this, 2371 $slide = slide.$slide, 2372 end = false, 2373 start = false, 2374 isMoved = self.isMoved(slide), 2375 isRevealed = slide.isRevealed, 2376 effect, 2377 effectClassName, 2378 duration, 2379 opacity; 2380 2381 slide.isRevealed = true; 2382 2383 effect = slide.opts[self.firstRun ? "animationEffect" : "transitionEffect"]; 2384 duration = slide.opts[self.firstRun ? "animationDuration" : "transitionDuration"]; 2385 2386 duration = parseInt(slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10); 2387 2388 if (isMoved || slide.pos !== self.currPos || !duration) { 2389 effect = false; 2390 } 2391 2392 // Check if can zoom 2393 if (effect === "zoom") { 2394 if (slide.pos === self.currPos && duration && slide.type === "image" && !slide.hasError && (start = self.getThumbPos(slide))) { 2395 end = self.getFitPos(slide); 2396 } else { 2397 effect = "fade"; 2398 } 2399 } 2400 2401 // Zoom animation 2402 // ============== 2403 if (effect === "zoom") { 2404 self.isAnimating = true; 2405 2406 end.scaleX = end.width / start.width; 2407 end.scaleY = end.height / start.height; 2408 2409 // Check if we need to animate opacity 2410 opacity = slide.opts.zoomOpacity; 2411 2412 if (opacity == "auto") { 2413 opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1; 2414 } 2415 2416 if (opacity) { 2417 start.opacity = 0.1; 2418 end.opacity = 1; 2419 } 2420 2421 // Draw image at start position 2422 $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start); 2423 2424 forceRedraw(slide.$content); 2425 2426 // Start animation 2427 $.fancybox.animate(slide.$content, end, duration, function () { 2428 self.isAnimating = false; 2429 2430 self.complete(); 2431 }); 2432 2433 return; 2434 } 2435 2436 self.updateSlide(slide); 2437 2438 // Simply show content if no effect 2439 // ================================ 2440 if (!effect) { 2441 slide.$content.removeClass("fancybox-is-hidden"); 2442 2443 if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) { 2444 slide.$content.hide().fadeIn("fast"); 2445 } 2446 2447 if (slide.pos === self.currPos) { 2448 self.complete(); 2449 } 2450 2451 return; 2452 } 2453 2454 // Prepare for CSS transiton 2455 // ========================= 2456 $.fancybox.stop($slide); 2457 2458 //effectClassName = "fancybox-animated fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-fx-" + effect; 2459 effectClassName = "fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-animated fancybox-fx-" + effect; 2460 2461 $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName); 2462 2463 slide.$content.removeClass("fancybox-is-hidden"); 2464 2465 // Force reflow 2466 forceRedraw($slide); 2467 2468 if (slide.type !== "image") { 2469 slide.$content.hide().show(0); 2470 } 2471 2472 $.fancybox.animate( 2473 $slide, 2474 "fancybox-slide--current", 2475 duration, 2476 function () { 2477 $slide.removeClass(effectClassName).css({ 2478 transform: "", 2479 opacity: "" 2480 }); 2481 2482 if (slide.pos === self.currPos) { 2483 self.complete(); 2484 } 2485 }, 2486 true 2487 ); 2488 }, 2489 2490 // Check if we can and have to zoom from thumbnail 2491 //================================================ 2492 2493 getThumbPos: function (slide) { 2494 var rez = false, 2495 $thumb = slide.$thumb, 2496 thumbPos, 2497 btw, 2498 brw, 2499 bbw, 2500 blw; 2501 2502 if (!$thumb || !inViewport($thumb[0])) { 2503 return false; 2504 } 2505 2506 thumbPos = $.fancybox.getTranslate($thumb); 2507 2508 btw = parseFloat($thumb.css("border-top-width") || 0); 2509 brw = parseFloat($thumb.css("border-right-width") || 0); 2510 bbw = parseFloat($thumb.css("border-bottom-width") || 0); 2511 blw = parseFloat($thumb.css("border-left-width") || 0); 2512 2513 rez = { 2514 top: thumbPos.top + btw, 2515 left: thumbPos.left + blw, 2516 width: thumbPos.width - brw - blw, 2517 height: thumbPos.height - btw - bbw, 2518 scaleX: 1, 2519 scaleY: 1 2520 }; 2521 2522 return thumbPos.width > 0 && thumbPos.height > 0 ? rez : false; 2523 }, 2524 2525 // Final adjustments after current gallery item is moved to position 2526 // and it`s content is loaded 2527 // ================================================================== 2528 2529 complete: function () { 2530 var self = this, 2531 current = self.current, 2532 slides = {}, 2533 $el; 2534 2535 if (self.isMoved() || !current.isLoaded) { 2536 return; 2537 } 2538 2539 if (!current.isComplete) { 2540 current.isComplete = true; 2541 2542 current.$slide.siblings().trigger("onReset"); 2543 2544 self.preload("inline"); 2545 2546 // Trigger any CSS transiton inside the slide 2547 forceRedraw(current.$slide); 2548 2549 current.$slide.addClass("fancybox-slide--complete"); 2550 2551 // Remove unnecessary slides 2552 $.each(self.slides, function (key, slide) { 2553 if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) { 2554 slides[slide.pos] = slide; 2555 } else if (slide) { 2556 $.fancybox.stop(slide.$slide); 2557 2558 slide.$slide.off().remove(); 2559 } 2560 }); 2561 2562 self.slides = slides; 2563 } 2564 2565 self.isAnimating = false; 2566 2567 self.updateCursor(); 2568 2569 self.trigger("afterShow"); 2570 2571 // Autoplay first html5 video/audio 2572 if (!!current.opts.video.autoStart) { 2573 current.$slide 2574 .find("video,audio") 2575 .filter(":visible:first") 2576 .trigger("play") 2577 .one("ended", function () { 2578 if (Document.exitFullscreen) { 2579 Document.exitFullscreen(); 2580 } else if (this.webkitExitFullscreen) { 2581 this.webkitExitFullscreen(); 2582 } 2583 2584 self.next(); 2585 }); 2586 } 2587 2588 // Try to focus on the first focusable element 2589 if (current.opts.autoFocus && current.contentType === "html") { 2590 // Look for the first input with autofocus attribute 2591 $el = current.$content.find("input[autofocus]:enabled:visible:first"); 2592 2593 if ($el.length) { 2594 $el.trigger("focus"); 2595 } else { 2596 self.focus(null, true); 2597 } 2598 } 2599 2600 // Avoid jumping 2601 current.$slide.scrollTop(0).scrollLeft(0); 2602 }, 2603 2604 // Preload next and previous slides 2605 // ================================ 2606 2607 preload: function (type) { 2608 var self = this, 2609 prev, 2610 next; 2611 2612 if (self.group.length < 2) { 2613 return; 2614 } 2615 2616 next = self.slides[self.currPos + 1]; 2617 prev = self.slides[self.currPos - 1]; 2618 2619 if (prev && prev.type === type) { 2620 self.loadSlide(prev); 2621 } 2622 2623 if (next && next.type === type) { 2624 self.loadSlide(next); 2625 } 2626 }, 2627 2628 // Try to find and focus on the first focusable element 2629 // ==================================================== 2630 2631 focus: function (e, firstRun) { 2632 var self = this, 2633 focusableStr = [ 2634 "a[href]", 2635 "area[href]", 2636 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 2637 "select:not([disabled]):not([aria-hidden])", 2638 "textarea:not([disabled]):not([aria-hidden])", 2639 "button:not([disabled]):not([aria-hidden])", 2640 "iframe", 2641 "object", 2642 "embed", 2643 "video", 2644 "audio", 2645 "[contenteditable]", 2646 '[tabindex]:not([tabindex^="-"])' 2647 ].join(","), 2648 focusableItems, 2649 focusedItemIndex; 2650 2651 if (self.isClosing) { 2652 return; 2653 } 2654 2655 if (e || !self.current || !self.current.isComplete) { 2656 // Focus on any element inside fancybox 2657 focusableItems = self.$refs.container.find("*:visible"); 2658 } else { 2659 // Focus inside current slide 2660 focusableItems = self.current.$slide.find("*:visible" + (firstRun ? ":not(.fancybox-close-small)" : "")); 2661 } 2662 2663 focusableItems = focusableItems.filter(focusableStr).filter(function () { 2664 return $(this).css("visibility") !== "hidden" && !$(this).hasClass("disabled"); 2665 }); 2666 2667 if (focusableItems.length) { 2668 focusedItemIndex = focusableItems.index(document.activeElement); 2669 2670 if (e && e.shiftKey) { 2671 // Back tab 2672 if (focusedItemIndex < 0 || focusedItemIndex == 0) { 2673 e.preventDefault(); 2674 2675 focusableItems.eq(focusableItems.length - 1).trigger("focus"); 2676 } 2677 } else { 2678 // Outside or Forward tab 2679 if (focusedItemIndex < 0 || focusedItemIndex == focusableItems.length - 1) { 2680 if (e) { 2681 e.preventDefault(); 2682 } 2683 2684 focusableItems.eq(0).trigger("focus"); 2685 } 2686 } 2687 } else { 2688 self.$refs.container.trigger("focus"); 2689 } 2690 }, 2691 2692 // Activates current instance - brings container to the front and enables keyboard, 2693 // notifies other instances about deactivating 2694 // ================================================================================= 2695 2696 activate: function () { 2697 var self = this; 2698 2699 // Deactivate all instances 2700 $(".fancybox-container").each(function () { 2701 var instance = $(this).data("FancyBox"); 2702 2703 // Skip self and closing instances 2704 if (instance && instance.id !== self.id && !instance.isClosing) { 2705 instance.trigger("onDeactivate"); 2706 2707 instance.removeEvents(); 2708 2709 instance.isVisible = false; 2710 } 2711 }); 2712 2713 self.isVisible = true; 2714 2715 if (self.current || self.isIdle) { 2716 self.update(); 2717 2718 self.updateControls(); 2719 } 2720 2721 self.trigger("onActivate"); 2722 2723 self.addEvents(); 2724 }, 2725 2726 // Start closing procedure 2727 // This will start "zoom-out" animation if needed and clean everything up afterwards 2728 // ================================================================================= 2729 2730 close: function (e, d) { 2731 var self = this, 2732 current = self.current, 2733 effect, 2734 duration, 2735 $content, 2736 domRect, 2737 opacity, 2738 start, 2739 end; 2740 2741 var done = function () { 2742 self.cleanUp(e); 2743 }; 2744 2745 if (self.isClosing) { 2746 return false; 2747 } 2748 2749 self.isClosing = true; 2750 2751 // If beforeClose callback prevents closing, make sure content is centered 2752 if (self.trigger("beforeClose", e) === false) { 2753 self.isClosing = false; 2754 2755 requestAFrame(function () { 2756 self.update(); 2757 }); 2758 2759 return false; 2760 } 2761 2762 // Remove all events 2763 // If there are multiple instances, they will be set again by "activate" method 2764 self.removeEvents(); 2765 2766 $content = current.$content; 2767 effect = current.opts.animationEffect; 2768 duration = $.isNumeric(d) ? d : effect ? current.opts.animationDuration : 0; 2769 2770 current.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"); 2771 2772 if (e !== true) { 2773 $.fancybox.stop(current.$slide); 2774 } else { 2775 effect = false; 2776 } 2777 2778 // Remove other slides 2779 current.$slide 2780 .siblings() 2781 .trigger("onReset") 2782 .remove(); 2783 2784 // Trigger animations 2785 if (duration) { 2786 self.$refs.container 2787 .removeClass("fancybox-is-open") 2788 .addClass("fancybox-is-closing") 2789 .css("transition-duration", duration + "ms"); 2790 } 2791 2792 // Clean up 2793 self.hideLoading(current); 2794 2795 self.hideControls(true); 2796 2797 self.updateCursor(); 2798 2799 // Check if possible to zoom-out 2800 if ( 2801 effect === "zoom" && 2802 !($content && duration && current.type === "image" && !self.isMoved() && !current.hasError && (end = self.getThumbPos(current))) 2803 ) { 2804 effect = "fade"; 2805 } 2806 2807 if (effect === "zoom") { 2808 $.fancybox.stop($content); 2809 2810 domRect = $.fancybox.getTranslate($content); 2811 2812 start = { 2813 top: domRect.top, 2814 left: domRect.left, 2815 scaleX: domRect.width / end.width, 2816 scaleY: domRect.height / end.height, 2817 width: end.width, 2818 height: end.height 2819 }; 2820 2821 // Check if we need to animate opacity 2822 opacity = current.opts.zoomOpacity; 2823 2824 if (opacity == "auto") { 2825 opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1; 2826 } 2827 2828 if (opacity) { 2829 end.opacity = 0; 2830 } 2831 2832 $.fancybox.setTranslate($content, start); 2833 2834 forceRedraw($content); 2835 2836 $.fancybox.animate($content, end, duration, done); 2837 2838 return true; 2839 } 2840 2841 if (effect && duration) { 2842 $.fancybox.animate( 2843 current.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"), 2844 "fancybox-animated fancybox-fx-" + effect, 2845 duration, 2846 done 2847 ); 2848 } else { 2849 // If skip animation 2850 if (e === true) { 2851 setTimeout(done, duration); 2852 } else { 2853 done(); 2854 } 2855 } 2856 2857 return true; 2858 }, 2859 2860 // Final adjustments after removing the instance 2861 // ============================================= 2862 2863 cleanUp: function (e) { 2864 var self = this, 2865 instance, 2866 $focus = self.current.opts.$orig, 2867 x, 2868 y; 2869 2870 self.current.$slide.trigger("onReset"); 2871 2872 self.$refs.container.empty().remove(); 2873 2874 self.trigger("afterClose", e); 2875 2876 // Place back focus 2877 if (!!self.current.opts.backFocus) { 2878 if (!$focus || !$focus.length || !$focus.is(":visible")) { 2879 $focus = self.$trigger; 2880 } 2881 2882 if ($focus && $focus.length) { 2883 x = window.scrollX; 2884 y = window.scrollY; 2885 2886 $focus.trigger("focus"); 2887 2888 $("html, body") 2889 .scrollTop(y) 2890 .scrollLeft(x); 2891 } 2892 } 2893 2894 self.current = null; 2895 2896 // Check if there are other instances 2897 instance = $.fancybox.getInstance(); 2898 2899 if (instance) { 2900 instance.activate(); 2901 } else { 2902 $("body").removeClass("fancybox-active compensate-for-scrollbar"); 2903 2904 $("#fancybox-style-noscroll").remove(); 2905 } 2906 }, 2907 2908 // Call callback and trigger an event 2909 // ================================== 2910 2911 trigger: function (name, slide) { 2912 var args = Array.prototype.slice.call(arguments, 1), 2913 self = this, 2914 obj = slide && slide.opts ? slide : self.current, 2915 rez; 2916 2917 if (obj) { 2918 args.unshift(obj); 2919 } else { 2920 obj = self; 2921 } 2922 2923 args.unshift(self); 2924 2925 if ($.isFunction(obj.opts[name])) { 2926 rez = obj.opts[name].apply(obj, args); 2927 } 2928 2929 if (rez === false) { 2930 return rez; 2931 } 2932 2933 if (name === "afterClose" || !self.$refs) { 2934 $D.trigger(name + ".fb", args); 2935 } else { 2936 self.$refs.container.trigger(name + ".fb", args); 2937 } 2938 }, 2939 2940 // Update infobar values, navigation button states and reveal caption 2941 // ================================================================== 2942 2943 updateControls: function () { 2944 var self = this, 2945 current = self.current, 2946 index = current.index, 2947 $container = self.$refs.container, 2948 $caption = self.$refs.caption, 2949 caption = current.opts.caption; 2950 2951 // Recalculate content dimensions 2952 current.$slide.trigger("refresh"); 2953 2954 // Set caption 2955 if (caption && caption.length) { 2956 self.$caption = $caption; 2957 2958 $caption 2959 .children() 2960 .eq(0) 2961 .html(caption); 2962 } else { 2963 self.$caption = null; 2964 } 2965 2966 if (!self.hasHiddenControls && !self.isIdle) { 2967 self.showControls(); 2968 } 2969 2970 // Update info and navigation elements 2971 $container.find("[data-fancybox-count]").html(self.group.length); 2972 $container.find("[data-fancybox-index]").html(index + 1); 2973 2974 $container.find("[data-fancybox-prev]").prop("disabled", !current.opts.loop && index <= 0); 2975 $container.find("[data-fancybox-next]").prop("disabled", !current.opts.loop && index >= self.group.length - 1); 2976 2977 if (current.type === "image") { 2978 // Re-enable buttons; update download button source 2979 $container 2980 .find("[data-fancybox-zoom]") 2981 .show() 2982 .end() 2983 .find("[data-fancybox-download]") 2984 .attr("href", current.opts.image.src || current.src) 2985 .show(); 2986 } else if (current.opts.toolbar) { 2987 $container.find("[data-fancybox-download],[data-fancybox-zoom]").hide(); 2988 } 2989 2990 // Make sure focus is not on disabled button/element 2991 if ($(document.activeElement).is(":hidden,[disabled]")) { 2992 self.$refs.container.trigger("focus"); 2993 } 2994 }, 2995 2996 // Hide toolbar and caption 2997 // ======================== 2998 2999 hideControls: function (andCaption) { 3000 var self = this, 3001 arr = ["infobar", "toolbar", "nav"]; 3002 3003 if (andCaption || !self.current.opts.preventCaptionOverlap) { 3004 arr.push("caption"); 3005 } 3006 3007 this.$refs.container.removeClass( 3008 arr 3009 .map(function (i) { 3010 return "fancybox-show-" + i; 3011 }) 3012 .join(" ") 3013 ); 3014 3015 this.hasHiddenControls = true; 3016 }, 3017 3018 showControls: function () { 3019 var self = this, 3020 opts = self.current ? self.current.opts : self.opts, 3021 $container = self.$refs.container; 3022 3023 self.hasHiddenControls = false; 3024 self.idleSecondsCounter = 0; 3025 3026 $container 3027 .toggleClass("fancybox-show-toolbar", !!(opts.toolbar && opts.buttons)) 3028 .toggleClass("fancybox-show-infobar", !!(opts.infobar && self.group.length > 1)) 3029 .toggleClass("fancybox-show-caption", !!self.$caption) 3030 .toggleClass("fancybox-show-nav", !!(opts.arrows && self.group.length > 1)) 3031 .toggleClass("fancybox-is-modal", !!opts.modal); 3032 }, 3033 3034 // Toggle toolbar and caption 3035 // ========================== 3036 3037 toggleControls: function () { 3038 if (this.hasHiddenControls) { 3039 this.showControls(); 3040 } else { 3041 this.hideControls(); 3042 } 3043 } 3044 }); 3045 3046 $.fancybox = { 3047 version: "{fancybox-version}", 3048 defaults: defaults, 3049 3050 // Get current instance and execute a command. 3051 // 3052 // Examples of usage: 3053 // 3054 // $instance = $.fancybox.getInstance(); 3055 // $.fancybox.getInstance().jumpTo( 1 ); 3056 // $.fancybox.getInstance( 'jumpTo', 1 ); 3057 // $.fancybox.getInstance( function() { 3058 // console.info( this.currIndex ); 3059 // }); 3060 // ====================================================== 3061 3062 getInstance: function (command) { 3063 var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"), 3064 args = Array.prototype.slice.call(arguments, 1); 3065 3066 if (instance instanceof FancyBox) { 3067 if ($.type(command) === "string") { 3068 instance[command].apply(instance, args); 3069 } else if ($.type(command) === "function") { 3070 command.apply(instance, args); 3071 } 3072 3073 return instance; 3074 } 3075 3076 return false; 3077 }, 3078 3079 // Create new instance 3080 // =================== 3081 3082 open: function (items, opts, index) { 3083 return new FancyBox(items, opts, index); 3084 }, 3085 3086 // Close current or all instances 3087 // ============================== 3088 3089 close: function (all) { 3090 var instance = this.getInstance(); 3091 3092 if (instance) { 3093 instance.close(); 3094 3095 // Try to find and close next instance 3096 if (all === true) { 3097 this.close(all); 3098 } 3099 } 3100 }, 3101 3102 // Close all instances and unbind all events 3103 // ========================================= 3104 3105 destroy: function () { 3106 this.close(true); 3107 3108 $D.add("body").off("click.fb-start", "**"); 3109 }, 3110 3111 // Try to detect mobile devices 3112 // ============================ 3113 3114 isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), 3115 3116 // Detect if 'translate3d' support is available 3117 // ============================================ 3118 3119 use3d: (function () { 3120 var div = document.createElement("div"); 3121 3122 return ( 3123 window.getComputedStyle && 3124 window.getComputedStyle(div) && 3125 window.getComputedStyle(div).getPropertyValue("transform") && 3126 !(document.documentMode && document.documentMode < 11) 3127 ); 3128 })(), 3129 3130 // Helper function to get current visual state of an element 3131 // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] 3132 // ===================================================================== 3133 3134 getTranslate: function ($el) { 3135 var domRect; 3136 3137 if (!$el || !$el.length) { 3138 return false; 3139 } 3140 3141 domRect = $el[0].getBoundingClientRect(); 3142 3143 return { 3144 top: domRect.top || 0, 3145 left: domRect.left || 0, 3146 width: domRect.width, 3147 height: domRect.height, 3148 opacity: parseFloat($el.css("opacity")) 3149 }; 3150 }, 3151 3152 // Shortcut for setting "translate3d" properties for element 3153 // Can set be used to set opacity, too 3154 // ======================================================== 3155 3156 setTranslate: function ($el, props) { 3157 var str = "", 3158 css = {}; 3159 3160 if (!$el || !props) { 3161 return; 3162 } 3163 3164 if (props.left !== undefined || props.top !== undefined) { 3165 str = 3166 (props.left === undefined ? $el.position().left : props.left) + 3167 "px, " + 3168 (props.top === undefined ? $el.position().top : props.top) + 3169 "px"; 3170 3171 if (this.use3d) { 3172 str = "translate3d(" + str + ", 0px)"; 3173 } else { 3174 str = "translate(" + str + ")"; 3175 } 3176 } 3177 3178 if (props.scaleX !== undefined && props.scaleY !== undefined) { 3179 str += " scale(" + props.scaleX + ", " + props.scaleY + ")"; 3180 } else if (props.scaleX !== undefined) { 3181 str += " scaleX(" + props.scaleX + ")"; 3182 } 3183 3184 if (str.length) { 3185 css.transform = str; 3186 } 3187 3188 if (props.opacity !== undefined) { 3189 css.opacity = props.opacity; 3190 } 3191 3192 if (props.width !== undefined) { 3193 css.width = props.width; 3194 } 3195 3196 if (props.height !== undefined) { 3197 css.height = props.height; 3198 } 3199 3200 return $el.css(css); 3201 }, 3202 3203 // Simple CSS transition handler 3204 // ============================= 3205 3206 animate: function ($el, to, duration, callback, leaveAnimationName) { 3207 var self = this, 3208 from; 3209 3210 if ($.isFunction(duration)) { 3211 callback = duration; 3212 duration = null; 3213 } 3214 3215 self.stop($el); 3216 3217 from = self.getTranslate($el); 3218 3219 $el.on(transitionEnd, function (e) { 3220 // Skip events from child elements and z-index change 3221 if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == "z-index")) { 3222 return; 3223 } 3224 3225 self.stop($el); 3226 3227 if ($.isNumeric(duration)) { 3228 $el.css("transition-duration", ""); 3229 } 3230 3231 if ($.isPlainObject(to)) { 3232 if (to.scaleX !== undefined && to.scaleY !== undefined) { 3233 self.setTranslate($el, { 3234 top: to.top, 3235 left: to.left, 3236 width: from.width * to.scaleX, 3237 height: from.height * to.scaleY, 3238 scaleX: 1, 3239 scaleY: 1 3240 }); 3241 } 3242 } else if (leaveAnimationName !== true) { 3243 $el.removeClass(to); 3244 } 3245 3246 if ($.isFunction(callback)) { 3247 callback(e); 3248 } 3249 }); 3250 3251 if ($.isNumeric(duration)) { 3252 $el.css("transition-duration", duration + "ms"); 3253 } 3254 3255 // Start animation by changing CSS properties or class name 3256 if ($.isPlainObject(to)) { 3257 if (to.scaleX !== undefined && to.scaleY !== undefined) { 3258 delete to.width; 3259 delete to.height; 3260 3261 if ($el.parent().hasClass("fancybox-slide--image")) { 3262 $el.parent().addClass("fancybox-is-scaling"); 3263 } 3264 } 3265 3266 $.fancybox.setTranslate($el, to); 3267 } else { 3268 $el.addClass(to); 3269 } 3270 3271 // Make sure that `transitionend` callback gets fired 3272 $el.data( 3273 "timer", 3274 setTimeout(function () { 3275 $el.trigger(transitionEnd); 3276 }, duration + 33) 3277 ); 3278 }, 3279 3280 stop: function ($el, callCallback) { 3281 if ($el && $el.length) { 3282 clearTimeout($el.data("timer")); 3283 3284 if (callCallback) { 3285 $el.trigger(transitionEnd); 3286 } 3287 3288 $el.off(transitionEnd).css("transition-duration", ""); 3289 3290 $el.parent().removeClass("fancybox-is-scaling"); 3291 } 3292 } 3293 }; 3294 3295 // Default click handler for "fancyboxed" links 3296 // ============================================ 3297 3298 function _run(e, opts) { 3299 var items = [], 3300 index = 0, 3301 $target, 3302 value, 3303 instance; 3304 3305 // Avoid opening multiple times 3306 if (e && e.isDefaultPrevented()) { 3307 return; 3308 } 3309 3310 e.preventDefault(); 3311 3312 opts = opts || {}; 3313 3314 if (e && e.data) { 3315 opts = mergeOpts(e.data.options, opts); 3316 } 3317 3318 $target = opts.$target || $(e.currentTarget).trigger("blur"); 3319 instance = $.fancybox.getInstance(); 3320 3321 if (instance && instance.$trigger && instance.$trigger.is($target)) { 3322 return; 3323 } 3324 3325 if (opts.selector) { 3326 items = $(opts.selector); 3327 } else { 3328 // Get all related items and find index for clicked one 3329 value = $target.attr("data-fancybox") || ""; 3330 3331 if (value) { 3332 items = e.data ? e.data.items : []; 3333 items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]'); 3334 } else { 3335 items = [$target]; 3336 } 3337 } 3338 3339 index = $(items).index($target); 3340 3341 // Sometimes current item can not be found 3342 if (index < 0) { 3343 index = 0; 3344 } 3345 3346 instance = $.fancybox.open(items, opts, index); 3347 3348 // Save last active element 3349 instance.$trigger = $target; 3350 } 3351 3352 // Create a jQuery plugin 3353 // ====================== 3354 3355 $.fn.fancybox = function (options) { 3356 var selector; 3357 3358 options = options || {}; 3359 selector = options.selector || false; 3360 3361 if (selector) { 3362 // Use body element instead of document so it executes first 3363 $("body") 3364 .off("click.fb-start", selector) 3365 .on("click.fb-start", selector, { 3366 options: options 3367 }, _run); 3368 } else { 3369 this.off("click.fb-start").on( 3370 "click.fb-start", { 3371 items: this, 3372 options: options 3373 }, 3374 _run 3375 ); 3376 } 3377 3378 return this; 3379 }; 3380 3381 // Self initializing plugin for all elements having `data-fancybox` attribute 3382 // ========================================================================== 3383 3384 $D.on("click.fb-start", "[data-fancybox]", _run); 3385 3386 // Enable "trigger elements" 3387 // ========================= 3388 3389 $D.on("click.fb-start", "[data-fancybox-trigger]", function (e) { 3390 $('[data-fancybox="' + $(this).attr("data-fancybox-trigger") + '"]') 3391 .eq($(this).attr("data-fancybox-index") || 0) 3392 .trigger("click.fb-start", { 3393 $trigger: $(this) 3394 }); 3395 }); 3396 3397 // Track focus event for better accessibility styling 3398 // ================================================== 3399 (function () { 3400 var buttonStr = ".fancybox-button", 3401 focusStr = "fancybox-focus", 3402 $pressed = null; 3403 3404 $D.on("mousedown mouseup focus blur", buttonStr, function (e) { 3405 switch (e.type) { 3406 case "mousedown": 3407 $pressed = $(this); 3408 break; 3409 case "mouseup": 3410 $pressed = null; 3411 break; 3412 case "focusin": 3413 $(buttonStr).removeClass(focusStr); 3414 3415 if (!$(this).is($pressed) && !$(this).is("[disabled]")) { 3416 $(this).addClass(focusStr); 3417 } 3418 break; 3419 case "focusout": 3420 $(buttonStr).removeClass(focusStr); 3421 break; 3422 } 3423 }); 3424 })(); 3425})(window, document, jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.