// ================================================== // 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 : '', // 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 : '', // 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 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.
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 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 `` 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 = $('
').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; }); }, // 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( '

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 ( 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 = $('
').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, '' ); 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 = $('
') .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 = $('') .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 = $(''); 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 = $('
') .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 = $( '
' ).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 = $('
').append( $.trim( content ) ).contents(); // If we have text node, then add wrapping element to make vertical alignment work if ( content[0].nodeType === 3 ) { content = $('
').html( content ); } } // If "filter" option is provided, then filter content if ( slide.opts.filter ) { content = $('
').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' ) { $( '
' ).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 ); } return; }; 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 : '' }, 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 : '' }, 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 : '' }, 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 = $('
').appendTo( instance.$refs.container.find( parentEl ).addBack().filter( parentEl ) ); // Build list HTML list = '
    '; $.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 += '
  • '; } }); list += '
'; 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 : '' }, share : { tpl : '
' + '

{{SHARE}}

' + '' + '

' + '
' } }); 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 ));