// ================================================== // fancyBox v3.3.5 // // Licensed GPLv3 for open source use // or fancyBox Commercial License for commercial use // // http://fancyapps.com/fancybox/ // Copyright 2018 fancyApps // // ================================================== (function (window, document, $, undefined) { "use strict"; window.console = window.console || { info: function (stuff) { } }; // If there's no jQuery, fancyBox can't work // ========================================= if (!$) { return; } // Check if fancyBox is already initialized // ======================================== if ($.fn.fancybox) { console.info("fancyBox already initialized"); return; } // Private default settings // ======================== var defaults = { // Enable infinite gallery navigation loop: false, // Horizontal space between slides gutter: 50, // Enable keyboard navigation keyboard: true, // Should display navigation arrows at the screen edges arrows: true, // Should display counter at the top left corner infobar: true, // Should display close button (using `btnTpl.smallBtn` template) over the content // Can be true, false, "auto" // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items smallBtn: "auto", // Should display toolbar (buttons at the top) // Can be true, false, "auto" // If "auto" - will be automatically hidden if "smallBtn" is enabled toolbar: "auto", // 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: [ "zoom", //"share", //"slideShow", //"fullScreen", //"download", "thumbs", "close" ], // Detect "idle" time in seconds idleTime: 3, // 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 // true - wait for image to load and then display; // false - display thumbnail and load the full-sized image over top, // requires predefined image dimensions (`data-width` and `data-height` attributes) preload: false }, 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: '', // 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: 366, // 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: '", // Loading indicator template spinnerTpl: '
', // Error message template errorTpl: '

{{ERROR}}

', btnTpl: { download: '' + '' + '' + "" + "", zoom: '", close: '", // This small close button will be appended to your html/inline/ajax content by default, // if "smallBtn" option is not set to false smallBtn: '', // Arrows arrowLeft: '' + '' + '' + "" + "", arrowRight: '' + '' + '' + "" + "" }, // 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; // if you have not changed the layout, then most likely you need to use `clickSlide` option 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, 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.
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.
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 el = document.createElement("fakeelement"), t; 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; }; // Exclude array (`buttons`) options from deep merging // =================================================== var mergeOpts = function (opts1, opts2) { var rez = $.extend(true, {}, opts1, opts2); $.each(opts2, function (key, value) { if ($.isArray(value)) { rez[key] = value; } }); return rez; }; // Class definition // ================ var FancyBox = function (content, opts, index) { var self = this; self.opts = mergeOpts({ index: index }, $.fancybox.defaults); if ($.isPlainObject(opts)) { self.opts = mergeOpts(self.opts, opts); } if ($.fancybox.isMobile) { self.opts = mergeOpts(self.opts, self.opts.mobile); } self.id = self.opts.id || ++called; self.currIndex = parseInt(self.opts.index, 10) || 0; self.prevIndex = null; self.prevPos = null; self.currPos = 0; self.firstRun = true; // All group items self.group = []; // Existing slides (for current, next and previous gallery items) self.slides = {}; // Create group elements self.addContent(content); if (!self.group.length) { return; } // Save last active element self.$lastFocus = $(document.activeElement).trigger("blur"); 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; // Hide scrollbars // =============== if (!$.fancybox.getInstance() && firstItemOpts.hideScrollbar !== false) { $("body").addClass("fancybox-active"); if (!$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight) { if (scrollbarWidth === undefined) { $scrollDiv = $('
').appendTo("body"); scrollbarWidth = $.fancybox.scrollbarWidth = $scrollDiv[0].offsetWidth - $scrollDiv[0].clientWidth; $scrollDiv.remove(); } $("head").append( '" ); $("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; }); }, // Populate current group with fresh content // Check if each object has valid type and content // =============================================== addContent: function (content) { var self = this, items = $.makeArray(content), thumbs; $.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); // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'` opts = $item.data() || {}; opts = $.extend(true, {}, opts, opts.options); // Here we store clicked element opts.$orig = $item; obj.src = self.opts.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( '

Hi!

' ); 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 ((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.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 = "iframe"; } else if (src.charAt(0) === "#") { type = "inline"; } } if (type) { obj.type = type; } else { self.trigger("objectNeedsType", obj); } if (!obj.contentType) { obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type; } // Step 3 - Some adjustments // ========================= obj.index = self.group.length; if (obj.opts.smallBtn == "auto") { obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1; } if (obj.opts.toolbar === "auto") { obj.opts.toolbar = !obj.opts.smallBtn; } // Find thumbnail image if (obj.opts.$trigger && obj.index === self.opts.index) { obj.opts.$thumb = obj.opts.$trigger.find("img:first"); } if ((!obj.opts.$thumb || !obj.opts.$thumb.length) && obj.opts.$orig) { obj.opts.$thumb = obj.opts.$orig.find("img:first"); } // "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 (obj.type === "ajax") { srcParts = src.split(/\s+/, 2); if (srcParts.length > 1) { obj.src = srcParts.shift(); obj.opts.filter = srcParts.shift(); } } // 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); }); // Update controls if gallery is already opened if (Object.keys(self.slides).length) { self.updateControls(); // Update thumbnails, if needed thumbs = self.Thumbs; if (thumbs && thumbs.isActive) { thumbs.create(); thumbs.focus(); } } }, // 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("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function (e) { e.stopPropagation(); e.preventDefault(); self.previous(); }) .on("touchstart.fb-next click.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(); }, $.fancybox.isMobile ? 600 : 250); } }); // 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(); } }); // 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.ctrlKey || e.altKey || e.shiftKey || $(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) { var self = this, groupLen = self.group.length, firstRun, loop, current, previous, canvasWidth, currentPos, transitionProps; if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { return; } pos = parseInt(pos, 10); // Should loop? loop = self.current ? self.current.opts.loop : self.opts.loop; if (!loop && (pos < 0 || pos >= groupLen)) { return false; } firstRun = self.firstRun = !Object.keys(self.slides).length; 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"); // Validate duration length 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"); forceRedraw(self.$refs.container); // Make current slide visible current.$slide.addClass("fancybox-slide--previous"); // Attempt to load content into slide; // at this point image would start loading, but inline/html content would load immediately self.loadSlide(current); current.$slide.removeClass("fancybox-slide--previous").addClass("fancybox-slide--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, $slide, index; index = pos % self.group.length; index = index < 0 ? self.group.length + index : index; if (!self.slides[pos] && self.group[index]) { $slide = $('
').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; // x and y values should be relative to the slide // ============================================== scaleToActual: function (x, y, duration) { var self = this, current = self.current, $content = current.$content, canvasWidth = $.fancybox.getTranslate(current.$slide).width, canvasHeight = $.fancybox.getTranslate(current.$slide).height, newImgWidth = current.width, newImgHeight = current.height, imgPos, posX, posY, scaleX, scaleY; if (self.isAnimating || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { return; } $.fancybox.stop($content); self.isAnimating = true; x = x === undefined ? canvasWidth * 0.5 : x; y = y === undefined ? canvasHeight * 0.5 : y; imgPos = $.fancybox.getTranslate($content); imgPos.top -= $.fancybox.getTranslate(current.$slide).top; imgPos.left -= $.fancybox.getTranslate(current.$slide).left; 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( $content, { 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, current = self.current, $content = current.$content, end; if (self.isAnimating || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { return; } $.fancybox.stop($content); self.isAnimating = true; end = self.getFitPos(current); self.updateCursor(end.width, end.height); $.fancybox.animate( $content, { top: end.top, left: end.left, scaleX: end.width / $content.width(), scaleY: end.height / $content.height() }, duration || 330, function () { self.isAnimating = false; } ); }, // Calculate image size to fit inside viewport // =========================================== getFitPos: function (slide) { var self = this, $content = slide.$content, width = slide.width || slide.opts.width, height = slide.height || slide.opts.height, maxWidth, maxHeight, minRatio, margin, aspectRatio, rez = {}; if (!slide.isLoaded || !$content || !$content.length) { return false; } margin = { top: parseInt(slide.$slide.css("paddingTop"), 10), right: parseInt(slide.$slide.css("paddingRight"), 10), bottom: parseInt(slide.$slide.css("paddingBottom"), 10), left: parseInt(slide.$slide.css("paddingLeft"), 10) }; // We can not use $slide width here, because it can have different diemensions while in transiton maxWidth = parseInt(self.$refs.stage.width(), 10) - (margin.left + margin.right); maxHeight = parseInt(self.$refs.stage.height(), 10) - (margin.top + margin.bottom); if (!width || !height) { width = maxWidth; height = maxHeight; } minRatio = Math.min(1, maxWidth / width, maxHeight / height); // Use floor rounding to make sure it really fits width = Math.floor(minRatio * width); height = Math.floor(minRatio * height); if (slide.type === "image") { rez.top = Math.floor((maxHeight - height) * 0.5) + margin.top; rez.left = Math.floor((maxWidth - width) * 0.5) + margin.left; } else if (slide.contentType === "video") { // Force aspect ratio for the video // "I say the whole world must learn of our peaceful ways… by force!" aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9; if (height > width / aspectRatio) { height = width / aspectRatio; } else if (width > height * aspectRatio) { width = height * aspectRatio; } } rez.width = width; rez.height = height; return rez; }, // 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, $content = slide && slide.$content, width = slide.width || slide.opts.width, height = slide.height || slide.opts.height; if ($content && (width || height || slide.contentType === "video") && !slide.hasError) { $.fancybox.stop($content); $.fancybox.setTranslate($content, self.getFitPos(slide)); if (slide.pos === self.currPos) { self.isAnimating = false; self.updateCursor(); } } slide.$slide.trigger("refresh"); self.$refs.toolbar.toggleClass("compensate-for-scrollbar", slide.$slide.get(0).scrollHeight > slide.$slide.get(0).clientHeight); 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, current = self.current, $container = self.$refs.container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut"), isZoomable; if (!current || self.isClosing) { return; } isZoomable = self.isZoomable(); $container.toggleClass("fancybox-is-zoomable", isZoomable); $("[data-fancybox-zoom]").prop("disabled", !isZoomable); // Set cursor to zoom in/out if click event is 'zoom' if ( isZoomable && (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) === "zoom")) ) { if (self.isScaledDown(nextWidth, nextHeight)) { // If image is scaled down, then, obviously, it can be zoomed to full size $container.addClass("fancybox-can-zoomIn"); } else { if (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 (current.opts.touch && current.contentType !== "video") { $container.addClass("fancybox-can-drag"); } }, // Check if current slide is zoomable // ================================== isZoomable: function () { var self = this, current = self.current, fitPos; // Assume that slide is zoomable if: // - image is still loading // - actual size of the image is smaller than available area if (current && !self.isClosing && current.type === "image" && !current.hasError) { if (!current.isLoaded) { return true; } 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 (nextWidth, nextHeight) { var self = this, rez = false, current = self.current, $content = current.$content; if (nextWidth !== undefined && nextHeight !== undefined) { rez = nextWidth < current.width && nextHeight < current.height; } else if ($content) { rez = $.fancybox.getTranslate($content); rez = rez.width < current.width && rez.height < current.height; } return rez; }, // Check if image dimensions exceed parent element // =============================================== canPan: function () { var self = this, rez = false, current = self.current, $content; if (current.type === "image" && ($content = current.$content) && !current.hasError) { rez = self.getFitPos(current); rez = Math.abs($content.width() - rez.width) > 1 || Math.abs($content.height() - rez.height) > 1; } return rez; }, // Load content into the slide // =========================== loadSlide: function (slide) { var self = this, type, $slide, ajaxLoad; if (slide.isLoading || slide.isLoaded) { return; } slide.isLoading = true; self.trigger("beforeLoad", slide); type = slide.type; $slide = slide.$slide; $slide .off("refresh") .trigger("onReset") .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 "video": self.setContent( slide, '