PluginProbe
Property Hive / 2.2.4
Property Hive v2.2.4
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
propertyhive / assets / js / fancybox / jquery.fancybox.js

jquery.fancybox.js in Property Hive 2.2.4, at assets/js/fancybox/jquery.fancybox.js

5,708 lines 158.9 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]+)((\/|\?h=)([a-z0-9]+))?(.*)?/,
3467 //matcher: /^.+vimeo.com\/(\/)?([\d]+)(\/)?([a-zA-Z0-9]+)?(.*)?/,
3468 matcher: /^.+vimeo.com\/(\/)?([\d]+)(.*)?/,
3469 //matcher: /^.+vimeo.com\/(?:\/)?([\d]+)(?:\/)?([a-zA-Z0-9]+)?(.*)?/,
3470 params: {
3471 autoplay: 1,
3472 hd: 1,
3473 show_title: 1,
3474 show_byline: 1,
3475 show_portrait: 0,
3476 fullscreen: 1
3477 },
3478 paramPlace: 3,
3479 type: "iframe",
3480 url: "//player.vimeo.com/video/$2"
3481 },
3482
3483 instagram: {
3484 matcher: /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,
3485 type: "image",
3486 url: "//$1/p/$2/media/?size=l"
3487 },
3488
3489 // Examples:
3490 // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16
3491 // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z
3492 // https://www.google.com/maps/@52.2111123,2.9237542,6.61z?hl=en
3493 // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572
3494 gmap_place: {
3495 matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,
3496 type: "iframe",
3497 url: function (rez) {
3498 return (
3499 "//maps.google." +
3500 rez[2] +
3501 "/?ll=" +
3502 (rez[9] ? rez[9] + "&z=" + Math.floor(rez[10]) + (rez[12] ? rez[12].replace(/^\//, "&") : "") : rez[12] + "").replace(/\?/, "&") +
3503 "&output=" +
3504 (rez[12] && rez[12].indexOf("layer=c") > 0 ? "svembed" : "embed")
3505 );
3506 }
3507 },
3508
3509 // Examples:
3510 // https://www.google.com/maps/search/Empire+State+Building/
3511 // https://www.google.com/maps/search/?api=1&query=centurylink+field
3512 // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393
3513 gmap_search: {
3514 matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,
3515 type: "iframe",
3516 url: function (rez) {
3517 return "//maps.google." + rez[2] + "/maps?q=" + rez[5].replace("query=", "q=").replace("api=1", "") + "&output=embed";
3518 }
3519 }
3520 };
3521
3522 // Formats matching url to final form
3523 var format = function (url, rez, params) {
3524 if (!url) {
3525 return;
3526 }
3527
3528 params = params || "";
3529
3530 if ($.type(params) === "object") {
3531 params = $.param(params, true);
3532 }
3533
3534 $.each(rez, function (key, value) {
3535 url = url.replace("$" + key, value || "");
3536 });
3537
3538 if (params.length) {
3539 url += (url.indexOf("?") > 0 ? "&" : "?") + params;
3540 }
3541
3542 return url;
3543 };
3544
3545 $(document).on("objectNeedsType.fb", function (e, instance, item) {
3546 var url = item.src || "",
3547 type = false,
3548 media,
3549 thumb,
3550 rez,
3551 params,
3552 urlParams,
3553 paramObj,
3554 provider;
3555
3556 media = $.extend(true, {}, defaults, item.opts.media);
3557
3558 // Look for any matching media type
3559 $.each(media, function (providerName, providerOpts) {
3560
3561 var additional_params = new Array();
3562 if ( url.indexOf('player.vimeo.co') != -1 )
3563 {
3564 type = 'iframe';
3565
3566 var explode_url = url.split("?");
3567 var new_params = new Array();
3568 var explode_params = new Array();
3569 if (explode_url.length > 1) {
3570 explode_params = explode_url[1].split("&");
3571 }
3572
3573 var default_params = {
3574 autoplay: 1,
3575 hd: 1,
3576 show_title: 1,
3577 show_byline: 1,
3578 show_portrait: 0,
3579 fullscreen: 1
3580 };
3581
3582 url = explode_url[0] + '?';
3583 $.each(default_params, function (paramName, paramValue) {
3584 url += paramName + '=' + paramValue + '&';
3585 });
3586 for ( var m = 0; m < explode_params.length; ++m)
3587 {
3588 var explode_param = explode_params[m].split("=");
3589 if ( default_params[explode_param[0]] != 'undefined' )
3590 {
3591 url += explode_params[m] + '&';
3592 }
3593 }
3594
3595
3596 return url;
3597 }
3598 else
3599 {
3600 if ( url.indexOf('vimeo.co') != -1 )
3601 {
3602 var explode_url = url.split("?");
3603 explode_url = explode_url[0].replace(/\/$/, '');
3604 explode_url = explode_url.split("/");
3605 if ( $.isNumeric(explode_url[explode_url.length-2]) && explode_url[explode_url.length-1] != '' )
3606 {
3607 additional_params.push('h=' + explode_url[explode_url.length-1]);
3608 }
3609 }
3610 }
3611
3612 if (url.includes('youtube.com/shorts/'))
3613 {
3614 var videoId = url.split('/shorts/')[1].split('?')[0];
3615 url = 'https://youtube.com/watch?v=' + videoId;
3616 }
3617
3618 rez = url.match(providerOpts.matcher);
3619
3620 if (!rez) {
3621 return;
3622 }
3623
3624
3625
3626
3627 type = providerOpts.type;
3628 provider = providerName;
3629 paramObj = {};
3630
3631 if (providerOpts.paramPlace && rez[providerOpts.paramPlace]) {
3632 urlParams = rez[providerOpts.paramPlace];
3633
3634 if (urlParams[0] == "?") {
3635 urlParams = urlParams.substring(1);
3636 }
3637
3638 urlParams = urlParams.split("&");
3639
3640 for (var m = 0; m < urlParams.length; ++m) {
3641 var p = urlParams[m].split("=", 2);
3642
3643 if (p.length == 2) {
3644 paramObj[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
3645 }
3646 }
3647 }
3648
3649 params = $.extend(true, {}, providerOpts.params, item.opts[providerName], paramObj);
3650
3651 url =
3652 $.type(providerOpts.url) === "function" ? providerOpts.url.call(this, rez, params, item) : format(providerOpts.url, rez, params);
3653
3654 thumb =
3655 $.type(providerOpts.thumb) === "function" ? providerOpts.thumb.call(this, rez, params, item) : format(providerOpts.thumb, rez);
3656
3657 if (providerName === "youtube") {
3658 url = url.replace(/&t=((\d+)m)?(\d+)s/, function (match, p1, m, s) {
3659 return "&start=" + ((m ? parseInt(m, 10) * 60 : 0) + parseInt(s, 10));
3660 });
3661 } else if (providerName === "vimeo") {
3662 url = url.replace("&%23", "#");
3663 }
3664
3665 if (additional_params.length > 0 )
3666 {
3667 if ( url.indexOf('?') == -1 )
3668 {
3669 url += '?';
3670 }
3671 for (var i = 0; i < additional_params.length; ++i)
3672 {
3673 url += '&' + additional_params[0];
3674 }
3675 }
3676
3677 return false;
3678 });
3679
3680 // If it is found, then change content type and update the url
3681
3682 if (type) {
3683 if (!item.opts.thumb && !(item.opts.$thumb && item.opts.$thumb.length)) {
3684 item.opts.thumb = thumb;
3685 }
3686
3687 if (type === "iframe") {
3688 item.opts = $.extend(true, item.opts, {
3689 iframe: {
3690 preload: false,
3691 attr: {
3692 scrolling: "no"
3693 }
3694 }
3695 });
3696 }
3697
3698 $.extend(item, {
3699 type: type,
3700 src: url,
3701 origSrc: item.src,
3702 contentSource: provider,
3703 contentType: type === "image" ? "image" : provider == "gmap_place" || provider == "gmap_search" ? "map" : "video"
3704 });
3705 } else if (url) {
3706 item.type = item.opts.defaultType;
3707 }
3708 });
3709
3710 // Load YouTube/Video API on request to detect when video finished playing
3711 var VideoAPILoader = {
3712 youtube: {
3713 src: "https://www.youtube.com/iframe_api",
3714 class: "YT",
3715 loading: false,
3716 loaded: false
3717 },
3718
3719 vimeo: {
3720 src: "https://player.vimeo.com/api/player.js",
3721 class: "Vimeo",
3722 loading: false,
3723 loaded: false
3724 },
3725
3726 load: function (vendor) {
3727 var _this = this,
3728 script;
3729
3730 if (this[vendor].loaded) {
3731 setTimeout(function () {
3732 _this.done(vendor);
3733 });
3734 return;
3735 }
3736
3737 if (this[vendor].loading) {
3738 return;
3739 }
3740
3741 this[vendor].loading = true;
3742
3743 script = document.createElement("script");
3744 script.type = "text/javascript";
3745 script.src = this[vendor].src;
3746
3747 if (vendor === "youtube") {
3748 window.onYouTubeIframeAPIReady = function () {
3749 _this[vendor].loaded = true;
3750 _this.done(vendor);
3751 };
3752 } else {
3753 script.onload = function () {
3754 _this[vendor].loaded = true;
3755 _this.done(vendor);
3756 };
3757 }
3758
3759 document.body.appendChild(script);
3760 },
3761 done: function (vendor) {
3762 var instance, $el, player;
3763
3764 if (vendor === "youtube") {
3765 delete window.onYouTubeIframeAPIReady;
3766 }
3767
3768 instance = $.fancybox.getInstance();
3769
3770 if (instance) {
3771 $el = instance.current.$content.find("iframe");
3772
3773 if (vendor === "youtube" && YT !== undefined && YT) {
3774 player = new YT.Player($el.attr("id"), {
3775 events: {
3776 onStateChange: function (e) {
3777 if (e.data == 0) {
3778 instance.next();
3779 }
3780 }
3781 }
3782 });
3783 } else if (vendor === "vimeo" && Vimeo !== undefined && Vimeo) {
3784 player = new Vimeo.Player($el);
3785
3786 player.on("ended", function () {
3787 instance.next();
3788 });
3789 }
3790 }
3791 }
3792 };
3793
3794 $(document).on({
3795 "afterShow.fb": function (e, instance, current) {
3796 if (instance.group.length > 1 && (current.contentSource === "youtube" || current.contentSource === "vimeo")) {
3797 VideoAPILoader.load(current.contentSource);
3798 }
3799 }
3800 });
3801 })(jQuery);
3802 // ==========================================================================
3803 //
3804 // Guestures
3805 // Adds touch guestures, handles click and tap events
3806 //
3807 // ==========================================================================
3808 (function (window, document, $) {
3809 "use strict";
3810
3811 var requestAFrame = (function () {
3812 return (
3813 window.requestAnimationFrame ||
3814 window.webkitRequestAnimationFrame ||
3815 window.mozRequestAnimationFrame ||
3816 window.oRequestAnimationFrame ||
3817 // if all else fails, use setTimeout
3818 function (callback) {
3819 return window.setTimeout(callback, 1000 / 60);
3820 }
3821 );
3822 })();
3823
3824 var cancelAFrame = (function () {
3825 return (
3826 window.cancelAnimationFrame ||
3827 window.webkitCancelAnimationFrame ||
3828 window.mozCancelAnimationFrame ||
3829 window.oCancelAnimationFrame ||
3830 function (id) {
3831 window.clearTimeout(id);
3832 }
3833 );
3834 })();
3835
3836 var getPointerXY = function (e) {
3837 var result = [];
3838
3839 e = e.originalEvent || e || window.e;
3840 e = e.touches && e.touches.length ? e.touches : e.changedTouches && e.changedTouches.length ? e.changedTouches : [e];
3841
3842 for (var key in e) {
3843 if (e[key].pageX) {
3844 result.push({
3845 x: e[key].pageX,
3846 y: e[key].pageY
3847 });
3848 } else if (e[key].clientX) {
3849 result.push({
3850 x: e[key].clientX,
3851 y: e[key].clientY
3852 });
3853 }
3854 }
3855
3856 return result;
3857 };
3858
3859 var distance = function (point2, point1, what) {
3860 if (!point1 || !point2) {
3861 return 0;
3862 }
3863
3864 if (what === "x") {
3865 return point2.x - point1.x;
3866 } else if (what === "y") {
3867 return point2.y - point1.y;
3868 }
3869
3870 return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
3871 };
3872
3873 var isClickable = function ($el) {
3874 if (
3875 $el.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe') ||
3876 $.isFunction($el.get(0).onclick) ||
3877 $el.data("selectable")
3878 ) {
3879 return true;
3880 }
3881
3882 // Check for attributes like data-fancybox-next or data-fancybox-close
3883 for (var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++) {
3884 if (atts[i].nodeName.substr(0, 14) === "data-fancybox-") {
3885 return true;
3886 }
3887 }
3888
3889 return false;
3890 };
3891
3892 var hasScrollbars = function (el) {
3893 var overflowY = window.getComputedStyle(el)["overflow-y"],
3894 overflowX = window.getComputedStyle(el)["overflow-x"],
3895 vertical = (overflowY === "scroll" || overflowY === "auto") && el.scrollHeight > el.clientHeight,
3896 horizontal = (overflowX === "scroll" || overflowX === "auto") && el.scrollWidth > el.clientWidth;
3897
3898 return vertical || horizontal;
3899 };
3900
3901 var isScrollable = function ($el) {
3902 var rez = false;
3903
3904 while (true) {
3905 rez = hasScrollbars($el.get(0));
3906
3907 if (rez) {
3908 break;
3909 }
3910
3911 $el = $el.parent();
3912
3913 if (!$el.length || $el.hasClass("fancybox-stage") || $el.is("body")) {
3914 break;
3915 }
3916 }
3917
3918 return rez;
3919 };
3920
3921 var Guestures = function (instance) {
3922 var self = this;
3923
3924 self.instance = instance;
3925
3926 self.$bg = instance.$refs.bg;
3927 self.$stage = instance.$refs.stage;
3928 self.$container = instance.$refs.container;
3929
3930 self.destroy();
3931
3932 self.$container.on("touchstart.fb.touch mousedown.fb.touch", $.proxy(self, "ontouchstart"));
3933 };
3934
3935 Guestures.prototype.destroy = function () {
3936 var self = this;
3937
3938 self.$container.off(".fb.touch");
3939
3940 $(document).off(".fb.touch");
3941
3942 if (self.requestId) {
3943 cancelAFrame(self.requestId);
3944 self.requestId = null;
3945 }
3946
3947 if (self.tapped) {
3948 clearTimeout(self.tapped);
3949 self.tapped = null;
3950 }
3951 };
3952
3953 Guestures.prototype.ontouchstart = function (e) {
3954 var self = this,
3955 $target = $(e.target),
3956 instance = self.instance,
3957 current = instance.current,
3958 $slide = current.$slide,
3959 $content = current.$content,
3960 isTouchDevice = e.type == "touchstart";
3961
3962 // Do not respond to both (touch and mouse) events
3963 if (isTouchDevice) {
3964 self.$container.off("mousedown.fb.touch");
3965 }
3966
3967 // Ignore right click
3968 if (e.originalEvent && e.originalEvent.button == 2) {
3969 return;
3970 }
3971
3972 // Ignore taping on links, buttons, input elements
3973 if (!$slide.length || !$target.length || isClickable($target) || isClickable($target.parent())) {
3974 return;
3975 }
3976 // Ignore clicks on the scrollbar
3977 if (!$target.is("img") && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left) {
3978 return;
3979 }
3980
3981 // Ignore clicks while zooming or closing
3982 if (!current || instance.isAnimating || current.$slide.hasClass("fancybox-animated")) {
3983 e.stopPropagation();
3984 e.preventDefault();
3985
3986 return;
3987 }
3988
3989 self.realPoints = self.startPoints = getPointerXY(e);
3990
3991 if (!self.startPoints.length) {
3992 return;
3993 }
3994
3995 // Allow other scripts to catch touch event if "touch" is set to false
3996 if (current.touch) {
3997 e.stopPropagation();
3998 }
3999
4000 self.startEvent = e;
4001
4002 self.canTap = true;
4003 self.$target = $target;
4004 self.$content = $content;
4005 self.opts = current.opts.touch;
4006
4007 self.isPanning = false;
4008 self.isSwiping = false;
4009 self.isZooming = false;
4010 self.isScrolling = false;
4011 self.canPan = instance.canPan();
4012
4013 self.startTime = new Date().getTime();
4014 self.distanceX = self.distanceY = self.distance = 0;
4015
4016 self.canvasWidth = Math.round($slide[0].clientWidth);
4017 self.canvasHeight = Math.round($slide[0].clientHeight);
4018
4019 self.contentLastPos = null;
4020 self.contentStartPos = $.fancybox.getTranslate(self.$content) || {
4021 top: 0,
4022 left: 0
4023 };
4024 self.sliderStartPos = $.fancybox.getTranslate($slide);
4025
4026 // Since position will be absolute, but we need to make it relative to the stage
4027 self.stagePos = $.fancybox.getTranslate(instance.$refs.stage);
4028
4029 self.sliderStartPos.top -= self.stagePos.top;
4030 self.sliderStartPos.left -= self.stagePos.left;
4031
4032 self.contentStartPos.top -= self.stagePos.top;
4033 self.contentStartPos.left -= self.stagePos.left;
4034
4035 $(document)
4036 .off(".fb.touch")
4037 .on(isTouchDevice ? "touchend.fb.touch touchcancel.fb.touch" : "mouseup.fb.touch mouseleave.fb.touch", $.proxy(self, "ontouchend"))
4038 .on(isTouchDevice ? "touchmove.fb.touch" : "mousemove.fb.touch", $.proxy(self, "ontouchmove"));
4039
4040 if ($.fancybox.isMobile) {
4041 document.addEventListener("scroll", self.onscroll, true);
4042 }
4043
4044 // Skip if clicked outside the sliding area
4045 if (!(self.opts || self.canPan) || !($target.is(self.$stage) || self.$stage.find($target).length)) {
4046 if ($target.is(".fancybox-image")) {
4047 e.preventDefault();
4048 }
4049
4050 if (!($.fancybox.isMobile && $target.parents(".fancybox-caption").length)) {
4051 return;
4052 }
4053 }
4054
4055 self.isScrollable = isScrollable($target) || isScrollable($target.parent());
4056
4057 // Check if element is scrollable and try to prevent default behavior (scrolling)
4058 if (!($.fancybox.isMobile && self.isScrollable)) {
4059 e.preventDefault();
4060 }
4061
4062 // One finger or mouse click - swipe or pan an image
4063 if (self.startPoints.length === 1 || current.hasError) {
4064 if (self.canPan) {
4065 $.fancybox.stop(self.$content);
4066
4067 self.isPanning = true;
4068 } else {
4069 self.isSwiping = true;
4070 }
4071
4072 self.$container.addClass("fancybox-is-grabbing");
4073 }
4074
4075 // Two fingers - zoom image
4076 if (self.startPoints.length === 2 && current.type === "image" && (current.isLoaded || current.$ghost)) {
4077 self.canTap = false;
4078 self.isSwiping = false;
4079 self.isPanning = false;
4080
4081 self.isZooming = true;
4082
4083 $.fancybox.stop(self.$content);
4084
4085 self.centerPointStartX = (self.startPoints[0].x + self.startPoints[1].x) * 0.5 - $(window).scrollLeft();
4086 self.centerPointStartY = (self.startPoints[0].y + self.startPoints[1].y) * 0.5 - $(window).scrollTop();
4087
4088 self.percentageOfImageAtPinchPointX = (self.centerPointStartX - self.contentStartPos.left) / self.contentStartPos.width;
4089 self.percentageOfImageAtPinchPointY = (self.centerPointStartY - self.contentStartPos.top) / self.contentStartPos.height;
4090
4091 self.startDistanceBetweenFingers = distance(self.startPoints[0], self.startPoints[1]);
4092 }
4093 };
4094
4095 Guestures.prototype.onscroll = function (e) {
4096 var self = this;
4097
4098 self.isScrolling = true;
4099
4100 document.removeEventListener("scroll", self.onscroll, true);
4101 };
4102
4103 Guestures.prototype.ontouchmove = function (e) {
4104 var self = this;
4105
4106 // Make sure user has not released over iframe or disabled element
4107 if (e.originalEvent.buttons !== undefined && e.originalEvent.buttons === 0) {
4108 self.ontouchend(e);
4109 return;
4110 }
4111
4112 if (self.isScrolling) {
4113 self.canTap = false;
4114 return;
4115 }
4116
4117 self.newPoints = getPointerXY(e);
4118
4119 if (!(self.opts || self.canPan) || !self.newPoints.length || !self.newPoints.length) {
4120 return;
4121 }
4122
4123 if (!(self.isSwiping && self.isSwiping === true)) {
4124 e.preventDefault();
4125 }
4126
4127 self.distanceX = distance(self.newPoints[0], self.startPoints[0], "x");
4128 self.distanceY = distance(self.newPoints[0], self.startPoints[0], "y");
4129
4130 self.distance = distance(self.newPoints[0], self.startPoints[0]);
4131
4132 // Skip false ontouchmove events (Chrome)
4133 if (self.distance > 0) {
4134 if (self.isSwiping) {
4135 self.onSwipe(e);
4136 } else if (self.isPanning) {
4137 self.onPan();
4138 } else if (self.isZooming) {
4139 self.onZoom();
4140 }
4141 }
4142 };
4143
4144 Guestures.prototype.onSwipe = function (e) {
4145 var self = this,
4146 instance = self.instance,
4147 swiping = self.isSwiping,
4148 left = self.sliderStartPos.left || 0,
4149 angle;
4150
4151 // If direction is not yet determined
4152 if (swiping === true) {
4153 // We need at least 10px distance to correctly calculate an angle
4154 if (Math.abs(self.distance) > 10) {
4155 self.canTap = false;
4156
4157 if (instance.group.length < 2 && self.opts.vertical) {
4158 self.isSwiping = "y";
4159 } else if (instance.isDragging || self.opts.vertical === false || (self.opts.vertical === "auto" && $(window).width() > 800)) {
4160 self.isSwiping = "x";
4161 } else {
4162 angle = Math.abs((Math.atan2(self.distanceY, self.distanceX) * 180) / Math.PI);
4163
4164 self.isSwiping = angle > 45 && angle < 135 ? "y" : "x";
4165 }
4166
4167 if (self.isSwiping === "y" && $.fancybox.isMobile && self.isScrollable) {
4168 self.isScrolling = true;
4169
4170 return;
4171 }
4172
4173 instance.isDragging = self.isSwiping;
4174
4175 // Reset points to avoid jumping, because we dropped first swipes to calculate the angle
4176 self.startPoints = self.newPoints;
4177
4178 $.each(instance.slides, function (index, slide) {
4179 var slidePos, stagePos;
4180
4181 $.fancybox.stop(slide.$slide);
4182
4183 slidePos = $.fancybox.getTranslate(slide.$slide);
4184 stagePos = $.fancybox.getTranslate(instance.$refs.stage);
4185
4186 slide.$slide
4187 .css({
4188 transform: "",
4189 opacity: "",
4190 "transition-duration": ""
4191 })
4192 .removeClass("fancybox-animated")
4193 .removeClass(function (index, className) {
4194 return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" ");
4195 });
4196
4197 if (slide.pos === instance.current.pos) {
4198 self.sliderStartPos.top = slidePos.top - stagePos.top;
4199 self.sliderStartPos.left = slidePos.left - stagePos.left;
4200 }
4201
4202 $.fancybox.setTranslate(slide.$slide, {
4203 top: slidePos.top - stagePos.top,
4204 left: slidePos.left - stagePos.left
4205 });
4206 });
4207
4208 // Stop slideshow
4209 if (instance.SlideShow && instance.SlideShow.isActive) {
4210 instance.SlideShow.stop();
4211 }
4212 }
4213
4214 return;
4215 }
4216
4217 // Sticky edges
4218 if (swiping == "x") {
4219 if (
4220 self.distanceX > 0 &&
4221 (self.instance.group.length < 2 || (self.instance.current.index === 0 && !self.instance.current.opts.loop))
4222 ) {
4223 left = left + Math.pow(self.distanceX, 0.8);
4224 } else if (
4225 self.distanceX < 0 &&
4226 (self.instance.group.length < 2 ||
4227 (self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop))
4228 ) {
4229 left = left - Math.pow(-self.distanceX, 0.8);
4230 } else {
4231 left = left + self.distanceX;
4232 }
4233 }
4234
4235 self.sliderLastPos = {
4236 top: swiping == "x" ? 0 : self.sliderStartPos.top + self.distanceY,
4237 left: left
4238 };
4239
4240 if (self.requestId) {
4241 cancelAFrame(self.requestId);
4242
4243 self.requestId = null;
4244 }
4245
4246 self.requestId = requestAFrame(function () {
4247 if (self.sliderLastPos) {
4248 $.each(self.instance.slides, function (index, slide) {
4249 var pos = slide.pos - self.instance.currPos;
4250
4251 $.fancybox.setTranslate(slide.$slide, {
4252 top: self.sliderLastPos.top,
4253 left: self.sliderLastPos.left + pos * self.canvasWidth + pos * slide.opts.gutter
4254 });
4255 });
4256
4257 self.$container.addClass("fancybox-is-sliding");
4258 }
4259 });
4260 };
4261
4262 Guestures.prototype.onPan = function () {
4263 var self = this;
4264
4265 // Prevent accidental movement (sometimes, when tapping casually, finger can move a bit)
4266 if (distance(self.newPoints[0], self.realPoints[0]) < ($.fancybox.isMobile ? 10 : 5)) {
4267 self.startPoints = self.newPoints;
4268 return;
4269 }
4270
4271 self.canTap = false;
4272
4273 self.contentLastPos = self.limitMovement();
4274
4275 if (self.requestId) {
4276 cancelAFrame(self.requestId);
4277 }
4278
4279 self.requestId = requestAFrame(function () {
4280 $.fancybox.setTranslate(self.$content, self.contentLastPos);
4281 });
4282 };
4283
4284 // Make panning sticky to the edges
4285 Guestures.prototype.limitMovement = function () {
4286 var self = this;
4287
4288 var canvasWidth = self.canvasWidth;
4289 var canvasHeight = self.canvasHeight;
4290
4291 var distanceX = self.distanceX;
4292 var distanceY = self.distanceY;
4293
4294 var contentStartPos = self.contentStartPos;
4295
4296 var currentOffsetX = contentStartPos.left;
4297 var currentOffsetY = contentStartPos.top;
4298
4299 var currentWidth = contentStartPos.width;
4300 var currentHeight = contentStartPos.height;
4301
4302 var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY, newOffsetX, newOffsetY;
4303
4304 if (currentWidth > canvasWidth) {
4305 newOffsetX = currentOffsetX + distanceX;
4306 } else {
4307 newOffsetX = currentOffsetX;
4308 }
4309
4310 newOffsetY = currentOffsetY + distanceY;
4311
4312 // Slow down proportionally to traveled distance
4313 minTranslateX = Math.max(0, canvasWidth * 0.5 - currentWidth * 0.5);
4314 minTranslateY = Math.max(0, canvasHeight * 0.5 - currentHeight * 0.5);
4315
4316 maxTranslateX = Math.min(canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5);
4317 maxTranslateY = Math.min(canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5);
4318
4319 // ->
4320 if (distanceX > 0 && newOffsetX > minTranslateX) {
4321 newOffsetX = minTranslateX - 1 + Math.pow(-minTranslateX + currentOffsetX + distanceX, 0.8) || 0;
4322 }
4323
4324 // <-
4325 if (distanceX < 0 && newOffsetX < maxTranslateX) {
4326 newOffsetX = maxTranslateX + 1 - Math.pow(maxTranslateX - currentOffsetX - distanceX, 0.8) || 0;
4327 }
4328
4329 // \/
4330 if (distanceY > 0 && newOffsetY > minTranslateY) {
4331 newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0;
4332 }
4333
4334 // /\
4335 if (distanceY < 0 && newOffsetY < maxTranslateY) {
4336 newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0;
4337 }
4338
4339 return {
4340 top: newOffsetY,
4341 left: newOffsetX
4342 };
4343 };
4344
4345 Guestures.prototype.limitPosition = function (newOffsetX, newOffsetY, newWidth, newHeight) {
4346 var self = this;
4347
4348 var canvasWidth = self.canvasWidth;
4349 var canvasHeight = self.canvasHeight;
4350
4351 if (newWidth > canvasWidth) {
4352 newOffsetX = newOffsetX > 0 ? 0 : newOffsetX;
4353 newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX;
4354 } else {
4355 // Center horizontally
4356 newOffsetX = Math.max(0, canvasWidth / 2 - newWidth / 2);
4357 }
4358
4359 if (newHeight > canvasHeight) {
4360 newOffsetY = newOffsetY > 0 ? 0 : newOffsetY;
4361 newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY;
4362 } else {
4363 // Center vertically
4364 newOffsetY = Math.max(0, canvasHeight / 2 - newHeight / 2);
4365 }
4366
4367 return {
4368 top: newOffsetY,
4369 left: newOffsetX
4370 };
4371 };
4372
4373 Guestures.prototype.onZoom = function () {
4374 var self = this;
4375
4376 // Calculate current distance between points to get pinch ratio and new width and height
4377 var contentStartPos = self.contentStartPos;
4378
4379 var currentWidth = contentStartPos.width;
4380 var currentHeight = contentStartPos.height;
4381
4382 var currentOffsetX = contentStartPos.left;
4383 var currentOffsetY = contentStartPos.top;
4384
4385 var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]);
4386
4387 var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers;
4388
4389 var newWidth = Math.floor(currentWidth * pinchRatio);
4390 var newHeight = Math.floor(currentHeight * pinchRatio);
4391
4392 // This is the translation due to pinch-zooming
4393 var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX;
4394 var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY;
4395
4396 // Point between the two touches
4397 var centerPointEndX = (self.newPoints[0].x + self.newPoints[1].x) / 2 - $(window).scrollLeft();
4398 var centerPointEndY = (self.newPoints[0].y + self.newPoints[1].y) / 2 - $(window).scrollTop();
4399
4400 // And this is the translation due to translation of the centerpoint
4401 // between the two fingers
4402 var translateFromTranslatingX = centerPointEndX - self.centerPointStartX;
4403 var translateFromTranslatingY = centerPointEndY - self.centerPointStartY;
4404
4405 // The new offset is the old/current one plus the total translation
4406 var newOffsetX = currentOffsetX + (translateFromZoomingX + translateFromTranslatingX);
4407 var newOffsetY = currentOffsetY + (translateFromZoomingY + translateFromTranslatingY);
4408
4409 var newPos = {
4410 top: newOffsetY,
4411 left: newOffsetX,
4412 scaleX: pinchRatio,
4413 scaleY: pinchRatio
4414 };
4415
4416 self.canTap = false;
4417
4418 self.newWidth = newWidth;
4419 self.newHeight = newHeight;
4420
4421 self.contentLastPos = newPos;
4422
4423 if (self.requestId) {
4424 cancelAFrame(self.requestId);
4425 }
4426
4427 self.requestId = requestAFrame(function () {
4428 $.fancybox.setTranslate(self.$content, self.contentLastPos);
4429 });
4430 };
4431
4432 Guestures.prototype.ontouchend = function (e) {
4433 var self = this;
4434
4435 var swiping = self.isSwiping;
4436 var panning = self.isPanning;
4437 var zooming = self.isZooming;
4438 var scrolling = self.isScrolling;
4439
4440 self.endPoints = getPointerXY(e);
4441 self.dMs = Math.max(new Date().getTime() - self.startTime, 1);
4442
4443 self.$container.removeClass("fancybox-is-grabbing");
4444
4445 $(document).off(".fb.touch");
4446
4447 document.removeEventListener("scroll", self.onscroll, true);
4448
4449 if (self.requestId) {
4450 cancelAFrame(self.requestId);
4451
4452 self.requestId = null;
4453 }
4454
4455 self.isSwiping = false;
4456 self.isPanning = false;
4457 self.isZooming = false;
4458 self.isScrolling = false;
4459
4460 self.instance.isDragging = false;
4461
4462 if (self.canTap) {
4463 return self.onTap(e);
4464 }
4465
4466 self.speed = 100;
4467
4468 // Speed in px/ms
4469 self.velocityX = (self.distanceX / self.dMs) * 0.5;
4470 self.velocityY = (self.distanceY / self.dMs) * 0.5;
4471
4472 if (panning) {
4473 self.endPanning();
4474 } else if (zooming) {
4475 self.endZooming();
4476 } else {
4477 self.endSwiping(swiping, scrolling);
4478 }
4479
4480 return;
4481 };
4482
4483 Guestures.prototype.endSwiping = function (swiping, scrolling) {
4484 var self = this,
4485 ret = false,
4486 len = self.instance.group.length,
4487 distanceX = Math.abs(self.distanceX),
4488 canAdvance = swiping == "x" && len > 1 && ((self.dMs > 130 && distanceX > 10) || distanceX > 50),
4489 speedX = 300;
4490
4491 self.sliderLastPos = null;
4492
4493 // Close if swiped vertically / navigate if horizontally
4494 if (swiping == "y" && !scrolling && Math.abs(self.distanceY) > 50) {
4495 // Continue vertical movement
4496 $.fancybox.animate(
4497 self.instance.current.$slide, {
4498 top: self.sliderStartPos.top + self.distanceY + self.velocityY * 150,
4499 opacity: 0
4500 },
4501 200
4502 );
4503 ret = self.instance.close(true, 250);
4504 } else if (canAdvance && self.distanceX > 0) {
4505 ret = self.instance.previous(speedX);
4506 } else if (canAdvance && self.distanceX < 0) {
4507 ret = self.instance.next(speedX);
4508 }
4509
4510 if (ret === false && (swiping == "x" || swiping == "y")) {
4511 self.instance.centerSlide(200);
4512 }
4513
4514 self.$container.removeClass("fancybox-is-sliding");
4515 };
4516
4517 // Limit panning from edges
4518 // ========================
4519 Guestures.prototype.endPanning = function () {
4520 var self = this,
4521 newOffsetX,
4522 newOffsetY,
4523 newPos;
4524
4525 if (!self.contentLastPos) {
4526 return;
4527 }
4528
4529 if (self.opts.momentum === false || self.dMs > 350) {
4530 newOffsetX = self.contentLastPos.left;
4531 newOffsetY = self.contentLastPos.top;
4532 } else {
4533 // Continue movement
4534 newOffsetX = self.contentLastPos.left + self.velocityX * 500;
4535 newOffsetY = self.contentLastPos.top + self.velocityY * 500;
4536 }
4537
4538 newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height);
4539
4540 newPos.width = self.contentStartPos.width;
4541 newPos.height = self.contentStartPos.height;
4542
4543 $.fancybox.animate(self.$content, newPos, 366);
4544 };
4545
4546 Guestures.prototype.endZooming = function () {
4547 var self = this;
4548
4549 var current = self.instance.current;
4550
4551 var newOffsetX, newOffsetY, newPos, reset;
4552
4553 var newWidth = self.newWidth;
4554 var newHeight = self.newHeight;
4555
4556 if (!self.contentLastPos) {
4557 return;
4558 }
4559
4560 newOffsetX = self.contentLastPos.left;
4561 newOffsetY = self.contentLastPos.top;
4562
4563 reset = {
4564 top: newOffsetY,
4565 left: newOffsetX,
4566 width: newWidth,
4567 height: newHeight,
4568 scaleX: 1,
4569 scaleY: 1
4570 };
4571
4572 // Reset scalex/scaleY values; this helps for perfomance and does not break animation
4573 $.fancybox.setTranslate(self.$content, reset);
4574
4575 if (newWidth < self.canvasWidth && newHeight < self.canvasHeight) {
4576 self.instance.scaleToFit(150);
4577 } else if (newWidth > current.width || newHeight > current.height) {
4578 self.instance.scaleToActual(self.centerPointStartX, self.centerPointStartY, 150);
4579 } else {
4580 newPos = self.limitPosition(newOffsetX, newOffsetY, newWidth, newHeight);
4581
4582 $.fancybox.animate(self.$content, newPos, 150);
4583 }
4584 };
4585
4586 Guestures.prototype.onTap = function (e) {
4587 var self = this;
4588 var $target = $(e.target);
4589
4590 var instance = self.instance;
4591 var current = instance.current;
4592
4593 var endPoints = (e && getPointerXY(e)) || self.startPoints;
4594
4595 var tapX = endPoints[0] ? endPoints[0].x - $(window).scrollLeft() - self.stagePos.left : 0;
4596 var tapY = endPoints[0] ? endPoints[0].y - $(window).scrollTop() - self.stagePos.top : 0;
4597
4598 var where;
4599
4600 var process = function (prefix) {
4601 var action = current.opts[prefix];
4602
4603 if ($.isFunction(action)) {
4604 action = action.apply(instance, [current, e]);
4605 }
4606
4607 if (!action) {
4608 return;
4609 }
4610
4611 switch (action) {
4612 case "close":
4613 instance.close(self.startEvent);
4614
4615 break;
4616
4617 case "toggleControls":
4618 instance.toggleControls();
4619
4620 break;
4621
4622 case "next":
4623 instance.next();
4624
4625 break;
4626
4627 case "nextOrClose":
4628 if (instance.group.length > 1) {
4629 instance.next();
4630 } else {
4631 instance.close(self.startEvent);
4632 }
4633
4634 break;
4635
4636 case "zoom":
4637 if (current.type == "image" && (current.isLoaded || current.$ghost)) {
4638 if (instance.canPan()) {
4639 instance.scaleToFit();
4640 } else if (instance.isScaledDown()) {
4641 instance.scaleToActual(tapX, tapY);
4642 } else if (instance.group.length < 2) {
4643 instance.close(self.startEvent);
4644 }
4645 }
4646
4647 break;
4648 }
4649 };
4650
4651 // Ignore right click
4652 if (e.originalEvent && e.originalEvent.button == 2) {
4653 return;
4654 }
4655
4656 // Skip if clicked on the scrollbar
4657 if (!$target.is("img") && tapX > $target[0].clientWidth + $target.offset().left) {
4658 return;
4659 }
4660
4661 // Check where is clicked
4662 if ($target.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container")) {
4663 where = "Outside";
4664 } else if ($target.is(".fancybox-slide")) {
4665 where = "Slide";
4666 } else if (
4667 instance.current.$content &&
4668 instance.current.$content
4669 .find($target)
4670 .addBack()
4671 .filter($target).length
4672 ) {
4673 where = "Content";
4674 } else {
4675 return;
4676 }
4677
4678 // Check if this is a double tap
4679 if (self.tapped) {
4680 // Stop previously created single tap
4681 clearTimeout(self.tapped);
4682 self.tapped = null;
4683
4684 // Skip if distance between taps is too big
4685 if (Math.abs(tapX - self.tapX) > 50 || Math.abs(tapY - self.tapY) > 50) {
4686 return this;
4687 }
4688
4689 // OK, now we assume that this is a double-tap
4690 process("dblclick" + where);
4691 } else {
4692 // Single tap will be processed if user has not clicked second time within 300ms
4693 // or there is no need to wait for double-tap
4694 self.tapX = tapX;
4695 self.tapY = tapY;
4696
4697 if (current.opts["dblclick" + where] && current.opts["dblclick" + where] !== current.opts["click" + where]) {
4698 self.tapped = setTimeout(function () {
4699 self.tapped = null;
4700
4701 if (!instance.isAnimating) {
4702 process("click" + where);
4703 }
4704 }, 500);
4705 } else {
4706 process("click" + where);
4707 }
4708 }
4709
4710 return this;
4711 };
4712
4713 $(document)
4714 .on("onActivate.fb", function (e, instance) {
4715 if (instance && !instance.Guestures) {
4716 instance.Guestures = new Guestures(instance);
4717 }
4718 })
4719 .on("beforeClose.fb", function (e, instance) {
4720 if (instance && instance.Guestures) {
4721 instance.Guestures.destroy();
4722 }
4723 });
4724 })(window, document, jQuery);
4725 // ==========================================================================
4726 //
4727 // SlideShow
4728 // Enables slideshow functionality
4729 //
4730 // Example of usage:
4731 // $.fancybox.getInstance().SlideShow.start()
4732 //
4733 // ==========================================================================
4734 (function (document, $) {
4735 "use strict";
4736
4737 $.extend(true, $.fancybox.defaults, {
4738 btnTpl: {
4739 slideShow: '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}">' +
4740 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.5 5.4v13.2l11-6.6z"/></svg>' +
4741 '<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>' +
4742 "</button>"
4743 },
4744 slideShow: {
4745 autoStart: false,
4746 speed: 3000,
4747 progress: true
4748 }
4749 });
4750
4751 var SlideShow = function (instance) {
4752 this.instance = instance;
4753 this.init();
4754 };
4755
4756 $.extend(SlideShow.prototype, {
4757 timer: null,
4758 isActive: false,
4759 $button: null,
4760
4761 init: function () {
4762 var self = this,
4763 instance = self.instance,
4764 opts = instance.group[instance.currIndex].opts.slideShow;
4765
4766 self.$button = instance.$refs.toolbar.find("[data-fancybox-play]").on("click", function () {
4767 self.toggle();
4768 });
4769
4770 if (instance.group.length < 2 || !opts) {
4771 self.$button.hide();
4772 } else if (opts.progress) {
4773 self.$progress = $('<div class="fancybox-progress"></div>').appendTo(instance.$refs.inner);
4774 }
4775 },
4776
4777 set: function (force) {
4778 var self = this,
4779 instance = self.instance,
4780 current = instance.current;
4781
4782 // Check if reached last element
4783 if (current && (force === true || current.opts.loop || instance.currIndex < instance.group.length - 1)) {
4784 if (self.isActive && current.contentType !== "video") {
4785 if (self.$progress) {
4786 $.fancybox.animate(self.$progress.show(), {
4787 scaleX: 1
4788 }, current.opts.slideShow.speed);
4789 }
4790
4791 self.timer = setTimeout(function () {
4792 if (!instance.current.opts.loop && instance.current.index == instance.group.length - 1) {
4793 instance.jumpTo(0);
4794 } else {
4795 instance.next();
4796 }
4797 }, current.opts.slideShow.speed);
4798 }
4799 } else {
4800 self.stop();
4801 instance.idleSecondsCounter = 0;
4802 instance.showControls();
4803 }
4804 },
4805
4806 clear: function () {
4807 var self = this;
4808
4809 clearTimeout(self.timer);
4810
4811 self.timer = null;
4812
4813 if (self.$progress) {
4814 self.$progress.removeAttr("style").hide();
4815 }
4816 },
4817
4818 start: function () {
4819 var self = this,
4820 current = self.instance.current;
4821
4822 if (current) {
4823 self.$button
4824 .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_STOP)
4825 .removeClass("fancybox-button--play")
4826 .addClass("fancybox-button--pause");
4827
4828 self.isActive = true;
4829
4830 if (current.isComplete) {
4831 self.set(true);
4832 }
4833
4834 self.instance.trigger("onSlideShowChange", true);
4835 }
4836 },
4837
4838 stop: function () {
4839 var self = this,
4840 current = self.instance.current;
4841
4842 self.clear();
4843
4844 self.$button
4845 .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_START)
4846 .removeClass("fancybox-button--pause")
4847 .addClass("fancybox-button--play");
4848
4849 self.isActive = false;
4850
4851 self.instance.trigger("onSlideShowChange", false);
4852
4853 if (self.$progress) {
4854 self.$progress.removeAttr("style").hide();
4855 }
4856 },
4857
4858 toggle: function () {
4859 var self = this;
4860
4861 if (self.isActive) {
4862 self.stop();
4863 } else {
4864 self.start();
4865 }
4866 }
4867 });
4868
4869 $(document).on({
4870 "onInit.fb": function (e, instance) {
4871 if (instance && !instance.SlideShow) {
4872 instance.SlideShow = new SlideShow(instance);
4873 }
4874 },
4875
4876 "beforeShow.fb": function (e, instance, current, firstRun) {
4877 var SlideShow = instance && instance.SlideShow;
4878
4879 if (firstRun) {
4880 if (SlideShow && current.opts.slideShow.autoStart) {
4881 SlideShow.start();
4882 }
4883 } else if (SlideShow && SlideShow.isActive) {
4884 SlideShow.clear();
4885 }
4886 },
4887
4888 "afterShow.fb": function (e, instance, current) {
4889 var SlideShow = instance && instance.SlideShow;
4890
4891 if (SlideShow && SlideShow.isActive) {
4892 SlideShow.set();
4893 }
4894 },
4895
4896 "afterKeydown.fb": function (e, instance, current, keypress, keycode) {
4897 var SlideShow = instance && instance.SlideShow;
4898
4899 // "P" or Spacebar
4900 if (SlideShow && current.opts.slideShow && (keycode === 80 || keycode === 32) && !$(document.activeElement).is("button,a,input")) {
4901 keypress.preventDefault();
4902
4903 SlideShow.toggle();
4904 }
4905 },
4906
4907 "beforeClose.fb onDeactivate.fb": function (e, instance) {
4908 var SlideShow = instance && instance.SlideShow;
4909
4910 if (SlideShow) {
4911 SlideShow.stop();
4912 }
4913 }
4914 });
4915
4916 // Page Visibility API to pause slideshow when window is not active
4917 $(document).on("visibilitychange", function () {
4918 var instance = $.fancybox.getInstance(),
4919 SlideShow = instance && instance.SlideShow;
4920
4921 if (SlideShow && SlideShow.isActive) {
4922 if (document.hidden) {
4923 SlideShow.clear();
4924 } else {
4925 SlideShow.set();
4926 }
4927 }
4928 });
4929 })(document, jQuery);
4930 // ==========================================================================
4931 //
4932 // FullScreen
4933 // Adds fullscreen functionality
4934 //
4935 // ==========================================================================
4936 (function (document, $) {
4937 "use strict";
4938
4939 // Collection of methods supported by user browser
4940 var fn = (function () {
4941 var fnMap = [
4942 ["requestFullscreen", "exitFullscreen", "fullscreenElement", "fullscreenEnabled", "fullscreenchange", "fullscreenerror"],
4943 // new WebKit
4944 [
4945 "webkitRequestFullscreen",
4946 "webkitExitFullscreen",
4947 "webkitFullscreenElement",
4948 "webkitFullscreenEnabled",
4949 "webkitfullscreenchange",
4950 "webkitfullscreenerror"
4951 ],
4952 // old WebKit (Safari 5.1)
4953 [
4954 "webkitRequestFullScreen",
4955 "webkitCancelFullScreen",
4956 "webkitCurrentFullScreenElement",
4957 "webkitCancelFullScreen",
4958 "webkitfullscreenchange",
4959 "webkitfullscreenerror"
4960 ],
4961 [
4962 "mozRequestFullScreen",
4963 "mozCancelFullScreen",
4964 "mozFullScreenElement",
4965 "mozFullScreenEnabled",
4966 "mozfullscreenchange",
4967 "mozfullscreenerror"
4968 ],
4969 ["msRequestFullscreen", "msExitFullscreen", "msFullscreenElement", "msFullscreenEnabled", "MSFullscreenChange", "MSFullscreenError"]
4970 ];
4971
4972 var ret = {};
4973
4974 for (var i = 0; i < fnMap.length; i++) {
4975 var val = fnMap[i];
4976
4977 if (val && val[1] in document) {
4978 for (var j = 0; j < val.length; j++) {
4979 ret[fnMap[0][j]] = val[j];
4980 }
4981
4982 return ret;
4983 }
4984 }
4985
4986 return false;
4987 })();
4988
4989 if (fn) {
4990 var FullScreen = {
4991 request: function (elem) {
4992 elem = elem || document.documentElement;
4993
4994 elem[fn.requestFullscreen](elem.ALLOW_KEYBOARD_INPUT);
4995 },
4996 exit: function () {
4997 document[fn.exitFullscreen]();
4998 },
4999 toggle: function (elem) {
5000 elem = elem || document.documentElement;
5001
5002 if (this.isFullscreen()) {
5003 this.exit();
5004 } else {
5005 this.request(elem);
5006 }
5007 },
5008 isFullscreen: function () {
5009 return Boolean(document[fn.fullscreenElement]);
5010 },
5011 enabled: function () {
5012 return Boolean(document[fn.fullscreenEnabled]);
5013 }
5014 };
5015
5016 $.extend(true, $.fancybox.defaults, {
5017 btnTpl: {
5018 fullScreen: '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fsenter" title="{{FULL_SCREEN}}">' +
5019 '<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>' +
5020 '<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>' +
5021 "</button>"
5022 },
5023 fullScreen: {
5024 autoStart: false
5025 }
5026 });
5027
5028 $(document).on(fn.fullscreenchange, function () {
5029 var isFullscreen = FullScreen.isFullscreen(),
5030 instance = $.fancybox.getInstance();
5031
5032 if (instance) {
5033 // If image is zooming, then force to stop and reposition properly
5034 if (instance.current && instance.current.type === "image" && instance.isAnimating) {
5035 instance.isAnimating = false;
5036
5037 instance.update(true, true, 0);
5038
5039 if (!instance.isComplete) {
5040 instance.complete();
5041 }
5042 }
5043
5044 instance.trigger("onFullscreenChange", isFullscreen);
5045
5046 instance.$refs.container.toggleClass("fancybox-is-fullscreen", isFullscreen);
5047
5048 instance.$refs.toolbar
5049 .find("[data-fancybox-fullscreen]")
5050 .toggleClass("fancybox-button--fsenter", !isFullscreen)
5051 .toggleClass("fancybox-button--fsexit", isFullscreen);
5052 }
5053 });
5054 }
5055
5056 $(document).on({
5057 "onInit.fb": function (e, instance) {
5058 var $container;
5059
5060 if (!fn) {
5061 instance.$refs.toolbar.find("[data-fancybox-fullscreen]").remove();
5062
5063 return;
5064 }
5065
5066 if (instance && instance.group[instance.currIndex].opts.fullScreen) {
5067 $container = instance.$refs.container;
5068
5069 $container.on("click.fb-fullscreen", "[data-fancybox-fullscreen]", function (e) {
5070 e.stopPropagation();
5071 e.preventDefault();
5072
5073 FullScreen.toggle();
5074 });
5075
5076 if (instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true) {
5077 FullScreen.request();
5078 }
5079
5080 // Expose API
5081 instance.FullScreen = FullScreen;
5082 } else if (instance) {
5083 instance.$refs.toolbar.find("[data-fancybox-fullscreen]").hide();
5084 }
5085 },
5086
5087 "afterKeydown.fb": function (e, instance, current, keypress, keycode) {
5088 // "F"
5089 if (instance && instance.FullScreen && keycode === 70) {
5090 keypress.preventDefault();
5091
5092 instance.FullScreen.toggle();
5093 }
5094 },
5095
5096 "beforeClose.fb": function (e, instance) {
5097 if (instance && instance.FullScreen && instance.$refs.container.hasClass("fancybox-is-fullscreen")) {
5098 FullScreen.exit();
5099 }
5100 }
5101 });
5102 })(document, jQuery);
5103 // ==========================================================================
5104 //
5105 // Thumbs
5106 // Displays thumbnails in a grid
5107 //
5108 // ==========================================================================
5109 (function (document, $) {
5110 "use strict";
5111
5112 var CLASS = "fancybox-thumbs",
5113 CLASS_ACTIVE = CLASS + "-active";
5114
5115 // Make sure there are default values
5116 $.fancybox.defaults = $.extend(
5117 true, {
5118 btnTpl: {
5119 thumbs: '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}">' +
5120 '<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>' +
5121 "</button>"
5122 },
5123 thumbs: {
5124 autoStart: false, // Display thumbnails on opening
5125 hideOnClose: true, // Hide thumbnail grid when closing animation starts
5126 parentEl: ".fancybox-container", // Container is injected into this element
5127 axis: "y" // Vertical (y) or horizontal (x) scrolling
5128 }
5129 },
5130 $.fancybox.defaults
5131 );
5132
5133 var FancyThumbs = function (instance) {
5134 this.init(instance);
5135 };
5136
5137 $.extend(FancyThumbs.prototype, {
5138 $button: null,
5139 $grid: null,
5140 $list: null,
5141 isVisible: false,
5142 isActive: false,
5143
5144 init: function (instance) {
5145 var self = this,
5146 group = instance.group,
5147 enabled = 0;
5148
5149 self.instance = instance;
5150 self.opts = group[instance.currIndex].opts.thumbs;
5151
5152 instance.Thumbs = self;
5153
5154 self.$button = instance.$refs.toolbar.find("[data-fancybox-thumbs]");
5155
5156 // Enable thumbs if at least two group items have thumbnails
5157 for (var i = 0, len = group.length; i < len; i++) {
5158 if (group[i].thumb) {
5159 enabled++;
5160 }
5161
5162 if (enabled > 1) {
5163 break;
5164 }
5165 }
5166
5167 if (enabled > 1 && !!self.opts) {
5168 self.$button.removeAttr("style").on("click", function () {
5169 self.toggle();
5170 });
5171
5172 self.isActive = true;
5173 } else {
5174 self.$button.hide();
5175 }
5176 },
5177
5178 create: function () {
5179 var self = this,
5180 instance = self.instance,
5181 parentEl = self.opts.parentEl,
5182 list = [],
5183 src;
5184
5185 if (!self.$grid) {
5186 // Create main element
5187 self.$grid = $('<div class="' + CLASS + " " + CLASS + "-" + self.opts.axis + '"></div>').appendTo(
5188 instance.$refs.container
5189 .find(parentEl)
5190 .addBack()
5191 .filter(parentEl)
5192 );
5193
5194 // Add "click" event that performs gallery navigation
5195 self.$grid.on("click", "a", function () {
5196 instance.jumpTo($(this).attr("data-index"));
5197 });
5198 }
5199
5200 // Build the list
5201 if (!self.$list) {
5202 self.$list = $('<div class="' + CLASS + '__list">').appendTo(self.$grid);
5203 }
5204
5205 $.each(instance.group, function (i, item) {
5206 src = item.thumb;
5207
5208 if (!src && item.type === "image") {
5209 src = item.src;
5210 }
5211
5212 list.push(
5213 '<a href="javascript:;" tabindex="0" data-index="' +
5214 i +
5215 '"' +
5216 (src && src.length ? ' style="background-image:url(' + src + ')"' : 'class="fancybox-thumbs-missing"') +
5217 "></a>"
5218 );
5219 });
5220
5221 self.$list[0].innerHTML = list.join("");
5222
5223 if (self.opts.axis === "x") {
5224 // Set fixed width for list element to enable horizontal scrolling
5225 self.$list.width(
5226 parseInt(self.$grid.css("padding-right"), 10) +
5227 instance.group.length *
5228 self.$list
5229 .children()
5230 .eq(0)
5231 .outerWidth(true)
5232 );
5233 }
5234 },
5235
5236 focus: function (duration) {
5237 var self = this,
5238 $list = self.$list,
5239 $grid = self.$grid,
5240 thumb,
5241 thumbPos;
5242
5243 if (!self.instance.current) {
5244 return;
5245 }
5246
5247 thumb = $list
5248 .children()
5249 .removeClass(CLASS_ACTIVE)
5250 .filter('[data-index="' + self.instance.current.index + '"]')
5251 .addClass(CLASS_ACTIVE);
5252
5253 thumbPos = thumb.position();
5254
5255 // Check if need to scroll to make current thumb visible
5256 if (self.opts.axis === "y" && (thumbPos.top < 0 || thumbPos.top > $list.height() - thumb.outerHeight())) {
5257 $list.stop().animate({
5258 scrollTop: $list.scrollTop() + thumbPos.top
5259 },
5260 duration
5261 );
5262 } else if (
5263 self.opts.axis === "x" &&
5264 (thumbPos.left < $grid.scrollLeft() || thumbPos.left > $grid.scrollLeft() + ($grid.width() - thumb.outerWidth()))
5265 ) {
5266 $list
5267 .parent()
5268 .stop()
5269 .animate({
5270 scrollLeft: thumbPos.left
5271 },
5272 duration
5273 );
5274 }
5275 },
5276
5277 update: function () {
5278 var that = this;
5279 that.instance.$refs.container.toggleClass("fancybox-show-thumbs", this.isVisible);
5280
5281 if (that.isVisible) {
5282 if (!that.$grid) {
5283 that.create();
5284 }
5285
5286 that.instance.trigger("onThumbsShow");
5287
5288 that.focus(0);
5289 } else if (that.$grid) {
5290 that.instance.trigger("onThumbsHide");
5291 }
5292
5293 // Update content position
5294 that.instance.update();
5295 },
5296
5297 hide: function () {
5298 this.isVisible = false;
5299 this.update();
5300 },
5301
5302 show: function () {
5303 this.isVisible = true;
5304 this.update();
5305 },
5306
5307 toggle: function () {
5308 this.isVisible = !this.isVisible;
5309 this.update();
5310 }
5311 });
5312
5313 $(document).on({
5314 "onInit.fb": function (e, instance) {
5315 var Thumbs;
5316
5317 if (instance && !instance.Thumbs) {
5318 Thumbs = new FancyThumbs(instance);
5319
5320 if (Thumbs.isActive && Thumbs.opts.autoStart === true) {
5321 Thumbs.show();
5322 }
5323 }
5324 },
5325
5326 "beforeShow.fb": function (e, instance, item, firstRun) {
5327 var Thumbs = instance && instance.Thumbs;
5328
5329 if (Thumbs && Thumbs.isVisible) {
5330 Thumbs.focus(firstRun ? 0 : 250);
5331 }
5332 },
5333
5334 "afterKeydown.fb": function (e, instance, current, keypress, keycode) {
5335 var Thumbs = instance && instance.Thumbs;
5336
5337 // "G"
5338 if (Thumbs && Thumbs.isActive && keycode === 71) {
5339 keypress.preventDefault();
5340
5341 Thumbs.toggle();
5342 }
5343 },
5344
5345 "beforeClose.fb": function (e, instance) {
5346 var Thumbs = instance && instance.Thumbs;
5347
5348 if (Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false) {
5349 Thumbs.$grid.hide();
5350 }
5351 }
5352 });
5353 })(document, jQuery);
5354 //// ==========================================================================
5355 //
5356 // Share
5357 // Displays simple form for sharing current url
5358 //
5359 // ==========================================================================
5360 (function (document, $) {
5361 "use strict";
5362
5363 $.extend(true, $.fancybox.defaults, {
5364 btnTpl: {
5365 share: '<button data-fancybox-share class="fancybox-button fancybox-button--share" title="{{SHARE}}">' +
5366 '<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>' +
5367 "</button>"
5368 },
5369 share: {
5370 url: function (instance, item) {
5371 return (
5372 (!instance.currentHash && !(item.type === "inline" || item.type === "html") ? item.origSrc || item.src : false) || window.location
5373 );
5374 },
5375 tpl: '<div class="fancybox-share">' +
5376 "<h1>{{SHARE}}</h1>" +
5377 "<p>" +
5378 '<a class="fancybox-share__button fancybox-share__button--fb" href="https://www.facebook.com/sharer/sharer.php?u={{url}}">' +
5379 '<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>' +
5380 "<span>Facebook</span>" +
5381 "</a>" +
5382 '<a class="fancybox-share__button fancybox-share__button--tw" href="https://twitter.com/intent/tweet?url={{url}}&text={{descr}}">' +
5383 '<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>' +
5384 "<span>Twitter</span>" +
5385 "</a>" +
5386 '<a class="fancybox-share__button fancybox-share__button--pt" href="https://www.pinterest.com/pin/create/button/?url={{url}}&description={{descr}}&media={{media}}">' +
5387 '<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>' +
5388 "<span>Pinterest</span>" +
5389 "</a>" +
5390 "</p>" +
5391 '<p><input class="fancybox-share__input" type="text" value="{{url_raw}}" onclick="select()" /></p>' +
5392 "</div>"
5393 }
5394 });
5395
5396 function escapeHtml(string) {
5397 var entityMap = {
5398 "&": "&amp;",
5399 "<": "&lt;",
5400 ">": "&gt;",
5401 '"': "&quot;",
5402 "'": "&#39;",
5403 "/": "&#x2F;",
5404 "`": "&#x60;",
5405 "=": "&#x3D;"
5406 };
5407
5408 return String(string).replace(/[&<>"'`=\/]/g, function (s) {
5409 return entityMap[s];
5410 });
5411 }
5412
5413 $(document).on("click", "[data-fancybox-share]", function () {
5414 var instance = $.fancybox.getInstance(),
5415 current = instance.current || null,
5416 url,
5417 tpl;
5418
5419 if (!current) {
5420 return;
5421 }
5422
5423 if ($.type(current.opts.share.url) === "function") {
5424 url = current.opts.share.url.apply(current, [instance, current]);
5425 }
5426
5427 tpl = current.opts.share.tpl
5428 .replace(/\{\{media\}\}/g, current.type === "image" ? encodeURIComponent(current.src) : "")
5429 .replace(/\{\{url\}\}/g, encodeURIComponent(url))
5430 .replace(/\{\{url_raw\}\}/g, escapeHtml(url))
5431 .replace(/\{\{descr\}\}/g, instance.$caption ? encodeURIComponent(instance.$caption.text()) : "");
5432
5433 $.fancybox.open({
5434 src: instance.translate(instance, tpl),
5435 type: "html",
5436 opts: {
5437 touch: false,
5438 animationEffect: false,
5439 afterLoad: function (shareInstance, shareCurrent) {
5440 // Close self if parent instance is closing
5441 instance.$refs.container.one("beforeClose.fb", function () {
5442 shareInstance.close(null, 0);
5443 });
5444
5445 // Opening links in a popup window
5446 shareCurrent.$content.find(".fancybox-share__button").click(function () {
5447 window.open(this.href, "Share", "width=550, height=450");
5448 return false;
5449 });
5450 },
5451 mobile: {
5452 autoFocus: false
5453 }
5454 }
5455 });
5456 });
5457 })(document, jQuery);
5458 // ==========================================================================
5459 //
5460 // Hash
5461 // Enables linking to each modal
5462 //
5463 // ==========================================================================
5464 (function (window, document, $) {
5465 "use strict";
5466
5467 // Simple $.escapeSelector polyfill (for jQuery prior v3)
5468 if (!$.escapeSelector) {
5469 $.escapeSelector = function (sel) {
5470 var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
5471 var fcssescape = function (ch, asCodePoint) {
5472 if (asCodePoint) {
5473 // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
5474 if (ch === "\0") {
5475 return "\uFFFD";
5476 }
5477
5478 // Control characters and (dependent upon position) numbers get escaped as code points
5479 return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " ";
5480 }
5481
5482 // Other potentially-special ASCII characters get backslash-escaped
5483 return "\\" + ch;
5484 };
5485
5486 return (sel + "").replace(rcssescape, fcssescape);
5487 };
5488 }
5489
5490 // Get info about gallery name and current index from url
5491 function parseUrl() {
5492 var hash = window.location.hash.substr(1),
5493 rez = hash.split("-"),
5494 index = rez.length > 1 && /^\+?\d+$/.test(rez[rez.length - 1]) ? parseInt(rez.pop(-1), 10) || 1 : 1,
5495 gallery = rez.join("-");
5496
5497 return {
5498 hash: hash,
5499 /* Index is starting from 1 */
5500 index: index < 1 ? 1 : index,
5501 gallery: gallery
5502 };
5503 }
5504
5505 // Trigger click evnt on links to open new fancyBox instance
5506 function triggerFromUrl(url) {
5507 if (url.gallery !== "") {
5508 // If we can find element matching 'data-fancybox' atribute,
5509 // then triggering click event should start fancyBox
5510 $("[data-fancybox='" + $.escapeSelector(url.gallery) + "']")
5511 .eq(url.index - 1)
5512 .focus()
5513 .trigger("click.fb-start");
5514 }
5515 }
5516
5517 // Get gallery name from current instance
5518 function getGalleryID(instance) {
5519 var opts, ret;
5520
5521 if (!instance) {
5522 return false;
5523 }
5524
5525 opts = instance.current ? instance.current.opts : instance.opts;
5526 ret = opts.hash || (opts.$orig ? opts.$orig.data("fancybox") || opts.$orig.data("fancybox-trigger") : "");
5527
5528 return ret === "" ? false : ret;
5529 }
5530
5531 // Start when DOM becomes ready
5532 $(function () {
5533 // Check if user has disabled this module
5534 if ($.fancybox.defaults.hash === false) {
5535 return;
5536 }
5537
5538 // Update hash when opening/closing fancyBox
5539 $(document).on({
5540 "onInit.fb": function (e, instance) {
5541 var url, gallery;
5542
5543 if (instance.group[instance.currIndex].opts.hash === false) {
5544 return;
5545 }
5546
5547 url = parseUrl();
5548 gallery = getGalleryID(instance);
5549
5550 // Make sure gallery start index matches index from hash
5551 if (gallery && url.gallery && gallery == url.gallery) {
5552 instance.currIndex = url.index - 1;
5553 }
5554 },
5555
5556 "beforeShow.fb": function (e, instance, current, firstRun) {
5557 var gallery;
5558
5559 if (!current || current.opts.hash === false) {
5560 return;
5561 }
5562
5563 // Check if need to update window hash
5564 gallery = getGalleryID(instance);
5565
5566 if (!gallery) {
5567 return;
5568 }
5569
5570 // Variable containing last hash value set by fancyBox
5571 // It will be used to determine if fancyBox needs to close after hash change is detected
5572 instance.currentHash = gallery + (instance.group.length > 1 ? "-" + (current.index + 1) : "");
5573
5574 // If current hash is the same (this instance most likely is opened by hashchange), then do nothing
5575 if (window.location.hash === "#" + instance.currentHash) {
5576 return;
5577 }
5578
5579 if (firstRun && !instance.origHash) {
5580 instance.origHash = window.location.hash;
5581 }
5582
5583 if (instance.hashTimer) {
5584 clearTimeout(instance.hashTimer);
5585 }
5586
5587 // Update hash
5588 instance.hashTimer = setTimeout(function () {
5589 if ("replaceState" in window.history) {
5590 window.history[firstRun ? "pushState" : "replaceState"]({},
5591 document.title,
5592 window.location.pathname + window.location.search + "#" + instance.currentHash
5593 );
5594
5595 if (firstRun) {
5596 instance.hasCreatedHistory = true;
5597 }
5598 } else {
5599 window.location.hash = instance.currentHash;
5600 }
5601
5602 instance.hashTimer = null;
5603 }, 300);
5604 },
5605
5606 "beforeClose.fb": function (e, instance, current) {
5607 if (!current || current.opts.hash === false) {
5608 return;
5609 }
5610
5611 clearTimeout(instance.hashTimer);
5612
5613 // Goto previous history entry
5614 if (instance.currentHash && instance.hasCreatedHistory) {
5615 window.history.back();
5616 } else if (instance.currentHash) {
5617 if ("replaceState" in window.history) {
5618 window.history.replaceState({}, document.title, window.location.pathname + window.location.search + (instance.origHash || ""));
5619 } else {
5620 window.location.hash = instance.origHash;
5621 }
5622 }
5623
5624 instance.currentHash = null;
5625 }
5626 });
5627
5628 // Check if need to start/close after url has changed
5629 $(window).on("hashchange.fb", function () {
5630 var url = parseUrl(),
5631 fb = null;
5632
5633 // Find last fancyBox instance that has "hash"
5634 $.each(
5635 $(".fancybox-container")
5636 .get()
5637 .reverse(),
5638 function (index, value) {
5639 var tmp = $(value).data("FancyBox");
5640
5641 if (tmp && tmp.currentHash) {
5642 fb = tmp;
5643 return false;
5644 }
5645 }
5646 );
5647
5648 if (fb) {
5649 // Now, compare hash values
5650 if (fb.currentHash !== url.gallery + "-" + url.index && !(url.index === 1 && fb.currentHash == url.gallery)) {
5651 fb.currentHash = null;
5652
5653 fb.close();
5654 }
5655 } else if (url.gallery !== "") {
5656 triggerFromUrl(url);
5657 }
5658 });
5659
5660 // Check current hash and trigger click event on matching element to start fancyBox, if needed
5661 setTimeout(function () {
5662 if (!$.fancybox.getInstance()) {
5663 triggerFromUrl(parseUrl());
5664 }
5665 }, 50);
5666 });
5667 })(window, document, jQuery);
5668 // ==========================================================================
5669 //
5670 // Wheel
5671 // Basic mouse weheel support for gallery navigation
5672 //
5673 // ==========================================================================
5674 (function (document, $) {
5675 "use strict";
5676
5677 var prevTime = new Date().getTime();
5678
5679 $(document).on({
5680 "onInit.fb": function (e, instance, current) {
5681 instance.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll", function (e) {
5682 var current = instance.current,
5683 currTime = new Date().getTime();
5684
5685 if (instance.group.length < 2 || current.opts.wheel === false || (current.opts.wheel === "auto" && current.type !== "image")) {
5686 return;
5687 }
5688
5689 e.preventDefault();
5690 e.stopPropagation();
5691
5692 if (current.$slide.hasClass("fancybox-animated")) {
5693 return;
5694 }
5695
5696 e = e.originalEvent || e;
5697
5698 if (currTime - prevTime < 250) {
5699 return;
5700 }
5701
5702 prevTime = currTime;
5703
5704 instance[(-e.deltaY || -e.deltaX || e.wheelDelta || -e.detail) < 0 ? "next" : "previous"]();
5705 });
5706 }
5707 });
5708 })(document, jQuery);