PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.37
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.37
3.37 3.36 3.35 3.34 3.33 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.26 2.27 2.28 2.29 2.30 2.31 2.32 2.33 2.34 2.40 2.41 2.42 2.43 All 142 releases
photonic / include / ext / fancybox3 / fancybox3.js

fancybox3.js in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.37, at include/ext/fancybox3/fancybox3.js

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