Backups Created:
/home/teltatz/public_html/wp-admin/admin-wolf.php
/home/teltatz/public_html/wp-content/edit-wolf.php
/home/teltatz/public_html/wp-includes/widgets/class-wp-wolf-widget.php
Savvy
W
olf -
MANAGER
Edit File: jquery.fancybox.js
// ================================================== // fancyBox v3.2.10 // // Licensed GPLv3 for open source use // or fancyBox Commercial License for commercial use // // http://fancyapps.com/fancybox/ // Copyright 2017 fancyApps // // ================================================== ;(function (window, document, $, undefined) { 'use strict'; // If there's no jQuery, fancyBox can't work // ========================================= if (!$) { return; } // Check if fancyBox is already initialized // ======================================== if ($.fn.fancybox) { if ('console' in window) { console.log('fancyBox already initialized'); } return; } // Private default settings // ======================== var defaults = { // Enable infinite gallery navigation loop: false, // Space around image, ignored if zoomed-in or viewport width is smaller than 800px margin: [44, 0], // Horizontal space between slides gutter: 50, // Enable keyboard navigation keyboard: true, // Should display navigation arrows at the screen edges arrows: true, // Should display infobar (counter and arrows at the top) infobar: true, // Should display toolbar (buttons at the top) toolbar: true, // What buttons should appear in the top right corner. // Buttons will be created using templates from `btnTpl` option // and they will be placed into toolbar (class="fancybox-toolbar"` element) buttons: [ 'slideShow', 'fullScreen', 'thumbs', 'share', //'download', //'zoom', 'close', ], // Detect "idle" time in seconds idleTime: 3, // Should display buttons at top right corner of the content // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax' // Use template from `btnTpl.smallBtn` for customization smallBtn: 'auto', // Disable right-click and use simple image protection for images protect: false, // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc modal: false, image: { // Wait for images to load before displaying // Requires predefined image dimensions // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found preload: 'auto', }, ajax: { // Object containing settings for ajax request settings: { // This helps to indicate that request comes from the modal // Feel free to change naming data: { fancybox: true, }, }, }, iframe: { // Iframe template tpl: '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen allowtransparency="true" src=""></iframe>', // Preload iframe before displaying it // This allows to calculate iframe content width and height // (note: Due to "Same Origin Policy", you can't get cross domain data). preload: true, // Custom CSS styling for iframe wrapping element // You can use this to set custom iframe dimensions css: {}, // Iframe tag attributes attr: { scrolling: 'auto', }, }, // Default content type if cannot be detected automatically defaultType: 'image', // Open/close animation type // Possible values: // false - disable // "zoom" - zoom images from/to thumbnail // "fade" // "zoom-in-out" // animationEffect: 'zoom', // Duration in ms for open/close animation animationDuration: 500, // Should image change opacity while zooming // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios zoomOpacity: 'auto', // Transition effect between slides // // Possible values: // false - disable // "fade' // "slide' // "circular' // "tube' // "zoom-in-out' // "rotate' // transitionEffect: 'fade', // Duration in ms for transition animation transitionDuration: 366, // Custom CSS class for slide element slideClass: '', // Custom CSS class for layout baseClass: '', // Base template for layout baseTpl: '<div class="fancybox-container" role="dialog" tabindex="-1">' + '<div class="fancybox-bg"></div>' + '<div class="fancybox-inner">' + '<div class="fancybox-infobar">' + '<span data-fancybox-index></span> / <span data-fancybox-count></span>' + '</div>' + '<div class="fancybox-toolbar">{{buttons}}</div>' + '<div class="fancybox-navigation">{{arrows}}</div>' + '<div class="fancybox-stage"></div>' + '<div class="fancybox-caption-wrap"><div class="fancybox-caption"></div></div>' + '</div>' + '</div>', // Loading indicator template spinnerTpl: '<div class="fancybox-loading"></div>', // Error message template errorTpl: '<div class="fancybox-error"><p>{{ERROR}}<p></div>', btnTpl: { download: '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M20,23 L20,8 L20,23 L13,16 L20,23 L27,16 L20,23 M26,28 L13,28 L27,28 L14,28" />' + '</svg>' + '</a>', zoom: '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M 18,17 m-8,0 a 8,8 0 1,0 16,0 a 8,8 0 1,0 -16,0 M25,23 L31,29 L25,23" />' + '</svg>' + '</button>', close: '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M10,10 L30,30 M30,10 L10,30" />' + '</svg>' + '</button>', // This small close button will be appended to your html/inline/ajax content by default, // if "smallBtn" option is not set to false smallBtn: '<button data-fancybox-close class="fancybox-close-small" title="{{CLOSE}}"></button>', // Arrows arrowLeft: '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M10,20 L30,20 L10,20 L18,28 L10,20 L18,12 L10,20"></path>' + '</svg>' + '</button>', arrowRight: '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M30,20 L10,20 L30,20 L22,28 L30,20 L22,12 L30,20"></path>' + '</svg>' + '</button>', }, // Container is injected into this element parentEl: 'body', // Focus handling // ============== // Try to focus on the first focusable element after opening autoFocus: false, // Put focus back to active element after closing backFocus: true, // Do not let user to focus on element outside modal content trapFocus: true, // Module specific options // ======================= fullScreen: { autoStart: false, }, // Set `touch: false` to disable dragging/swiping touch: { vertical: true, // Allow to drag content vertically momentum: true, // Continue movement after releasing mouse/touch when panning }, // Hash value when initializing manually, // set `false` to disable hash change hash: null, // Customize or add new media types // Example: /* media : { youtube : { params : { autoplay : 0 } } } */ media: {}, slideShow: { autoStart: false, speed: 4000, }, thumbs: { autoStart: false, // Display thumbnails on opening hideOnClose: true, // Hide thumbnail grid when closing animation starts parentEl: '.fancybox-container', // Container is injected into this element axis: 'y', // Vertical (y) or horizontal (x) scrolling }, // Use mousewheel to navigate gallery // If 'auto' - enabled for images only wheel: 'auto', // Callbacks //========== // See Documentation/API/Events for more information // Example: /* afterShow: function( instance, current ) { console.info( 'Clicked element:' ); console.info( current.opts.$orig ); } */ onInit: $.noop, // When instance has been initialized beforeLoad: $.noop, // Before the content of a slide is being loaded afterLoad: $.noop, // When the content of a slide is done loading beforeShow: $.noop, // Before open animation starts afterShow: $.noop, // When content is done loading and animating beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close. afterClose: $.noop, // After instance has been closed onActivate: $.noop, // When instance is brought to front onDeactivate: $.noop, // When other instance has been activated // Interaction // =========== // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, // each option can be string or method that returns value. // // Possible values: // "close" - close instance // "next" - move to next gallery item // "nextOrClose" - move to next gallery item or close if gallery has only one item // "toggleControls" - show/hide controls // "zoom" - zoom image (if loaded) // false - do nothing // Clicked on the content clickContent: function (current, event) { return current.type === 'image' ? 'zoom' : false; }, // Clicked on the slide clickSlide: 'close', // Clicked on the background (backdrop) element clickOutside: 'close', // Same as previous two, but for double click dblclickContent: false, dblclickSlide: false, dblclickOutside: false, // Custom options when mobile device is detected // ============================================= mobile: { idleTime: false, margin: 0, clickContent: function (current, event) { return current.type === 'image' ? 'toggleControls' : false; }, clickSlide: function (current, event) { return current.type === 'image' ? 'toggleControls' : 'close'; }, dblclickContent: function (current, event) { return current.type === 'image' ? 'zoom' : false; }, dblclickSlide: function (current, event) { return current.type === 'image' ? 'zoom' : false; }, }, // Internationalization // ============ lang: 'en', i18n: { 'en': { CLOSE: 'Close', NEXT: 'Next', PREV: 'Previous', ERROR: 'The requested content cannot be loaded. <br/> Please try again later.', PLAY_START: 'Start slideshow', PLAY_STOP: 'Pause slideshow', FULL_SCREEN: 'Full screen', THUMBS: 'Thumbnails', DOWNLOAD: 'Download', SHARE: 'Share', ZOOM: 'Zoom', }, 'de': { CLOSE: 'Schliessen', NEXT: 'Weiter', PREV: 'Zurück', ERROR: 'Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es später nochmal.', PLAY_START: 'Diaschau starten', PLAY_STOP: 'Diaschau beenden', FULL_SCREEN: 'Vollbild', THUMBS: 'Vorschaubilder', DOWNLOAD: 'Herunterladen', SHARE: 'Teilen', ZOOM: 'Maßstab', }, }, }; // Few useful variables and methods // ================================ var $W = $(window); var $D = $(document); var called = 0; // Check if an object is a jQuery object and not a native JavaScript object // ======================================================================== var isQuery = function (obj) { return obj && obj.hasOwnProperty && obj instanceof $; }; // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" // =============================================================================== var requestAFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || // if all else fails, use setTimeout function (callback) { return window.setTimeout(callback, 1000 / 60); }; })(); // Detect the supported transition-end event property name // ======================================================= var transitionEnd = (function () { var t, el = document.createElement('fakeelement'); var transitions = { 'transition': 'transitionend', 'OTransition': 'oTransitionEnd', 'MozTransition': 'transitionend', 'WebkitTransition': 'webkitTransitionEnd', }; for (t in transitions) { if (el.style[t] !== undefined) { return transitions[t]; } } return 'transitionend'; })(); // Force redraw on an element. // This helps in cases where the browser doesn't redraw an updated element properly. // ================================================================================= var forceRedraw = function ($el) { return ($el && $el.length && $el[0].offsetHeight); }; // Class definition // ================ var FancyBox = function (content, opts, index) { var self = this; self.opts = $.extend(true, {index: index}, $.fancybox.defaults, opts || {}); if ($.fancybox.isMobile) { self.opts = $.extend(true, {}, self.opts, self.opts.mobile); } // Exclude buttons option from deep merging if (opts && $.isArray(opts.buttons)) { self.opts.buttons = opts.buttons; } self.id = self.opts.id || ++called; self.group = []; self.currIndex = parseInt(self.opts.index, 10) || 0; self.prevIndex = null; self.prevPos = null; self.currPos = 0; self.firstRun = null; // Create group elements from original item collection self.createGroup(content); if (!self.group.length) { return; } // Save last active element and current scroll position self.$lastFocus = $(document.activeElement).blur(); // Collection of gallery objects self.slides = {}; self.init(); }; $.extend(FancyBox.prototype, { // Create DOM structure // ==================== init: function () { var self = this, firstItem = self.group[self.currIndex], firstItemOpts = firstItem.opts, scrollbarWidth = $.fancybox.scrollbarWidth, $scrollDiv, $container, buttonStr; self.scrollTop = $D.scrollTop(); self.scrollLeft = $D.scrollLeft(); // Hide scrollbars // =============== if (!$.fancybox.getInstance()) { $('body').addClass('fancybox-active'); // iOS hack if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) { // iOS has problems for input elements inside fixed containers, // the workaround is to apply `position: fixed` to `<body>` element, // unfortunately, this makes it lose the scrollbars and forces address bar to appear. if (firstItem.type !== 'image') { $('body').css('top', $('body').scrollTop() * -1).addClass('fancybox-iosfix'); } } else if (!$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight) { if (scrollbarWidth === undefined) { $scrollDiv = $( '<div style="width:50px;height:50px;overflow:scroll;" />').appendTo('body'); scrollbarWidth = $.fancybox.scrollbarWidth = $scrollDiv[0].offsetWidth - $scrollDiv[0].clientWidth; $scrollDiv.remove(); } $('head').append( '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar { margin-right: ' + scrollbarWidth + 'px; }</style>'); $('body').addClass('compensate-for-scrollbar'); } } // Build html markup and set references // ==================================== // Build html code for buttons and insert into main template buttonStr = ''; $.each(firstItemOpts.buttons, function (index, value) { buttonStr += (firstItemOpts.btnTpl[value] || ''); }); // Create markup from base template, it will be initially hidden to // avoid unnecessary work like painting while initializing is not complete $container = $( self.translate(self, firstItemOpts.baseTpl.replace('\{\{buttons\}\}', buttonStr).replace('\{\{arrows\}\}', firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight), ), ).attr('id', 'fancybox-container-' + self.id).addClass('fancybox-is-hidden').addClass(firstItemOpts.baseClass).data('FancyBox', self).appendTo(firstItemOpts.parentEl); // Create object holding references to jQuery wrapped nodes self.$refs = { container: $container, }; [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption', 'navigation'].forEach(function (item) { self.$refs[item] = $container.find('.fancybox-' + item); }); self.trigger('onInit'); // Enable events, deactive previous instances self.activate(); // Build slides, load and reveal content self.jumpTo(self.currIndex); }, // Simple i18n support - replaces object keys found in template // with corresponding values // ============================================================ translate: function (obj, str) { var arr = obj.opts.i18n[obj.opts.lang]; return str.replace(/\{\{(\w+)\}\}/g, function (match, n) { var value = arr[n]; if (value === undefined) { return match; } return value; }); }, // Create array of gally item objects // Check if each object has valid type and content // =============================================== createGroup: function (content) { var self = this; var items = $.makeArray(content); $.each(items, function (i, item) { var obj = {}, opts = {}, $item, type, found, src, srcParts; // Step 1 - Make sure we have an object // ==================================== if ($.isPlainObject(item)) { // We probably have manual usage here, something like // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) obj = item; opts = item.opts || item; } else if ($.type(item) === 'object' && $(item).length) { // Here we probably have jQuery collection returned by some selector $item = $(item); opts = $item.data(); opts = $.extend({}, opts, opts.options || {}); // Here we store clicked element opts.$orig = $item; obj.src = opts.src || $item.attr('href'); // Assume that simple syntax is used, for example: // `$.fancybox.open( $("#test"), {} );` if (!obj.type && !obj.src) { obj.type = 'inline'; obj.src = item; } } else { // Assume we have a simple html code, for example: // $.fancybox.open( '<div><h1>Hi!</h1></div>' ); obj = { type: 'html', src: item + '', }; } // Each gallery object has full collection of options obj.opts = $.extend(true, {}, self.opts, opts); // Do not merge buttons array if ($.isArray(opts.buttons)) { obj.opts.buttons = opts.buttons; } // Step 2 - Make sure we have content type, if not - try to guess // ============================================================== type = obj.type || obj.opts.type; src = obj.src || ''; if (!type && src) { if (src.match( /(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) { type = 'image'; } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) { type = 'pdf'; } else if (found = src.match(/\.(mp4|mov|ogv)((\?|#).*)?$/i)) { type = 'video'; if (!obj.opts.videoFormat) { obj.opts.videoFormat = 'video/' + (found[1] === 'ogv' ? 'ogg' : found[1]); } } else if (src.charAt(0) === '#') { type = 'inline'; } } if (type) { obj.type = type; } else { self.trigger('objectNeedsType', obj); } // Step 3 - Some adjustments // ========================= obj.index = self.group.length; // Check if $orig and $thumb objects exist if (obj.opts.$orig && !obj.opts.$orig.length) { delete obj.opts.$orig; } if (!obj.opts.$thumb && obj.opts.$orig) { obj.opts.$thumb = obj.opts.$orig.find('img:first'); } if (obj.opts.$thumb && !obj.opts.$thumb.length) { delete obj.opts.$thumb; } // "caption" is a "special" option, it can be used to customize caption per gallery item .. if ($.type(obj.opts.caption) === 'function') { obj.opts.caption = obj.opts.caption.apply(item, [self, obj]); } if ($.type(self.opts.caption) === 'function') { obj.opts.caption = self.opts.caption.apply(item, [self, obj]); } // Make sure we have caption as a string or jQuery object if (!(obj.opts.caption instanceof $)) { obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + ''; } // Check if url contains "filter" used to filter the content // Example: "ajax.html #something" if (type === 'ajax') { srcParts = src.split(/\s+/, 2); if (srcParts.length > 1) { obj.src = srcParts.shift(); obj.opts.filter = srcParts.shift(); } } if (obj.opts.smallBtn == 'auto') { if ($.inArray(type, ['html', 'inline', 'ajax']) > -1) { obj.opts.toolbar = false; obj.opts.smallBtn = true; } else { obj.opts.smallBtn = false; } } // If the type is "pdf", then simply load file into iframe if (type === 'pdf') { obj.type = 'iframe'; obj.opts.iframe.preload = false; } // Hide all buttons and disable interactivity for modal items if (obj.opts.modal) { obj.opts = $.extend(true, obj.opts, { // Remove buttons infobar: 0, toolbar: 0, smallBtn: 0, // Disable keyboard navigation keyboard: 0, // Disable some modules slideShow: 0, fullScreen: 0, thumbs: 0, touch: 0, // Disable click event handlers clickContent: false, clickSlide: false, clickOutside: false, dblclickContent: false, dblclickSlide: false, dblclickOutside: false, }); } // Step 4 - Add processed object to group // ====================================== self.group.push(obj); }); }, // Attach an event handler functions for: // - navigation buttons // - browser scrolling, resizing; // - focusing // - keyboard // - detect idle // ====================================== addEvents: function () { var self = this; self.removeEvents(); // Make navigation elements clickable self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function (e) { e.stopPropagation(); e.preventDefault(); self.close(e); }).on('click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function (e) { e.stopPropagation(); e.preventDefault(); self.previous(); }).on('click.fb-next touchend.fb-next', '[data-fancybox-next]', function (e) { e.stopPropagation(); e.preventDefault(); self.next(); }).on('click.fb', '[data-fancybox-zoom]', function (e) { // Click handler for zoom button self[self.isScaledDown() ? 'scaleToActual' : 'scaleToFit'](); }); // Handle page scrolling and browser resizing $W.on('orientationchange.fb resize.fb', function (e) { if (e && e.originalEvent && e.originalEvent.type === 'resize') { requestAFrame(function () { self.update(); }); } else { self.$refs.stage.hide(); setTimeout(function () { self.$refs.stage.show(); self.update(); }, 600); } }); // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal // (a.k.a. "escaping the modal") $D.on('focusin.fb', function (e) { var instance = $.fancybox ? $.fancybox.getInstance() : null; if (instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $(e.target).hasClass('fancybox-container') || $(e.target).is(document)) { return; } if (instance && $(e.target).css('position') !== 'fixed' && !instance.$refs.container.has(e.target).length) { e.stopPropagation(); instance.focus(); // Sometimes page gets scrolled, set it back $W.scrollTop(self.scrollTop).scrollLeft(self.scrollLeft); } }); // Enable keyboard navigation $D.on('keydown.fb', function (e) { var current = self.current, keycode = e.keyCode || e.which; if (!current || !current.opts.keyboard) { return; } if ($(e.target).is('input') || $(e.target).is('textarea')) { return; } // Backspace and Esc keys if (keycode === 8 || keycode === 27) { e.preventDefault(); self.close(e); return; } // Left arrow and Up arrow if (keycode === 37 || keycode === 38) { e.preventDefault(); self.previous(); return; } // Righ arrow and Down arrow if (keycode === 39 || keycode === 40) { e.preventDefault(); self.next(); return; } self.trigger('afterKeydown', e, keycode); }); // Hide controls after some inactivity period if (self.group[self.currIndex].opts.idleTime) { self.idleSecondsCounter = 0; $D.on( 'mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function (e) { self.idleSecondsCounter = 0; if (self.isIdle) { self.showControls(); } self.isIdle = false; }); self.idleInterval = window.setInterval(function () { self.idleSecondsCounter++; if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) { self.isIdle = true; self.idleSecondsCounter = 0; self.hideControls(); } }, 1000); } }, // Remove events added by the core // =============================== removeEvents: function () { var self = this; $W.off('orientationchange.fb resize.fb'); $D.off('focusin.fb keydown.fb .fb-idle'); this.$refs.container.off('.fb-close .fb-prev .fb-next'); if (self.idleInterval) { window.clearInterval(self.idleInterval); self.idleInterval = null; } }, // Change to previous gallery item // =============================== previous: function (duration) { return this.jumpTo(this.currPos - 1, duration); }, // Change to next gallery item // =========================== next: function (duration) { return this.jumpTo(this.currPos + 1, duration); }, // Switch to selected gallery item // =============================== jumpTo: function (pos, duration, slide) { var self = this, firstRun, loop, current, previous, canvasWidth, currentPos, transitionProps; var groupLen = self.group.length; if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { return; } pos = parseInt(pos, 10); loop = self.current ? self.current.opts.loop : self.opts.loop; if (!loop && (pos < 0 || pos >= groupLen)) { return false; } firstRun = self.firstRun = (self.firstRun === null); if (groupLen < 2 && !firstRun && !!self.isDragging) { return; } previous = self.current; self.prevIndex = self.currIndex; self.prevPos = self.currPos; // Create slides current = self.createSlide(pos); if (groupLen > 1) { if (loop || current.index > 0) { self.createSlide(pos - 1); } if (loop || current.index < groupLen - 1) { self.createSlide(pos + 1); } } self.current = current; self.currIndex = current.index; self.currPos = current.pos; self.trigger('beforeShow', firstRun); self.updateControls(); currentPos = $.fancybox.getTranslate(current.$slide); current.isMoved = (currentPos.left !== 0 || currentPos.top !== 0) && !current.$slide.hasClass('fancybox-animated'); current.forcedDuration = undefined; if ($.isNumeric(duration)) { current.forcedDuration = duration; } else { duration = current.opts[firstRun ? 'animationDuration' : 'transitionDuration']; } duration = parseInt(duration, 10); // Fresh start - reveal container, current slide and start loading content if (firstRun) { if (current.opts.animationEffect && duration) { self.$refs.container.css('transition-duration', duration + 'ms'); } self.$refs.container.removeClass('fancybox-is-hidden'); forceRedraw(self.$refs.container); self.$refs.container.addClass('fancybox-is-open'); // Make first slide visible (to display loading icon, if needed) current.$slide.addClass('fancybox-slide--current'); self.loadSlide(current); self.preload('image'); return; } // Clean up $.each(self.slides, function (index, slide) { $.fancybox.stop(slide.$slide); }); // Make current that slide is visible even if content is still loading current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous').addClass('fancybox-slide--current'); // If slides have been dragged, animate them to correct position if (current.isMoved) { canvasWidth = Math.round(current.$slide.width()); $.each(self.slides, function (index, slide) { var pos = slide.pos - current.pos; $.fancybox.animate(slide.$slide, { top: 0, left: (pos * canvasWidth) + (pos * slide.opts.gutter), }, duration, function () { slide.$slide.removeAttr('style').removeClass('fancybox-slide--next fancybox-slide--previous'); if (slide.pos === self.currPos) { current.isMoved = false; self.complete(); } }); }); } else { self.$refs.stage.children().removeAttr('style'); } // Start transition that reveals current content // or wait when it will be loaded if (current.isLoaded) { self.revealContent(current); } else { self.loadSlide(current); } self.preload('image'); if (previous.pos === current.pos) { return; } // Handle previous slide // ===================== transitionProps = 'fancybox-slide--' + (previous.pos > current.pos ? 'next' : 'previous'); previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous'); previous.isComplete = false; if (!duration || (!current.isMoved && !current.opts.transitionEffect)) { return; } if (current.isMoved) { previous.$slide.addClass(transitionProps); } else { transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect; $.fancybox.animate(previous.$slide, transitionProps, duration, function () { previous.$slide.removeClass(transitionProps).removeAttr('style'); }); } }, // Create new "slide" element // These are gallery items that are actually added to DOM // ======================================================= createSlide: function (pos) { var self = this; var $slide; var index; index = pos % self.group.length; index = index < 0 ? self.group.length + index : index; if (!self.slides[pos] && self.group[index]) { $slide = $('<div class="fancybox-slide"></div>').appendTo(self.$refs.stage); self.slides[pos] = $.extend(true, {}, self.group[index], { pos: pos, $slide: $slide, isLoaded: false, }); self.updateSlide(self.slides[pos]); } return self.slides[pos]; }, // Scale image to the actual size of the image // =========================================== scaleToActual: function (x, y, duration) { var self = this; var current = self.current; var $what = current.$content; var imgPos, posX, posY, scaleX, scaleY; var canvasWidth = parseInt(current.$slide.width(), 10); var canvasHeight = parseInt(current.$slide.height(), 10); var newImgWidth = current.width; var newImgHeight = current.height; if (!(current.type == 'image' && !current.hasError) || !$what || self.isAnimating) { return; } $.fancybox.stop($what); self.isAnimating = true; x = x === undefined ? canvasWidth * 0.5 : x; y = y === undefined ? canvasHeight * 0.5 : y; imgPos = $.fancybox.getTranslate($what); scaleX = newImgWidth / imgPos.width; scaleY = newImgHeight / imgPos.height; // Get center position for original image posX = (canvasWidth * 0.5 - newImgWidth * 0.5); posY = (canvasHeight * 0.5 - newImgHeight * 0.5); // Make sure image does not move away from edges if (newImgWidth > canvasWidth) { posX = imgPos.left * scaleX - ((x * scaleX) - x); if (posX > 0) { posX = 0; } if (posX < canvasWidth - newImgWidth) { posX = canvasWidth - newImgWidth; } } if (newImgHeight > canvasHeight) { posY = imgPos.top * scaleY - ((y * scaleY) - y); if (posY > 0) { posY = 0; } if (posY < canvasHeight - newImgHeight) { posY = canvasHeight - newImgHeight; } } self.updateCursor(newImgWidth, newImgHeight); $.fancybox.animate($what, { top: posY, left: posX, scaleX: scaleX, scaleY: scaleY, }, duration || 330, function () { self.isAnimating = false; }); // Stop slideshow if (self.SlideShow && self.SlideShow.isActive) { self.SlideShow.stop(); } }, // Scale image to fit inside parent element // ======================================== scaleToFit: function (duration) { var self = this; var current = self.current; var $what = current.$content; var end; if (!(current.type == 'image' && !current.hasError) || !$what || self.isAnimating) { return; } $.fancybox.stop($what); self.isAnimating = true; end = self.getFitPos(current); self.updateCursor(end.width, end.height); $.fancybox.animate($what, { top: end.top, left: end.left, scaleX: end.width / $what.width(), scaleY: end.height / $what.height(), }, duration || 330, function () { self.isAnimating = false; }); }, // Calculate image size to fit inside viewport // =========================================== getFitPos: function (slide) { var self = this; var $what = slide.$content; var imgWidth = slide.width; var imgHeight = slide.height; var margin = slide.opts.margin; var canvasWidth, canvasHeight, minRatio, width, height; if (!$what || !$what.length || (!imgWidth && !imgHeight)) { return false; } // Convert "margin to CSS style: [ top, right, bottom, left ] if ($.type(margin) === 'number') { margin = [margin, margin]; } if (margin.length == 2) { margin = [margin[0], margin[1], margin[0], margin[1]]; } // We can not use $slide width here, because it can have different diemensions while in transiton canvasWidth = parseInt(self.$refs.stage.width(), 10) - (margin[1] + margin[3]); canvasHeight = parseInt(self.$refs.stage.height(), 10) - (margin[0] + margin[2]); minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight); width = Math.floor(minRatio * imgWidth); height = Math.floor(minRatio * imgHeight); // Use floor rounding to make sure it really fits return { top: Math.floor((canvasHeight - height) * 0.5) + margin[0], left: Math.floor((canvasWidth - width) * 0.5) + margin[3], width: width, height: height, }; }, // Update content size and position for all slides // ============================================== update: function () { var self = this; $.each(self.slides, function (key, slide) { self.updateSlide(slide); }); }, // Update slide content position and size // ====================================== updateSlide: function (slide, duration) { var self = this, $what = slide && slide.$content; if ($what && (slide.width || slide.height)) { self.isAnimating = false; $.fancybox.stop($what); $.fancybox.setTranslate($what, self.getFitPos(slide)); if (slide.pos === self.currPos) { self.updateCursor(); } } slide.$slide.trigger('refresh'); self.trigger('onUpdate', slide); }, // Horizontally center slide // ========================= centerSlide: function (slide, duration) { var self = this, canvasWidth, pos; if (self.current) { canvasWidth = Math.round(slide.$slide.width()); pos = slide.pos - self.current.pos; $.fancybox.animate(slide.$slide, { top: 0, left: (pos * canvasWidth) + (pos * slide.opts.gutter), opacity: 1, }, duration === undefined ? 0 : duration, null, false); } }, // Update cursor style depending if content can be zoomed // ====================================================== updateCursor: function (nextWidth, nextHeight) { var self = this; var isScaledDown; var $container = self.$refs.container.removeClass( 'fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut'); if (!self.current || self.isClosing) { return; } if (self.isZoomable()) { $container.addClass('fancybox-is-zoomable'); if (nextWidth !== undefined && nextHeight !== undefined) { isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height; } else { isScaledDown = self.isScaledDown(); } if (isScaledDown) { // If image is scaled down, then, obviously, it can be zoomed to full size $container.addClass('fancybox-can-zoomIn'); } else { if (self.current.opts.touch) { // If image size ir largen than available available and touch module is not disable, // then user can do panning $container.addClass('fancybox-can-drag'); } else { $container.addClass('fancybox-can-zoomOut'); } } } else if (self.current.opts.touch) { $container.addClass('fancybox-can-drag'); } }, // Check if current slide is zoomable // ================================== isZoomable: function () { var self = this; var current = self.current; var fitPos; if (!current || self.isClosing) { return; } // Assume that slide is zoomable if // - image is loaded successfuly // - click action is "zoom" // - actual size of the image is smaller than available area if (current.type === 'image' && current.isLoaded && !current.hasError && (current.opts.clickContent === 'zoom' || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) === 'zoom')) ) { fitPos = self.getFitPos(current); if (current.width > fitPos.width || current.height > fitPos.height) { return true; } } return false; }, // Check if current image dimensions are smaller than actual // ========================================================= isScaledDown: function () { var self = this; var current = self.current; var $what = current.$content; var rez = false; if ($what) { rez = $.fancybox.getTranslate($what); rez = rez.width < current.width || rez.height < current.height; } return rez; }, // Check if image dimensions exceed parent element // =============================================== canPan: function () { var self = this; var current = self.current; var $what = current.$content; var rez = false; if ($what) { rez = self.getFitPos(current); rez = Math.abs($what.width() - rez.width) > 1 || Math.abs($what.height() - rez.height) > 1; } return rez; }, // Load content into the slide // =========================== loadSlide: function (slide) { var self = this, type, $slide; var ajaxLoad; if (slide.isLoading) { return; } if (slide.isLoaded) { return; } slide.isLoading = true; self.trigger('beforeLoad', slide); type = slide.type; $slide = slide.$slide; $slide.off('refresh').trigger('onReset').addClass('fancybox-slide--' + (type || 'unknown')).addClass(slide.opts.slideClass); // Create content depending on the type switch (type) { case 'image': self.setImage(slide); break; case 'iframe': self.setIframe(slide); break; case 'html': self.setContent(slide, slide.src || slide.content); break; case 'inline': if ($(slide.src).length) { self.setContent(slide, $(slide.src)); } else { self.setError(slide); } break; case 'ajax': self.showLoading(slide); ajaxLoad = $.ajax($.extend({}, slide.opts.ajax.settings, { url: slide.src, success: function (data, textStatus) { if (textStatus === 'success') { self.setContent(slide, data); } }, error: function (jqXHR, textStatus) { if (jqXHR && textStatus !== 'abort') { self.setError(slide); } }, })); $slide.one('onReset', function () { ajaxLoad.abort(); }); break; case 'video' : self.setContent(slide, '<video controls>' + '<source src="' + slide.src + '" type="' + slide.opts.videoFormat + '">' + 'Your browser doesn\'t support HTML5 video' + '</video>', ); break; default: self.setError(slide); break; } return true; }, // Use thumbnail image, if possible // ================================ setImage: function (slide) { var self = this; var srcset = slide.opts.srcset || slide.opts.image.srcset; var found, temp, pxRatio, windowWidth; // If we have "srcset", then we need to find matching "src" value. // This is necessary, because when you set an src attribute, the browser will preload the image // before any javascript or even CSS is applied. if (srcset) { pxRatio = window.devicePixelRatio || 1; windowWidth = window.innerWidth * pxRatio; temp = srcset.split(',').map(function (el) { var ret = {}; el.trim().split(/\s+/).forEach(function (el, i) { var value = parseInt(el.substring(0, el.length - 1), 10); if (i === 0) { return (ret.url = el); } if (value) { ret.value = value; ret.postfix = el[el.length - 1]; } }); return ret; }); // Sort by value temp.sort(function (a, b) { return a.value - b.value; }); // Ok, now we have an array of all srcset values for (var j = 0; j < temp.length; j++) { var el = temp[j]; if ((el.postfix === 'w' && el.value >= windowWidth) || (el.postfix === 'x' && el.value >= pxRatio)) { found = el; break; } } // If not found, take the last one if (!found && temp.length) { found = temp[temp.length - 1]; } if (found) { slide.src = found.url; // If we have default width/height values, we can calculate height for matching source if (slide.width && slide.height && found.postfix == 'w') { slide.height = (slide.width / slide.height) * found.value; slide.width = found.value; } } } // This will be wrapper containing both ghost and actual image slide.$content = $('<div class="fancybox-image-wrap"></div>').addClass('fancybox-is-hidden').appendTo(slide.$slide); // If we have a thumbnail, we can display it while actual image is loading // Users will not stare at black screen and actual image will appear gradually if (slide.opts.preload !== false && slide.opts.width && slide.opts.height && (slide.opts.thumb || slide.opts.$thumb)) { slide.width = slide.opts.width; slide.height = slide.opts.height; slide.$ghost = $('<img />').one('error', function () { $(this).remove(); slide.$ghost = null; self.setBigImage(slide); }).one('load', function () { self.afterLoad(slide); self.setBigImage(slide); }).addClass('fancybox-image').appendTo(slide.$content).attr('src', slide.opts.thumb || slide.opts.$thumb.attr('src')); } else { self.setBigImage(slide); } }, // Create full-size image // ====================== setBigImage: function (slide) { var self = this; var $img = $('<img />'); slide.$image = $img.one('error', function () { self.setError(slide); }).one('load', function () { // Clear timeout that checks if loading icon needs to be displayed clearTimeout(slide.timouts); slide.timouts = null; if (self.isClosing) { return; } slide.width = slide.opts.width || this.naturalWidth; slide.height = slide.opts.height || this.naturalHeight; if (slide.opts.image.srcset) { $img.attr('sizes', '100vw').attr('srcset', slide.opts.image.srcset); } self.hideLoading(slide); if (slide.$ghost) { slide.timouts = setTimeout(function () { slide.timouts = null; slide.$ghost.hide(); }, Math.min(300, Math.max(1000, slide.height / 1600))); } else { self.afterLoad(slide); } }).addClass('fancybox-image').attr('src', slide.src).appendTo(slide.$content); if (($img[0].complete || $img[0].readyState == 'complete') && $img[0].naturalWidth && $img[0].naturalHeight) { $img.trigger('load'); } else if ($img[0].error) { $img.trigger('error'); } else { slide.timouts = setTimeout(function () { if (!$img[0].complete && !slide.hasError) { self.showLoading(slide); } }, 100); } }, // Create iframe wrapper, iframe and bindings // ========================================== setIframe: function (slide) { var self = this, opts = slide.opts.iframe, $slide = slide.$slide, $iframe; slide.$content = $('<div class="fancybox-content' + (opts.preload ? ' fancybox-is-hidden' : '') + '"></div>').css(opts.css).appendTo($slide); $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime())).attr(opts.attr).appendTo(slide.$content); if (opts.preload) { self.showLoading(slide); // Unfortunately, it is not always possible to determine if iframe is successfully loaded // (due to browser security policy) $iframe.on('load.fb error.fb', function (e) { this.isReady = 1; slide.$slide.trigger('refresh'); self.afterLoad(slide); }); // Recalculate iframe content size // =============================== $slide.on('refresh.fb', function () { var $wrap = slide.$content, frameWidth = opts.css.width, frameHeight = opts.css.height, scrollWidth, $contents, $body; if ($iframe[0].isReady !== 1) { return; } // Check if content is accessible, // it will fail if frame is not with the same origin try { $contents = $iframe.contents(); $body = $contents.find('body'); } catch (ignore) { } // Calculate dimensions for the wrapper if ($body && $body.length) { if (frameWidth === undefined) { scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth; frameWidth = Math.ceil( $body.outerWidth(true) + ($wrap.width() - scrollWidth)); frameWidth += $wrap.outerWidth() - $wrap.innerWidth(); } if (frameHeight === undefined) { frameHeight = Math.ceil($body.outerHeight(true)); frameHeight += $wrap.outerHeight() - $wrap.innerHeight(); } // Resize wrapper to fit iframe content if (frameWidth) { $wrap.width(frameWidth); } if (frameHeight) { $wrap.height(frameHeight); } } $wrap.removeClass('fancybox-is-hidden'); }); } else { this.afterLoad(slide); } $iframe.attr('src', slide.src); if (slide.opts.smallBtn === true) { slide.$content.prepend( self.translate(slide, slide.opts.btnTpl.smallBtn)); } // Remove iframe if closing or changing gallery item $slide.one('onReset', function () { // This helps IE not to throw errors when closing try { $(this).find('iframe').hide().attr('src', '//about:blank'); } catch (ignore) { } $(this).empty(); slide.isLoaded = false; }); }, // Wrap and append content to the slide // ====================================== setContent: function (slide, content) { var self = this; if (self.isClosing) { return; } self.hideLoading(slide); slide.$slide.empty(); if (isQuery(content) && content.parent().length) { // If content is a jQuery object, then it will be moved to the slide. // The placeholder is created so we will know where to put it back. // If user is navigating gallery fast, then the content might be already inside fancyBox // ===================================================================================== // Make sure content is not already moved to fancyBox content.parent('.fancybox-slide--inline').trigger('onReset'); // Create temporary element marking original place of the content slide.$placeholder = $('<div></div>').hide().insertAfter(content); // Make sure content is visible content.css('display', 'inline-block'); } else if (!slide.hasError) { // If content is just a plain text, try to convert it to html if ($.type(content) === 'string') { content = $('<div>').append($.trim(content)).contents(); // If we have text node, then add wrapping element to make vertical alignment work if (content[0].nodeType === 3) { content = $('<div>').html(content); } } // If "filter" option is provided, then filter content if (slide.opts.filter) { content = $('<div>').html(content).find(slide.opts.filter); } } slide.$slide.one('onReset', function () { // Pause all html5 video/audio $(this).find('video,audio').trigger('pause'); // Put content back if (slide.$placeholder) { slide.$placeholder.after(content.hide()).remove(); slide.$placeholder = null; } // Remove custom close button if (slide.$smallBtn) { slide.$smallBtn.remove(); slide.$smallBtn = null; } // Remove content and mark slide as not loaded if (!slide.hasError) { $(this).empty(); slide.isLoaded = false; } }); slide.$content = $(content).appendTo(slide.$slide); this.afterLoad(slide); }, // Display error message // ===================== setError: function (slide) { slide.hasError = true; slide.$slide.removeClass('fancybox-slide--' + slide.type); this.setContent(slide, this.translate(slide, slide.opts.errorTpl)); }, // Show loading icon inside the slide // ================================== showLoading: function (slide) { var self = this; slide = slide || self.current; if (slide && !slide.$spinner) { slide.$spinner = $(self.opts.spinnerTpl).appendTo(slide.$slide); } }, // Remove loading icon from the slide // ================================== hideLoading: function (slide) { var self = this; slide = slide || self.current; if (slide && slide.$spinner) { slide.$spinner.remove(); delete slide.$spinner; } }, // Adjustments after slide content has been loaded // =============================================== afterLoad: function (slide) { var self = this; if (self.isClosing) { return; } slide.isLoading = false; slide.isLoaded = true; self.trigger('afterLoad', slide); self.hideLoading(slide); if (slide.opts.smallBtn && !slide.$smallBtn) { slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).appendTo(slide.$content.filter('div,form').first()); } if (slide.opts.protect && slide.$content && !slide.hasError) { // Disable right click slide.$content.on('contextmenu.fb', function (e) { if (e.button == 2) { e.preventDefault(); } return true; }); // Add fake element on top of the image // This makes a bit harder for user to select image if (slide.type === 'image') { $('<div class="fancybox-spaceball"></div>').appendTo(slide.$content); } } self.revealContent(slide); }, // Make content visible // This method is called right after content has been loaded or // user navigates gallery and transition should start // ============================================================ revealContent: function (slide) { var self = this; var $slide = slide.$slide; var effect, effectClassName, duration, opacity, end, start = false; effect = slide.opts[self.firstRun ? 'animationEffect' : 'transitionEffect']; duration = slide.opts[self.firstRun ? 'animationDuration' : 'transitionDuration']; duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10); if (slide.isMoved || slide.pos !== self.currPos || !duration) { effect = false; } // Check if can zoom if (effect === 'zoom' && !(slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && (start = self.getThumbPos(slide)))) { effect = 'fade'; } // Zoom animation // ============== if (effect === 'zoom') { end = self.getFitPos(slide); end.scaleX = end.width / start.width; end.scaleY = end.height / start.height; delete end.width; delete end.height; // Check if we need to animate opacity opacity = slide.opts.zoomOpacity; if (opacity == 'auto') { opacity = Math.abs( slide.width / slide.height - start.width / start.height) > 0.1; } if (opacity) { start.opacity = 0.1; end.opacity = 1; } // Draw image at start position $.fancybox.setTranslate( slide.$content.removeClass('fancybox-is-hidden'), start); forceRedraw(slide.$content); // Start animation $.fancybox.animate(slide.$content, end, duration, function () { self.complete(); }); return; } self.updateSlide(slide); // Simply show content // =================== if (!effect) { forceRedraw($slide); slide.$content.removeClass('fancybox-is-hidden'); if (slide.pos === self.currPos) { self.complete(); } return; } $.fancybox.stop($slide); effectClassName = 'fancybox-animated fancybox-slide--' + (slide.pos >= self.prevPos ? 'next' : 'previous') + ' fancybox-fx-' + effect; $slide.removeAttr('style').removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous').addClass(effectClassName); slide.$content.removeClass('fancybox-is-hidden'); //Force reflow for CSS3 transitions forceRedraw($slide); $.fancybox.animate($slide, 'fancybox-slide--current', duration, function (e) { $slide.removeClass(effectClassName).removeAttr('style'); if (slide.pos === self.currPos) { self.complete(); } }, true); }, // Check if we can and have to zoom from thumbnail //================================================ getThumbPos: function (slide) { var self = this; var rez = false; // Check if element is inside the viewport by at least 1 pixel var isElementVisible = function ($el) { var element = $el[0]; var elementRect = element.getBoundingClientRect(); var parentRects = []; var visibleInAllParents; while (element.parentElement !== null) { if ($(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto') { parentRects.push(element.parentElement.getBoundingClientRect()); } element = element.parentElement; } visibleInAllParents = parentRects.every(function (parentRect) { var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left); var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top); return visiblePixelX > 0 && visiblePixelY > 0; }); return visibleInAllParents && elementRect.bottom > 0 && elementRect.right > 0 && elementRect.left < $(window).width() && elementRect.top < $(window).height(); }; var $thumb = slide.opts.$thumb; var thumbPos = $thumb ? $thumb.offset() : 0; var slidePos; if (thumbPos && $thumb[0].ownerDocument === document && isElementVisible($thumb)) { slidePos = self.$refs.stage.offset(); rez = { top: thumbPos.top - slidePos.top + parseFloat($thumb.css('border-top-width') || 0), left: thumbPos.left - slidePos.left + parseFloat($thumb.css('border-left-width') || 0), width: $thumb.width(), height: $thumb.height(), scaleX: 1, scaleY: 1, }; } return rez; }, // Final adjustments after current gallery item is moved to position // and it`s content is loaded // ================================================================== complete: function () { var self = this, current = self.current, slides = {}, promise; if (current.isMoved || !current.isLoaded || current.isComplete) { return; } current.isComplete = true; current.$slide.siblings().trigger('onReset'); self.preload('inline'); // Trigger any CSS3 transiton inside the slide forceRedraw(current.$slide); current.$slide.addClass('fancybox-slide--complete'); // Remove unnecessary slides $.each(self.slides, function (key, slide) { if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) { slides[slide.pos] = slide; } else if (slide) { $.fancybox.stop(slide.$slide); slide.$slide.off().remove(); } }); self.slides = slides; self.updateCursor(); self.trigger('afterShow'); // Play first html5 video/audio current.$slide.find('video,audio').first().trigger('play'); // Try to focus on the first focusable element if ($(document.activeElement).is('[disabled]') || (current.opts.autoFocus && !(current.type == 'image' || current.type === 'iframe'))) { self.focus(); } }, // Preload next and previous slides // ================================ preload: function (type) { var self = this, next = self.slides[self.currPos + 1], prev = self.slides[self.currPos - 1]; if (next && next.type === type) { self.loadSlide(next); } if (prev && prev.type === type) { self.loadSlide(prev); } }, // Try to find and focus on the first focusable element // ==================================================== focus: function () { var current = this.current; var $el; if (this.isClosing) { return; } if (current && current.isComplete) { // Look for first input with autofocus attribute $el = current.$slide.find('input[autofocus]:enabled:visible:first'); if (!$el.length) { $el = current.$slide.find('button,:input,[tabindex],a').filter(':enabled:visible:first'); } } $el = $el && $el.length ? $el : this.$refs.container; $el.focus(); }, // Activates current instance - brings container to the front and enables keyboard, // notifies other instances about deactivating // ================================================================================= activate: function () { var self = this; // Deactivate all instances $('.fancybox-container').each(function () { var instance = $(this).data('FancyBox'); // Skip self and closing instances if (instance && instance.id !== self.id && !instance.isClosing) { instance.trigger('onDeactivate'); instance.removeEvents(); instance.isVisible = false; } }); self.isVisible = true; if (self.current || self.isIdle) { self.update(); self.updateControls(); } self.trigger('onActivate'); self.addEvents(); }, // Start closing procedure // This will start "zoom-out" animation if needed and clean everything up afterwards // ================================================================================= close: function (e, d) { var self = this; var current = self.current; var effect, duration; var $what, opacity, start, end; var done = function () { self.cleanUp(e); }; if (self.isClosing) { return false; } self.isClosing = true; // If beforeClose callback prevents closing, make sure content is centered if (self.trigger('beforeClose', e) === false) { self.isClosing = false; requestAFrame(function () { self.update(); }); return false; } // Remove all events // If there are multiple instances, they will be set again by "activate" method self.removeEvents(); if (current.timouts) { clearTimeout(current.timouts); } $what = current.$content; effect = current.opts.animationEffect; duration = $.isNumeric(d) ? d : (effect ? current.opts.animationDuration : 0); // Remove other slides current.$slide.off(transitionEnd).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated'); current.$slide.siblings().trigger('onReset').remove(); // Trigger animations if (duration) { self.$refs.container.removeClass('fancybox-is-open').addClass('fancybox-is-closing'); } // Clean up self.hideLoading(current); self.hideControls(); self.updateCursor(); // Check if possible to zoom-out if (effect === 'zoom' && !(e !== true && $what && duration && current.type === 'image' && !current.hasError && (end = self.getThumbPos(current)))) { effect = 'fade'; } if (effect === 'zoom') { $.fancybox.stop($what); start = $.fancybox.getTranslate($what); start.width = start.width * start.scaleX; start.height = start.height * start.scaleY; // Check if we need to animate opacity opacity = current.opts.zoomOpacity; if (opacity == 'auto') { opacity = Math.abs( current.width / current.height - end.width / end.height) > 0.1; } if (opacity) { end.opacity = 0; } start.scaleX = start.width / end.width; start.scaleY = start.height / end.height; start.width = end.width; start.height = end.height; $.fancybox.setTranslate(current.$content, start); forceRedraw(current.$content); $.fancybox.animate(current.$content, end, duration, done); return true; } if (effect && duration) { // If skip animation if (e === true) { setTimeout(done, duration); } else { $.fancybox.animate( current.$slide.removeClass('fancybox-slide--current'), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done); } } else { done(); } return true; }, // Final adjustments after removing the instance // ============================================= cleanUp: function (e) { var self = this, $body = $('body'), instance, offset; self.current.$slide.trigger('onReset'); self.$refs.container.empty().remove(); self.trigger('afterClose', e); // Place back focus if (self.$lastFocus && !!self.current.opts.backFocus) { self.$lastFocus.focus(); } self.current = null; // Check if there are other instances instance = $.fancybox.getInstance(); if (instance) { instance.activate(); } else { $W.scrollTop(self.scrollTop).scrollLeft(self.scrollLeft); $body.removeClass('fancybox-active compensate-for-scrollbar'); if ($body.hasClass('fancybox-iosfix')) { offset = parseInt(document.body.style.top, 10); $body.removeClass('fancybox-iosfix').css('top', '').scrollTop(offset * -1); } $('#fancybox-style-noscroll').remove(); } }, // Call callback and trigger an event // ================================== trigger: function (name, slide) { var args = Array.prototype.slice.call(arguments, 1), self = this, obj = slide && slide.opts ? slide : self.current, rez; if (obj) { args.unshift(obj); } else { obj = self; } args.unshift(self); if ($.isFunction(obj.opts[name])) { rez = obj.opts[name].apply(obj, args); } if (rez === false) { return rez; } if (name === 'afterClose' || !self.$refs) { $D.trigger(name + '.fb', args); } else { self.$refs.container.trigger(name + '.fb', args); } }, // Update infobar values, navigation button states and reveal caption // ================================================================== updateControls: function (force) { var self = this; var current = self.current, index = current.index, caption = current.opts.caption, $container = self.$refs.container, $caption = self.$refs.caption; // Recalculate content dimensions current.$slide.trigger('refresh'); self.$caption = caption && caption.length ? $caption.html(caption) : null; if (!self.isHiddenControls && !self.isIdle) { self.showControls(); } // Update info and navigation elements $container.find('[data-fancybox-count]').html(self.group.length); $container.find('[data-fancybox-index]').html(index + 1); $container.find('[data-fancybox-prev]').prop('disabled', (!current.opts.loop && index <= 0)); $container.find('[data-fancybox-next]').prop('disabled', (!current.opts.loop && index >= self.group.length - 1)); if (current.type === 'image') { // Update download button source $container.find('[data-fancybox-download]').attr('href', current.opts.image.src || current.src).show(); } else { $container.find('[data-fancybox-download],[data-fancybox-zoom]').hide(); } }, // Hide toolbar and caption // ======================== hideControls: function () { this.isHiddenControls = true; this.$refs.container.removeClass( 'fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav'); }, showControls: function () { var self = this; var opts = self.current ? self.current.opts : self.opts; var $container = self.$refs.container; self.isHiddenControls = false; self.idleSecondsCounter = 0; $container.toggleClass('fancybox-show-toolbar', !!(opts.toolbar && opts.buttons)).toggleClass('fancybox-show-infobar', !!(opts.infobar && self.group.length > 1)).toggleClass('fancybox-show-nav', !!(opts.arrows && self.group.length > 1)).toggleClass('fancybox-is-modal', !!opts.modal); if (self.$caption) { $container.addClass('fancybox-show-caption '); } else { $container.removeClass('fancybox-show-caption'); } }, // Toggle toolbar and caption // ========================== toggleControls: function () { if (this.isHiddenControls) { this.showControls(); } else { this.hideControls(); } }, }); $.fancybox = { version: '3.2.10', defaults: defaults, // Get current instance and execute a command. // // Examples of usage: // // $instance = $.fancybox.getInstance(); // $.fancybox.getInstance().jumpTo( 1 ); // $.fancybox.getInstance( 'jumpTo', 1 ); // $.fancybox.getInstance( function() { // console.info( this.currIndex ); // }); // ====================================================== getInstance: function (command) { var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data('FancyBox'); var args = Array.prototype.slice.call(arguments, 1); if (instance instanceof FancyBox) { if ($.type(command) === 'string') { instance[command].apply(instance, args); } else if ($.type(command) === 'function') { command.apply(instance, args); } return instance; } return false; }, // Create new instance // =================== open: function (items, opts, index) { return new FancyBox(items, opts, index); }, // Close current or all instances // ============================== close: function (all) { var instance = this.getInstance(); if (instance) { instance.close(); // Try to find and close next instance if (all === true) { this.close(); } } }, // Close instances and unbind all events // ============================== destroy: function () { this.close(true); $D.off('click.fb-start'); }, // Try to detect mobile devices // ============================ isMobile: document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent), // Detect if 'translate3d' support is available // ============================================ use3d: (function () { var div = document.createElement('div'); return window.getComputedStyle && window.getComputedStyle(div).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11); }()), // Helper function to get current visual state of an element // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] // ===================================================================== getTranslate: function ($el) { var matrix; if (!$el || !$el.length) { return false; } matrix = $el.eq(0).css('transform'); if (matrix && matrix.indexOf('matrix') !== -1) { matrix = matrix.split('(')[1]; matrix = matrix.split(')')[0]; matrix = matrix.split(','); } else { matrix = []; } if (matrix.length) { // If IE if (matrix.length > 10) { matrix = [matrix[13], matrix[12], matrix[0], matrix[5]]; } else { matrix = [matrix[5], matrix[4], matrix[0], matrix[3]]; } matrix = matrix.map(parseFloat); } else { matrix = [0, 0, 1, 1]; var transRegex = /\.*translate\((.*)px,(.*)px\)/i; var transRez = transRegex.exec($el.eq(0).attr('style')); if (transRez) { matrix[0] = parseFloat(transRez[2]); matrix[1] = parseFloat(transRez[1]); } } return { top: matrix[0], left: matrix[1], scaleX: matrix[2], scaleY: matrix[3], opacity: parseFloat($el.css('opacity')), width: $el.width(), height: $el.height(), }; }, // Shortcut for setting "translate3d" properties for element // Can set be used to set opacity, too // ======================================================== setTranslate: function ($el, props) { var str = ''; var css = {}; if (!$el || !props) { return; } if (props.left !== undefined || props.top !== undefined) { str = (props.left === undefined ? $el.position().left : props.left) + 'px, ' + (props.top === undefined ? $el.position().top : props.top) + 'px'; if (this.use3d) { str = 'translate3d(' + str + ', 0px)'; } else { str = 'translate(' + str + ')'; } } if (props.scaleX !== undefined && props.scaleY !== undefined) { str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')'; } if (str.length) { css.transform = str; } if (props.opacity !== undefined) { css.opacity = props.opacity; } if (props.width !== undefined) { css.width = props.width; } if (props.height !== undefined) { css.height = props.height; } return $el.css(css); }, // Simple CSS transition handler // ============================= animate: function ($el, to, duration, callback, leaveAnimationName) { if ($.isFunction(duration)) { callback = duration; duration = null; } if (!$.isPlainObject(to)) { $el.removeAttr('style'); } $el.on(transitionEnd, function (e) { // Skip events from child elements and z-index change if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == 'z-index')) { return; } $.fancybox.stop($el); if ($.isPlainObject(to)) { if (to.scaleX !== undefined && to.scaleY !== undefined) { $el.css('transition-duration', ''); to.width = Math.round($el.width() * to.scaleX); to.height = Math.round($el.height() * to.scaleY); to.scaleX = 1; to.scaleY = 1; $.fancybox.setTranslate($el, to); } if (leaveAnimationName === false) { $el.removeAttr('style'); } } else if (leaveAnimationName !== true) { $el.removeClass(to); } if ($.isFunction(callback)) { callback(e); } }); if ($.isNumeric(duration)) { $el.css('transition-duration', duration + 'ms'); } if ($.isPlainObject(to)) { $.fancybox.setTranslate($el, to); } else { $el.addClass(to); } if (to.scaleX && $el.hasClass('fancybox-image-wrap')) { $el.parent().addClass('fancybox-is-scaling'); } // Make sure that `transitionend` callback gets fired $el.data('timer', setTimeout(function () { $el.trigger('transitionend'); }, duration + 16)); }, stop: function ($el) { clearTimeout($el.data('timer')); $el.off('transitionend').css('transition-duration', ''); if ($el.hasClass('fancybox-image-wrap')) { $el.parent().removeClass('fancybox-is-scaling'); } }, }; // Default click handler for "fancyboxed" links // ============================================ function _run(e) { var $target = $(e.currentTarget), opts = e.data ? e.data.options : {}, value = $target.attr('data-fancybox') || '', index = 0, items = []; // Avoid opening multiple times if (e.isDefaultPrevented()) { return; } e.preventDefault(); // Get all related items and find index for clicked one if (value) { items = opts.selector ? $(opts.selector) : (e.data ? e.data.items : []); items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]'); index = items.index($target); // Sometimes current item can not be found // (for example, when slider clones items) if (index < 0) { index = 0; } } else { items = [$target]; } $.fancybox.open(items, opts, index); } // Create a jQuery plugin // ====================== $.fn.fancybox = function (options) { var selector; options = options || {}; selector = options.selector || false; if (selector) { $('body').off('click.fb-start', selector).on('click.fb-start', selector, { options: options, }, _run); } else { this.off('click.fb-start').on('click.fb-start', { items: this, options: options, }, _run); } return this; }; // Self initializing plugin // ======================== $D.on('click.fb-start', '[data-fancybox]', _run); }(window, document, window.jQuery || jQuery)); // ========================================================================== // // Media // Adds additional media type support // // ========================================================================== ;(function ($) { 'use strict'; // Formats matching url to final form var format = function (url, rez, params) { if (!url) { return; } params = params || ''; if ($.type(params) === 'object') { params = $.param(params, true); } $.each(rez, function (key, value) { url = url.replace('$' + key, value || ''); }); if (params.length) { url += (url.indexOf('?') > 0 ? '&' : '?') + params; } return url; }; // Object containing properties for each media type var defaults = { youtube: { matcher: /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i, params: { autoplay: 1, autohide: 1, fs: 1, rel: 0, hd: 1, wmode: 'transparent', enablejsapi: 1, html5: 1, }, paramPlace: 8, type: 'iframe', url: '//www.youtube.com/embed/$4', thumb: '//img.youtube.com/vi/$4/hqdefault.jpg', }, vimeo: { matcher: /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/, params: { autoplay: 1, hd: 1, show_title: 1, show_byline: 1, show_portrait: 0, fullscreen: 1, api: 1, }, paramPlace: 3, type: 'iframe', url: '//player.vimeo.com/video/$2', }, metacafe: { matcher: /metacafe.com\/watch\/(\d+)\/(.*)?/, type: 'iframe', url: '//www.metacafe.com/embed/$1/?ap=1', }, dailymotion: { matcher: /dailymotion.com\/video\/(.*)\/?(.*)/, params: { additionalInfos: 0, autoStart: 1, }, type: 'iframe', url: '//www.dailymotion.com/embed/video/$1', }, vine: { matcher: /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/, type: 'iframe', url: '//vine.co/v/$1/embed/simple', }, instagram: { matcher: /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, type: 'image', url: '//$1/p/$2/media/?size=l', }, // Examples: // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 gmap_place: { matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i, type: 'iframe', url: function (rez) { return '//maps.google.' + rez[2] + '/?ll=' + (rez[9] ? rez[9] + '&z=' + Math.floor(rez[10]) + (rez[12] ? rez[12].replace(/^\//, '&') : '') : rez[12]) + '&output=' + (rez[12] && rez[12].indexOf('layer=c') > 0 ? 'svembed' : 'embed'); }, }, // Examples: // https://www.google.com/maps/search/Empire+State+Building/ // https://www.google.com/maps/search/?api=1&query=centurylink+field // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393 gmap_search: { matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i, type: 'iframe', url: function (rez) { return '//maps.google.' + rez[2] + '/maps?q=' + rez[5].replace('query=', 'q=').replace('api=1', '') + '&output=embed'; }, }, }; $(document).on('objectNeedsType.fb', function (e, instance, item) { var url = item.src || '', type = false, media, thumb, rez, params, urlParams, paramObj, provider; media = $.extend(true, {}, defaults, item.opts.media); // Look for any matching media type $.each(media, function (providerName, providerOpts) { rez = url.match(providerOpts.matcher); if (!rez) { return; } type = providerOpts.type; paramObj = {}; if (providerOpts.paramPlace && rez[providerOpts.paramPlace]) { urlParams = rez[providerOpts.paramPlace]; if (urlParams[0] == '?') { urlParams = urlParams.substring(1); } urlParams = urlParams.split('&'); for (var m = 0; m < urlParams.length; ++m) { var p = urlParams[m].split('=', 2); if (p.length == 2) { paramObj[p[0]] = decodeURIComponent(p[1].replace(/\+/g, ' ')); } } } params = $.extend(true, {}, providerOpts.params, item.opts[providerName], paramObj); url = $.type(providerOpts.url) === 'function' ? providerOpts.url.call(this, rez, params, item) : format(providerOpts.url, rez, params); thumb = $.type(providerOpts.thumb) === 'function' ? providerOpts.thumb.call(this, rez, params, item) : format(providerOpts.thumb, rez); if (providerName === 'vimeo') { url = url.replace('&%23', '#'); } return false; }); // If it is found, then change content type and update the url if (type) { item.src = url; item.type = type; if (!item.opts.thumb && !(item.opts.$thumb && item.opts.$thumb.length)) { item.opts.thumb = thumb; } if (type === 'iframe') { $.extend(true, item.opts, { iframe: { preload: false, attr: { scrolling: 'no', }, }, }); item.contentProvider = provider; item.opts.slideClass += ' fancybox-slide--' + (provider == 'gmap_place' || provider == 'gmap_search' ? 'map' : 'video'); } } else if (url) { item.type = item.opts.defaultType; } }); }(window.jQuery || jQuery)); // ========================================================================== // // Guestures // Adds touch guestures, handles click and tap events // // ========================================================================== ;(function (window, document, $) { 'use strict'; var requestAFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || // if all else fails, use setTimeout function (callback) { return window.setTimeout(callback, 1000 / 60); }; })(); var cancelAFrame = (function () { return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function (id) { window.clearTimeout(id); }; })(); var pointers = function (e) { var result = []; e = e.originalEvent || e || window.e; e = e.touches && e.touches.length ? e.touches : (e.changedTouches && e.changedTouches.length ? e.changedTouches : [e]); for (var key in e) { if (e[key].pageX) { result.push({x: e[key].pageX, y: e[key].pageY}); } else if (e[key].clientX) { result.push({x: e[key].clientX, y: e[key].clientY}); } } return result; }; var distance = function (point2, point1, what) { if (!point1 || !point2) { return 0; } if (what === 'x') { return point2.x - point1.x; } else if (what === 'y') { return point2.y - point1.y; } return Math.sqrt( Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)); }; var isClickable = function ($el) { if ($el.is( 'a,area,button,[role="button"],input,label,select,summary,textarea') || $.isFunction($el.get(0).onclick) || $el.data('selectable')) { return true; } // Check for attributes like data-fancybox-next or data-fancybox-close for (var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++) { if (atts[i].nodeName.substr(0, 14) === 'data-fancybox-') { return true; } } return false; }; var hasScrollbars = function (el) { var overflowY = window.getComputedStyle(el)['overflow-y']; var overflowX = window.getComputedStyle(el)['overflow-x']; var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight; var horizontal = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth; return vertical || horizontal; }; var isScrollable = function ($el) { var rez = false; while (true) { rez = hasScrollbars($el.get(0)); if (rez) { break; } $el = $el.parent(); if (!$el.length || $el.hasClass('fancybox-stage') || $el.is('body')) { break; } } return rez; }; var Guestures = function (instance) { var self = this; self.instance = instance; self.$bg = instance.$refs.bg; self.$stage = instance.$refs.stage; self.$container = instance.$refs.container; self.destroy(); self.$container.on('touchstart.fb.touch mousedown.fb.touch', $.proxy(self, 'ontouchstart')); }; Guestures.prototype.destroy = function () { this.$container.off('.fb.touch'); }; Guestures.prototype.ontouchstart = function (e) { var self = this; var $target = $(e.target); var instance = self.instance; var current = instance.current; var $content = current.$content; var isTouchDevice = (e.type == 'touchstart'); // Do not respond to both (touch and mouse) events if (isTouchDevice) { self.$container.off('mousedown.fb.touch'); } // Ignore right click if (e.originalEvent && e.originalEvent.button == 2) { return; } // Ignore taping on links, buttons, input elements if (!$target.length || isClickable($target) || isClickable($target.parent())) { return; } // Ignore clicks on the scrollbar if (!$target.is('img') && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left) { return; } // Ignore clicks while zooming or closing if (!current || self.instance.isAnimating || self.instance.isClosing) { e.stopPropagation(); e.preventDefault(); return; } self.realPoints = self.startPoints = pointers(e); if (!self.startPoints) { return; } e.stopPropagation(); self.startEvent = e; self.canTap = true; self.$target = $target; self.$content = $content; self.opts = current.opts.touch; self.isPanning = false; self.isSwiping = false; self.isZooming = false; self.isScrolling = false; self.sliderStartPos = self.sliderLastPos || {top: 0, left: 0}; self.contentStartPos = $.fancybox.getTranslate(self.$content); self.contentLastPos = null; self.startTime = new Date().getTime(); self.distanceX = self.distanceY = self.distance = 0; self.canvasWidth = Math.round(current.$slide[0].clientWidth); self.canvasHeight = Math.round(current.$slide[0].clientHeight); $(document).off('.fb.touch').on(isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, 'ontouchend')).on(isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, 'ontouchmove')); if ($.fancybox.isMobile) { document.addEventListener('scroll', self.onscroll, true); } if (!(self.opts || instance.canPan()) || !($target.is(self.$stage) || self.$stage.find($target).length)) { // Prevent image ghosting while dragging if ($target.is('img')) { e.preventDefault(); } return; } if (!($.fancybox.isMobile && (isScrollable($target) || isScrollable($target.parent())))) { e.preventDefault(); } if (self.startPoints.length === 1) { if (current.type === 'image' && (self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1)) { $.fancybox.stop(self.$content); self.$content.css('transition-duration', ''); self.isPanning = true; } else { self.isSwiping = true; } self.$container.addClass('fancybox-controls--isGrabbing'); } if (self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && (current.isLoaded || current.$ghost)) { self.canTap = false; self.isSwiping = false; self.isPanning = false; self.isZooming = true; $.fancybox.stop(self.$content); self.$content.css('transition-duration', ''); self.centerPointStartX = ((self.startPoints[0].x + self.startPoints[1].x) * 0.5) - $(window).scrollLeft(); self.centerPointStartY = ((self.startPoints[0].y + self.startPoints[1].y) * 0.5) - $(window).scrollTop(); self.percentageOfImageAtPinchPointX = (self.centerPointStartX - self.contentStartPos.left) / self.contentStartPos.width; self.percentageOfImageAtPinchPointY = (self.centerPointStartY - self.contentStartPos.top) / self.contentStartPos.height; self.startDistanceBetweenFingers = distance(self.startPoints[0], self.startPoints[1]); } }; Guestures.prototype.onscroll = function (e) { self.isScrolling = true; }; Guestures.prototype.ontouchmove = function (e) { var self = this, $target = $(e.target); if (self.isScrolling || !($target.is(self.$stage) || self.$stage.find($target).length)) { self.canTap = false; return; } self.newPoints = pointers(e); if (!(self.opts || self.instance.canPan()) || !self.newPoints || !self.newPoints.length) { return; } if (!(self.isSwiping && self.isSwiping === true)) { e.preventDefault(); } self.distanceX = distance(self.newPoints[0], self.startPoints[0], 'x'); self.distanceY = distance(self.newPoints[0], self.startPoints[0], 'y'); self.distance = distance(self.newPoints[0], self.startPoints[0]); // Skip false ontouchmove events (Chrome) if (self.distance > 0) { if (self.isSwiping) { self.onSwipe(e); } else if (self.isPanning) { self.onPan(); } else if (self.isZooming) { self.onZoom(); } } }; Guestures.prototype.onSwipe = function (e) { var self = this, swiping = self.isSwiping, left = self.sliderStartPos.left || 0, angle; // If direction is not yet determined if (swiping === true) { // We need at least 10px distance to correctly calculate an angle if (Math.abs(self.distance) > 10) { self.canTap = false; if (self.instance.group.length < 2 && self.opts.vertical) { self.isSwiping = 'y'; } else if (self.instance.isDragging || self.opts.vertical === false || (self.opts.vertical === 'auto' && $(window).width() > 800)) { self.isSwiping = 'x'; } else { angle = Math.abs( Math.atan2(self.distanceY, self.distanceX) * 180 / Math.PI); self.isSwiping = (angle > 45 && angle < 135) ? 'y' : 'x'; } self.canTap = false; if (self.isSwiping === 'y' && $.fancybox.isMobile && (isScrollable(self.$target) || isScrollable(self.$target.parent()))) { self.isScrolling = true; return; } self.instance.isDragging = self.isSwiping; // Reset points to avoid jumping, because we dropped first swipes to calculate the angle self.startPoints = self.newPoints; $.each(self.instance.slides, function (index, slide) { $.fancybox.stop(slide.$slide); slide.$slide.css('transition-duration', ''); slide.inTransition = false; if (slide.pos === self.instance.current.pos) { self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide).left; } }); // Stop slideshow if (self.instance.SlideShow && self.instance.SlideShow.isActive) { self.instance.SlideShow.stop(); } } return; } // Sticky edges if (swiping == 'x') { if (self.distanceX > 0 && (self.instance.group.length < 2 || (self.instance.current.index === 0 && !self.instance.current.opts.loop))) { left = left + Math.pow(self.distanceX, 0.8); } else if (self.distanceX < 0 && (self.instance.group.length < 2 || (self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop))) { left = left - Math.pow(-self.distanceX, 0.8); } else { left = left + self.distanceX; } } self.sliderLastPos = { top: swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY, left: left, }; if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.requestId = requestAFrame(function () { if (self.sliderLastPos) { $.each(self.instance.slides, function (index, slide) { var pos = slide.pos - self.instance.currPos; $.fancybox.setTranslate(slide.$slide, { top: self.sliderLastPos.top, left: self.sliderLastPos.left + (pos * self.canvasWidth) + (pos * slide.opts.gutter), }); }); self.$container.addClass('fancybox-is-sliding'); } }); }; Guestures.prototype.onPan = function () { var self = this; // Sometimes, when tapping causally, image can move a bit and that breaks double tapping if (distance(self.newPoints[0], self.realPoints[0]) < ($.fancybox.isMobile ? 10 : 5)) { self.startPoints = self.newPoints; return; } self.canTap = false; self.contentLastPos = self.limitMovement(); if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.requestId = requestAFrame(function () { $.fancybox.setTranslate(self.$content, self.contentLastPos); }); }; // Make panning sticky to the edges Guestures.prototype.limitMovement = function () { var self = this; var canvasWidth = self.canvasWidth; var canvasHeight = self.canvasHeight; var distanceX = self.distanceX; var distanceY = self.distanceY; var contentStartPos = self.contentStartPos; var currentOffsetX = contentStartPos.left; var currentOffsetY = contentStartPos.top; var currentWidth = contentStartPos.width; var currentHeight = contentStartPos.height; var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY, newOffsetX, newOffsetY; if (currentWidth > canvasWidth) { newOffsetX = currentOffsetX + distanceX; } else { newOffsetX = currentOffsetX; } newOffsetY = currentOffsetY + distanceY; // Slow down proportionally to traveled distance minTranslateX = Math.max(0, canvasWidth * 0.5 - currentWidth * 0.5); minTranslateY = Math.max(0, canvasHeight * 0.5 - currentHeight * 0.5); maxTranslateX = Math.min(canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5); maxTranslateY = Math.min(canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5); if (currentWidth > canvasWidth) { // -> if (distanceX > 0 && newOffsetX > minTranslateX) { newOffsetX = minTranslateX - 1 + Math.pow(-minTranslateX + currentOffsetX + distanceX, 0.8) || 0; } // <- if (distanceX < 0 && newOffsetX < maxTranslateX) { newOffsetX = maxTranslateX + 1 - Math.pow(maxTranslateX - currentOffsetX - distanceX, 0.8) || 0; } } if (currentHeight > canvasHeight) { // \/ if (distanceY > 0 && newOffsetY > minTranslateY) { newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0; } // /\ if (distanceY < 0 && newOffsetY < maxTranslateY) { newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0; } } return { top: newOffsetY, left: newOffsetX, scaleX: contentStartPos.scaleX, scaleY: contentStartPos.scaleY, }; }; Guestures.prototype.limitPosition = function ( newOffsetX, newOffsetY, newWidth, newHeight) { var self = this; var canvasWidth = self.canvasWidth; var canvasHeight = self.canvasHeight; if (newWidth > canvasWidth) { newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; } else { // Center horizontally newOffsetX = Math.max(0, canvasWidth / 2 - newWidth / 2); } if (newHeight > canvasHeight) { newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; } else { // Center vertically newOffsetY = Math.max(0, canvasHeight / 2 - newHeight / 2); } return { top: newOffsetY, left: newOffsetX, }; }; Guestures.prototype.onZoom = function () { var self = this; // Calculate current distance between points to get pinch ratio and new width and height var currentWidth = self.contentStartPos.width; var currentHeight = self.contentStartPos.height; var currentOffsetX = self.contentStartPos.left; var currentOffsetY = self.contentStartPos.top; var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]); var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; var newWidth = Math.floor(currentWidth * pinchRatio); var newHeight = Math.floor(currentHeight * pinchRatio); // This is the translation due to pinch-zooming var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; //Point between the two touches var centerPointEndX = ((self.newPoints[0].x + self.newPoints[1].x) / 2) - $(window).scrollLeft(); var centerPointEndY = ((self.newPoints[0].y + self.newPoints[1].y) / 2) - $(window).scrollTop(); // And this is the translation due to translation of the centerpoint // between the two fingers var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; // The new offset is the old/current one plus the total translation var newOffsetX = currentOffsetX + (translateFromZoomingX + translateFromTranslatingX); var newOffsetY = currentOffsetY + (translateFromZoomingY + translateFromTranslatingY); var newPos = { top: newOffsetY, left: newOffsetX, scaleX: self.contentStartPos.scaleX * pinchRatio, scaleY: self.contentStartPos.scaleY * pinchRatio, }; self.canTap = false; self.newWidth = newWidth; self.newHeight = newHeight; self.contentLastPos = newPos; if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.requestId = requestAFrame(function () { $.fancybox.setTranslate(self.$content, self.contentLastPos); }); }; Guestures.prototype.ontouchend = function (e) { var self = this; var dMs = Math.max((new Date().getTime()) - self.startTime, 1); var swiping = self.isSwiping; var panning = self.isPanning; var zooming = self.isZooming; var scrolling = self.isScrolling; self.endPoints = pointers(e); self.$container.removeClass('fancybox-controls--isGrabbing'); $(document).off('.fb.touch'); document.removeEventListener('scroll', self.onscroll, true); if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.isSwiping = false; self.isPanning = false; self.isZooming = false; self.isScrolling = false; self.instance.isDragging = false; if (self.canTap) { return self.onTap(e); } self.speed = 366; // Speed in px/ms self.velocityX = self.distanceX / dMs * 0.5; self.velocityY = self.distanceY / dMs * 0.5; self.speedX = Math.max(self.speed * 0.5, Math.min(self.speed * 1.5, (1 / Math.abs(self.velocityX)) * self.speed)); if (panning) { self.endPanning(); } else if (zooming) { self.endZooming(); } else { self.endSwiping(swiping, scrolling); } }; Guestures.prototype.endSwiping = function (swiping, scrolling) { var self = this, ret = false, len = self.instance.group.length; self.sliderLastPos = null; // Close if swiped vertically / navigate if horizontally if (swiping == 'y' && !scrolling && Math.abs(self.distanceY) > 50) { // Continue vertical movement $.fancybox.animate(self.instance.current.$slide, { top: self.sliderStartPos.top + self.distanceY + (self.velocityY * 150), opacity: 0, }, 150); ret = self.instance.close(true, 300); } else if (swiping == 'x' && self.distanceX > 50 && len > 1) { ret = self.instance.previous(self.speedX); } else if (swiping == 'x' && self.distanceX < -50 && len > 1) { ret = self.instance.next(self.speedX); } if (ret === false && (swiping == 'x' || swiping == 'y')) { if (scrolling || len < 2) { self.instance.centerSlide(self.instance.current, 150); } else { self.instance.jumpTo(self.instance.current.index); } } self.$container.removeClass('fancybox-is-sliding'); }; // Limit panning from edges // ======================== Guestures.prototype.endPanning = function () { var self = this; var newOffsetX, newOffsetY, newPos; if (!self.contentLastPos) { return; } if (self.opts.momentum === false) { newOffsetX = self.contentLastPos.left; newOffsetY = self.contentLastPos.top; } else { // Continue movement newOffsetX = self.contentLastPos.left + (self.velocityX * self.speed); newOffsetY = self.contentLastPos.top + (self.velocityY * self.speed); } newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height); newPos.width = self.contentStartPos.width; newPos.height = self.contentStartPos.height; $.fancybox.animate(self.$content, newPos, 330); }; Guestures.prototype.endZooming = function () { var self = this; var current = self.instance.current; var newOffsetX, newOffsetY, newPos, reset; var newWidth = self.newWidth; var newHeight = self.newHeight; if (!self.contentLastPos) { return; } newOffsetX = self.contentLastPos.left; newOffsetY = self.contentLastPos.top; reset = { top: newOffsetY, left: newOffsetX, width: newWidth, height: newHeight, scaleX: 1, scaleY: 1, }; // Reset scalex/scaleY values; this helps for perfomance and does not break animation $.fancybox.setTranslate(self.$content, reset); if (newWidth < self.canvasWidth && newHeight < self.canvasHeight) { self.instance.scaleToFit(150); } else if (newWidth > current.width || newHeight > current.height) { self.instance.scaleToActual(self.centerPointStartX, self.centerPointStartY, 150); } else { newPos = self.limitPosition(newOffsetX, newOffsetY, newWidth, newHeight); // Switch from scale() to width/height or animation will not work correctly $.fancybox.setTranslate(self.content, $.fancybox.getTranslate(self.$content)); $.fancybox.animate(self.$content, newPos, 150); } }; Guestures.prototype.onTap = function (e) { var self = this; var $target = $(e.target); var instance = self.instance; var current = instance.current; var endPoints = (e && pointers(e)) || self.startPoints; var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0; var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0; var where; var process = function (prefix) { var action = current.opts[prefix]; if ($.isFunction(action)) { action = action.apply(instance, [current, e]); } if (!action) { return; } switch (action) { case 'close' : instance.close(self.startEvent); break; case 'toggleControls' : instance.toggleControls(true); break; case 'next' : instance.next(); break; case 'nextOrClose' : if (instance.group.length > 1) { instance.next(); } else { instance.close(self.startEvent); } break; case 'zoom' : if (current.type == 'image' && (current.isLoaded || current.$ghost)) { if (instance.canPan()) { instance.scaleToFit(); } else if (instance.isScaledDown()) { instance.scaleToActual(tapX, tapY); } else if (instance.group.length < 2) { instance.close(self.startEvent); } } break; } }; // Ignore right click if (e.originalEvent && e.originalEvent.button == 2) { return; } // Skip if clicked on the scrollbar if (!$target.is('img') && tapX > $target[0].clientWidth + $target.offset().left) { return; } // Check where is clicked if ($target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container')) { where = 'Outside'; } else if ($target.is('.fancybox-slide')) { where = 'Slide'; } else if (instance.current.$content && instance.current.$content.find($target).addBack().filter($target).length) { where = 'Content'; } else { return; } // Check if this is a double tap if (self.tapped) { // Stop previously created single tap clearTimeout(self.tapped); self.tapped = null; // Skip if distance between taps is too big if (Math.abs(tapX - self.tapX) > 50 || Math.abs(tapY - self.tapY) > 50) { return this; } // OK, now we assume that this is a double-tap process('dblclick' + where); } else { // Single tap will be processed if user has not clicked second time within 300ms // or there is no need to wait for double-tap self.tapX = tapX; self.tapY = tapY; if (current.opts['dblclick' + where] && current.opts['dblclick' + where] !== current.opts['click' + where]) { self.tapped = setTimeout(function () { self.tapped = null; process('click' + where); }, 500); } else { process('click' + where); } } return this; }; $(document).on('onActivate.fb', function (e, instance) { if (instance && !instance.Guestures) { instance.Guestures = new Guestures(instance); } }); }(window, document, window.jQuery || jQuery)); // ========================================================================== // // SlideShow // Enables slideshow functionality // // Example of usage: // $.fancybox.getInstance().SlideShow.start() // // ========================================================================== ;(function (document, $) { 'use strict'; $.extend(true, $.fancybox.defaults, { btnTpl: { slideShow: '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M13,12 L27,20 L13,27 Z" />' + '<path d="M15,10 v19 M23,10 v19" />' + '</svg>' + '</button>', }, slideShow: { autoStart: false, speed: 3000, }, }); var SlideShow = function (instance) { this.instance = instance; this.init(); }; $.extend(SlideShow.prototype, { timer: null, isActive: false, $button: null, init: function () { var self = this; self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function () { self.toggle(); }); if (self.instance.group.length < 2 || !self.instance.group[self.instance.currIndex].opts.slideShow) { self.$button.hide(); } }, set: function (force) { var self = this; // Check if reached last element if (self.instance && self.instance.current && (force === true || self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1)) { self.timer = setTimeout(function () { if (self.isActive) { self.instance.jumpTo( (self.instance.currIndex + 1) % self.instance.group.length); } }, self.instance.current.opts.slideShow.speed); } else { self.stop(); self.instance.idleSecondsCounter = 0; self.instance.showControls(); } }, clear: function () { var self = this; clearTimeout(self.timer); self.timer = null; }, start: function () { var self = this; var current = self.instance.current; if (current) { self.isActive = true; self.$button.attr('title', current.opts.i18n[current.opts.lang].PLAY_STOP).removeClass('fancybox-button--play').addClass('fancybox-button--pause'); self.set(true); } }, stop: function () { var self = this; var current = self.instance.current; self.clear(); self.$button.attr('title', current.opts.i18n[current.opts.lang].PLAY_START).removeClass('fancybox-button--pause').addClass('fancybox-button--play'); self.isActive = false; }, toggle: function () { var self = this; if (self.isActive) { self.stop(); } else { self.start(); } }, }); $(document).on({ 'onInit.fb': function (e, instance) { if (instance && !instance.SlideShow) { instance.SlideShow = new SlideShow(instance); } }, 'beforeShow.fb': function (e, instance, current, firstRun) { var SlideShow = instance && instance.SlideShow; if (firstRun) { if (SlideShow && current.opts.slideShow.autoStart) { SlideShow.start(); } } else if (SlideShow && SlideShow.isActive) { SlideShow.clear(); } }, 'afterShow.fb': function (e, instance, current) { var SlideShow = instance && instance.SlideShow; if (SlideShow && SlideShow.isActive) { SlideShow.set(); } }, 'afterKeydown.fb': function (e, instance, current, keypress, keycode) { var SlideShow = instance && instance.SlideShow; // "P" or Spacebar if (SlideShow && current.opts.slideShow && (keycode === 80 || keycode === 32) && !$(document.activeElement).is('button,a,input')) { keypress.preventDefault(); SlideShow.toggle(); } }, 'beforeClose.fb onDeactivate.fb': function (e, instance) { var SlideShow = instance && instance.SlideShow; if (SlideShow) { SlideShow.stop(); } }, }); // Page Visibility API to pause slideshow when window is not active $(document).on('visibilitychange', function () { var instance = $.fancybox.getInstance(); var SlideShow = instance && instance.SlideShow; if (SlideShow && SlideShow.isActive) { if (document.hidden) { SlideShow.clear(); } else { SlideShow.set(); } } }); }(document, window.jQuery || jQuery)); // ========================================================================== // // FullScreen // Adds fullscreen functionality // // ========================================================================== ;(function (document, $) { 'use strict'; // Collection of methods supported by user browser var fn = (function () { var fnMap = [ [ 'requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', ], // new WebKit [ 'webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', ], // old WebKit (Safari 5.1) [ 'webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror', ], [ 'mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', ], [ 'msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', ], ]; var val; var ret = {}; var i, j; for (i = 0; i < fnMap.length; i++) { val = fnMap[i]; if (val && val[1] in document) { for (j = 0; j < val.length; j++) { ret[fnMap[0][j]] = val[j]; } return ret; } } return false; })(); // If browser does not have Full Screen API, then simply unset default button template and stop if (!fn) { if ($ && $.fancybox) { $.fancybox.defaults.btnTpl.fullScreen = false; } return; } var FullScreen = { request: function (elem) { elem = elem || document.documentElement; elem[fn.requestFullscreen](elem.ALLOW_KEYBOARD_INPUT); }, exit: function () { document[fn.exitFullscreen](); }, toggle: function (elem) { elem = elem || document.documentElement; if (this.isFullscreen()) { this.exit(); } else { this.request(elem); } }, isFullscreen: function () { return Boolean(document[fn.fullscreenElement]); }, enabled: function () { return Boolean(document[fn.fullscreenEnabled]); }, }; $.extend(true, $.fancybox.defaults, { btnTpl: { fullScreen: '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fullscreen" title="{{FULL_SCREEN}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M9,12 h22 v16 h-22 v-16 v16 h22 v-16 Z" />' + '</svg>' + '</button>', }, fullScreen: { autoStart: false, }, }); $(document).on({ 'onInit.fb': function (e, instance) { var $container; if (instance && instance.group[instance.currIndex].opts.fullScreen) { $container = instance.$refs.container; $container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function (e) { e.stopPropagation(); e.preventDefault(); FullScreen.toggle($container[0]); }); if (instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true) { FullScreen.request($container[0]); } // Expose API instance.FullScreen = FullScreen; } else if (instance) { instance.$refs.toolbar.find('[data-fancybox-fullscreen]').hide(); } }, 'afterKeydown.fb': function (e, instance, current, keypress, keycode) { // "P" or Spacebar if (instance && instance.FullScreen && keycode === 70) { keypress.preventDefault(); instance.FullScreen.toggle(instance.$refs.container[0]); } }, 'beforeClose.fb': function (instance) { if (instance && instance.FullScreen) { FullScreen.exit(); } }, }); $(document).on(fn.fullscreenchange, function () { var isFullscreen = FullScreen.isFullscreen(), instance = $.fancybox.getInstance(); if (instance) { // If image is zooming, then force to stop and reposition properly if (instance.current && instance.current.type === 'image' && instance.isAnimating) { instance.current.$content.css('transition', 'none'); instance.isAnimating = false; instance.update(true, true, 0); } instance.trigger('onFullscreenChange', isFullscreen); instance.$refs.container.toggleClass('fancybox-is-fullscreen', isFullscreen); } }); }(document, window.jQuery || jQuery)); // ========================================================================== // // Thumbs // Displays thumbnails in a grid // // ========================================================================== ;(function (document, $) { 'use strict'; // Make sure there are default values $.fancybox.defaults = $.extend(true, { btnTpl: { thumbs: '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}">' + '<svg viewBox="0 0 120 120">' + '<path d="M30,30 h14 v14 h-14 Z M50,30 h14 v14 h-14 Z M70,30 h14 v14 h-14 Z M30,50 h14 v14 h-14 Z M50,50 h14 v14 h-14 Z M70,50 h14 v14 h-14 Z M30,70 h14 v14 h-14 Z M50,70 h14 v14 h-14 Z M70,70 h14 v14 h-14 Z" />' + '</svg>' + '</button>', }, thumbs: { autoStart: false, // Display thumbnails on opening hideOnClose: true, // Hide thumbnail grid when closing animation starts parentEl: '.fancybox-container', // Container is injected into this element axis: 'y', // Vertical (y) or horizontal (x) scrolling }, }, $.fancybox.defaults); var FancyThumbs = function (instance) { this.init(instance); }; $.extend(FancyThumbs.prototype, { $button: null, $grid: null, $list: null, isVisible: false, isActive: false, init: function (instance) { var self = this; self.instance = instance; instance.Thumbs = self; // Enable thumbs if at least two group items have thumbnails var first = instance.group[0], second = instance.group[1]; self.opts = instance.group[instance.currIndex].opts.thumbs; self.$button = instance.$refs.toolbar.find('[data-fancybox-thumbs]'); if (self.opts && first && second && ( (first.type == 'image' || first.opts.thumb || first.opts.$thumb) && (second.type == 'image' || second.opts.thumb || second.opts.$thumb) )) { self.$button.show().on('click', function () { self.toggle(); }); self.isActive = true; } else { self.$button.hide(); } }, create: function () { var self = this, instance = self.instance, parentEl = self.opts.parentEl, list, src; self.$grid = $( '<div class="fancybox-thumbs fancybox-thumbs-' + self.opts.axis + '"></div>').appendTo(instance.$refs.container.find(parentEl).addBack().filter(parentEl)); // Build list HTML list = '<ul>'; $.each(instance.group, function (i, item) { src = item.opts.thumb || (item.opts.$thumb ? item.opts.$thumb.attr('src') : null); if (!src && item.type === 'image') { src = item.src; } if (src && src.length) { list += '<li data-index="' + i + '" tabindex="0" class="fancybox-thumbs-loading"><img data-src="' + src + '" /></li>'; } }); list += '</ul>'; self.$list = $(list).appendTo(self.$grid).on('click', 'li', function () { instance.jumpTo($(this).data('index')); }); self.$list.find('img').hide().one('load', function () { var $parent = $(this).parent().removeClass('fancybox-thumbs-loading'), thumbWidth = $parent.outerWidth(), thumbHeight = $parent.outerHeight(), width, height, widthRatio, heightRatio; width = this.naturalWidth || this.width; height = this.naturalHeight || this.height; // Calculate thumbnail dimensions; center vertically and horizontally widthRatio = width / thumbWidth; heightRatio = height / thumbHeight; if (widthRatio >= 1 && heightRatio >= 1) { if (widthRatio > heightRatio) { width = width / heightRatio; height = thumbHeight; } else { width = thumbWidth; height = height / widthRatio; } } $(this).css({ width: Math.floor(width), height: Math.floor(height), 'margin-top': height > thumbHeight ? (Math.floor(thumbHeight * 0.3 - height * 0.3)) : Math.floor(thumbHeight * 0.5 - height * 0.5), 'margin-left': Math.floor(thumbWidth * 0.5 - width * 0.5), }).show(); }).each(function () { this.src = $(this).data('src'); }); if (self.opts.axis === 'x') { self.$list.width(parseInt(self.$grid.css('padding-right')) + (instance.group.length * self.$list.children().eq(0).outerWidth(true)) + 'px'); } }, focus: function (duration) { var self = this, $list = self.$list, thumb, thumbPos; if (self.instance.current) { thumb = $list.children().removeClass('fancybox-thumbs-active').filter('[data-index="' + self.instance.current.index + '"]').addClass('fancybox-thumbs-active'); thumbPos = thumb.position(); // Check if need to scroll to make current thumb visible if (self.opts.axis === 'y' && (thumbPos.top < 0 || thumbPos.top > ($list.height() - thumb.outerHeight()))) { $list.stop().animate({'scrollTop': $list.scrollTop() + thumbPos.top}, duration); } else if (self.opts.axis === 'x' && ( thumbPos.left < $list.parent().scrollLeft() || thumbPos.left > ($list.parent().scrollLeft() + ($list.parent().width() - thumb.outerWidth())) ) ) { $list.parent().stop().animate({'scrollLeft': thumbPos.left}, duration); } } }, update: function () { this.instance.$refs.container.toggleClass('fancybox-show-thumbs', this.isVisible); if (this.isVisible) { if (!this.$grid) { this.create(); } this.instance.trigger('onThumbsShow'); this.focus(0); } else if (this.$grid) { this.instance.trigger('onThumbsHide'); } // Update content position this.instance.update(); }, hide: function () { this.isVisible = false; this.update(); }, show: function () { this.isVisible = true; this.update(); }, toggle: function () { this.isVisible = !this.isVisible; this.update(); }, }); $(document).on({ 'onInit.fb': function (e, instance) { var Thumbs; if (instance && !instance.Thumbs) { Thumbs = new FancyThumbs(instance); if (Thumbs.isActive && Thumbs.opts.autoStart === true) { Thumbs.show(); } } }, 'beforeShow.fb': function (e, instance, item, firstRun) { var Thumbs = instance && instance.Thumbs; if (Thumbs && Thumbs.isVisible) { Thumbs.focus(firstRun ? 0 : 250); } }, 'afterKeydown.fb': function (e, instance, current, keypress, keycode) { var Thumbs = instance && instance.Thumbs; // "G" if (Thumbs && Thumbs.isActive && keycode === 71) { keypress.preventDefault(); Thumbs.toggle(); } }, 'beforeClose.fb': function (e, instance) { var Thumbs = instance && instance.Thumbs; if (Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false) { Thumbs.$grid.hide(); } }, }); }(document, window.jQuery)); //// ========================================================================== // // Share // Displays simple form for sharing current url // // ========================================================================== ;(function (document, $) { 'use strict'; $.extend(true, $.fancybox.defaults, { btnTpl: { share: '<button data-fancybox-share class="fancybox-button fancybox-button--share" title="{{SHARE}}">' + '<svg viewBox="0 0 40 40">' + '<path d="M6,30 C8,18 19,16 23,16 L23,16 L23,10 L33,20 L23,29 L23,24 C19,24 8,27 6,30 Z">' + '</svg>' + '</button>', }, share: { tpl: '<div class="fancybox-share">' + '<h1>{{SHARE}}</h1>' + '<p class="fancybox-share__links">' + '<a class="fancybox-share__button fancybox-share__button--fb" href="https://www.facebook.com/sharer/sharer.php?u={{url}}">' + '<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>' + '<span>Facebook</span>' + '</a>' + '<a class="fancybox-share__button fancybox-share__button--pt" href="https://www.pinterest.com/pin/create/button/?url={{url}}&description={{descr}}&media={{media}}">' + '<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>' + '<span>Pinterest</span>' + '</a>' + '<a class="fancybox-share__button fancybox-share__button--tw" href="https://twitter.com/intent/tweet?url={{url}}&text={{descr}}">' + '<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>' + '<span>Twitter</span>' + '</a>' + '</p>' + '<p><input class="fancybox-share__input" type="text" value="{{url_raw}}" /></p>' + '</div>', }, }); function escapeHtml(string) { var entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''', '/': '/', '`': '`', '=': '=', }; return String(string).replace(/[&<>"'`=\/]/g, function (s) { return entityMap[s]; }); } $(document).on('click', '[data-fancybox-share]', function () { var f = $.fancybox.getInstance(), url, tpl; if (f) { url = f.current.opts.hash === false ? f.current.src : window.location; tpl = f.current.opts.share.tpl.replace(/\{\{media\}\}/g, f.current.type === 'image' ? encodeURIComponent(f.current.src) : '').replace(/\{\{url\}\}/g, encodeURIComponent(url)).replace(/\{\{url_raw\}\}/g, escapeHtml(url)).replace(/\{\{descr\}\}/g, f.$caption ? encodeURIComponent(f.$caption.text()) : ''); $.fancybox.open({ src: f.translate(f, tpl), type: 'html', opts: { animationEffect: 'fade', animationDuration: 250, afterLoad: function (instance, current) { // Opening links in a popup window current.$content.find('.fancybox-share__links a').click(function () { window.open(this.href, 'Share', 'width=550, height=450'); return false; }); }, }, }); } }); }(document, window.jQuery || jQuery)); // ========================================================================== // // Hash // Enables linking to each modal // // ========================================================================== ;(function (document, window, $) { 'use strict'; // Simple $.escapeSelector polyfill (for jQuery prior v3) if (!$.escapeSelector) { $.escapeSelector = function (sel) { var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; var fcssescape = function (ch, asCodePoint) { if (asCodePoint) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if (ch === '\0') { return '\uFFFD'; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice(0, -1) + '\\' + ch.charCodeAt(ch.length - 1).toString(16) + ' '; } // Other potentially-special ASCII characters get backslash-escaped return '\\' + ch; }; return (sel + '').replace(rcssescape, fcssescape); }; } // Create new history entry only once var shouldCreateHistory = true; // Variable containing last hash value set by fancyBox // It will be used to determine if fancyBox needs to close after hash change is detected var currentHash = null; // Throttling the history change var timerID = null; // Get info about gallery name and current index from url function parseUrl() { var hash = window.location.hash.substr(1); var rez = hash.split('-'); var index = rez.length > 1 && /^\+?\d+$/.test(rez[rez.length - 1]) ? parseInt(rez.pop(-1), 10) || 1 : 1; var gallery = rez.join('-'); // Index is starting from 1 if (index < 1) { index = 1; } return { hash: hash, index: index, gallery: gallery, }; } // Trigger click evnt on links to open new fancyBox instance function triggerFromUrl(url) { var $el; if (url.gallery !== '') { // If we can find element matching 'data-fancybox' atribute, then trigger click event for that .. $el = $('[data-fancybox=\'' + $.escapeSelector(url.gallery) + '\']').eq(url.index - 1); if (!$el.length) { // .. if not, try finding element by ID $el = $('#' + $.escapeSelector(url.gallery) + ''); } if ($el.length) { shouldCreateHistory = false; $el.trigger('click'); } } } // Get gallery name from current instance function getGalleryID(instance) { var opts; if (!instance) { return false; } opts = instance.current ? instance.current.opts : instance.opts; return opts.hash || (opts.$orig ? opts.$orig.data('fancybox') : ''); } // Start when DOM becomes ready $(function () { // Check if user has disabled this module if ($.fancybox.defaults.hash === false) { return; } // Update hash when opening/closing fancyBox $(document).on({ 'onInit.fb': function (e, instance) { var url, gallery; if (instance.group[instance.currIndex].opts.hash === false) { return; } url = parseUrl(); gallery = getGalleryID(instance); // Make sure gallery start index matches index from hash if (gallery && url.gallery && gallery == url.gallery) { instance.currIndex = url.index - 1; } }, 'beforeShow.fb': function (e, instance, current) { var gallery; if (!current || current.opts.hash === false) { return; } gallery = getGalleryID(instance); // Update window hash if (gallery && gallery !== '') { if (window.location.hash.indexOf(gallery) < 0) { instance.opts.origHash = window.location.hash; } currentHash = gallery + (instance.group.length > 1 ? '-' + (current.index + 1) : ''); if ('replaceState' in window.history) { if (timerID) { clearTimeout(timerID); } timerID = setTimeout(function () { window.history[shouldCreateHistory ? 'pushState' : 'replaceState']({}, document.title, window.location.pathname + window.location.search + '#' + currentHash); timerID = null; shouldCreateHistory = false; }, 300); } else { window.location.hash = currentHash; } } }, 'beforeClose.fb': function (e, instance, current) { var gallery, origHash; if (timerID) { clearTimeout(timerID); } if (current.opts.hash === false) { return; } gallery = getGalleryID(instance); origHash = instance && instance.opts.origHash ? instance.opts.origHash : ''; // Remove hash from location bar if (gallery && gallery !== '') { if ('replaceState' in history) { window.history.replaceState({}, document.title, window.location.pathname + window.location.search + origHash); } else { window.location.hash = origHash; // Keep original scroll position $(window).scrollTop(instance.scrollTop).scrollLeft(instance.scrollLeft); } } currentHash = null; }, }); // Check if need to close after url has changed $(window).on('hashchange.fb', function () { var url = parseUrl(); if ($.fancybox.getInstance()) { if (currentHash && currentHash !== url.gallery + '-' + url.index && !(url.index === 1 && currentHash == url.gallery)) { currentHash = null; $.fancybox.close(); } } else if (url.gallery !== '') { triggerFromUrl(url); } }); // Check current hash and trigger click event on matching element to start fancyBox, if needed setTimeout(function () { triggerFromUrl(parseUrl()); }, 50); }); }(document, window, window.jQuery || jQuery)); ;(function (document, $) { 'use strict'; var prevTime = new Date().getTime(); $(document).on({ 'onInit.fb': function (e, instance, current) { instance.$refs.stage.on( 'mousewheel DOMMouseScroll wheel MozMousePixelScroll', function (e) { var current = instance.current, currTime = new Date().getTime(); if (instance.group.length < 1 || current.opts.wheel === false || (current.opts.wheel === 'auto' && current.type !== 'image')) { return; } e.preventDefault(); e.stopPropagation(); if (current.$slide.hasClass('fancybox-animated')) { return; } e = e.originalEvent || e; if (currTime - prevTime < 250) { return; } prevTime = currTime; instance[(-e.deltaY || -e.deltaX || e.wheelDelta || -e.detail) < 0 ? 'next' : 'previous'](); }); }, }); }(document, window.jQuery || jQuery));