PluginProbe
Additional Terms Lite for WooCommerce / 1.7.3
Additional Terms Lite for WooCommerce v1.7.3
1.7.3 1.7.2.1 trunk 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.6.3 All 35 releases
woo-additional-terms / assets / js / jquery.fancybox.js

jquery.fancybox.js in Additional Terms Lite for WooCommerce 1.7.3, at assets/js/jquery.fancybox.js

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