/** * * UI on top of main sliding area (caption, arrows, close button, etc.). * Built just using public methods/properties of PhotoSwipe. * */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.PhotoSwipeUI_Default = factory(); } })(this, function () { 'use strict'; var PhotoSwipeUI_Default = function(pswp, framework) { var ui = this; var _overlayUIUpdated = false, _controlsVisible = true, _fullscrenAPI, _controls, _captionContainer, _fakeCaptionContainer, _indexIndicator, _shareButton, _shareModal, _shareModalHidden = true, _initalCloseOnScrollValue, _isIdle, _listen, _loadingIndicator, _loadingIndicatorHidden, _loadingIndicatorTimeout, _galleryHasOneSlide, _options, _defaultUIOptions = { barsSize: {top:44, bottom:'auto'}, closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'], timeToIdle: 4000, timeToIdleOutside: 1000, loadingIndicatorDelay: 1000, // 2s addCaptionHTMLFn: function(item, captionEl /*, isFake */) { if(!item.title) { captionEl.children[0].innerHTML = ''; return false; } captionEl.children[0].innerHTML = item.title; return true; }, closeEl:true, captionEl: true, fullscreenEl: true, zoomEl: false, shareEl: false, counterEl: true, arrowEl: true, preloaderEl: true, tapToClose: false, tapToToggleControls: true, clickToCloseNonZoomable: true, shareButtons: [ {id:'facebook', label:'Share on Facebook', url:'https://www.facebook.com/sharer/sharer.php?u={{url}}'}, {id:'twitter', label:'Tweet', url:'https://twitter.com/intent/tweet?text={{text}}&url={{url}}'}, {id:'pinterest', label:'Pin it', url:'http://www.pinterest.com/pin/create/button/'+ '?url={{url}}&media={{image_url}}&description={{text}}'}, {id:'download', label:'Download image', url:'{{raw_image_url}}', download:true} ], getImageURLForShare: function( /* shareButtonData */ ) { return pswp.currItem.src || ''; }, getPageURLForShare: function( /* shareButtonData */ ) { return window.location.href; }, getTextForShare: function( /* shareButtonData */ ) { return pswp.currItem.title || ''; }, indexIndicatorSep: ' / ', fitControlsWidth: 1200 }, _blockControlsTap, _blockControlsTapTimeout; var _onControlsTap = function(e) { if(_blockControlsTap) { return true; } e = e || window.event; if(_options.timeToIdle && _options.mouseUsed && !_isIdle) { // reset idle timer _onIdleMouseMove(); } var target = e.target || e.srcElement, uiElement, clickedClass = target.getAttribute('class') || '', found; for(var i = 0; i < _uiElements.length; i++) { uiElement = _uiElements[i]; if(uiElement.onTap && clickedClass.indexOf('pswp__' + uiElement.name ) > -1 ) { uiElement.onTap(); found = true; } } if(found) { if(e.stopPropagation) { e.stopPropagation(); } _blockControlsTap = true; // Some versions of Android don't prevent ghost click event // when preventDefault() was called on touchstart and/or touchend. // // This happens on v4.3, 4.2, 4.1, // older versions strangely work correctly, // but just in case we add delay on all of them) var tapDelay = framework.features.isOldAndroid ? 600 : 30; _blockControlsTapTimeout = setTimeout(function() { _blockControlsTap = false; }, tapDelay); } }, _fitControlsInViewport = function() { return !pswp.likelyTouchDevice || _options.mouseUsed || screen.width > _options.fitControlsWidth; }, _togglePswpClass = function(el, cName, add) { framework[ (add ? 'add' : 'remove') + 'Class' ](el, 'pswp__' + cName); }, // add class when there is just one item in the gallery // (by default it hides left/right arrows and 1ofX counter) _countNumItems = function() { var hasOneSlide = (_options.getNumItemsFn() === 1); if(hasOneSlide !== _galleryHasOneSlide) { _togglePswpClass(_controls, 'ui--one-slide', hasOneSlide); _galleryHasOneSlide = hasOneSlide; } }, _toggleShareModalClass = function() { _togglePswpClass(_shareModal, 'share-modal--hidden', _shareModalHidden); }, _toggleShareModal = function() { _shareModalHidden = !_shareModalHidden; if(!_shareModalHidden) { _toggleShareModalClass(); setTimeout(function() { if(!_shareModalHidden) { framework.addClass(_shareModal, 'pswp__share-modal--fade-in'); } }, 30); } else { framework.removeClass(_shareModal, 'pswp__share-modal--fade-in'); setTimeout(function() { if(_shareModalHidden) { _toggleShareModalClass(); } }, 300); } if(!_shareModalHidden) { _updateShareURLs(); } return false; }, _openWindowPopup = function(e) { e = e || window.event; var target = e.target || e.srcElement; pswp.shout('shareLinkClick', e, target); if(!target.href) { return false; } if( target.hasAttribute('download') ) { return true; } window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,'+ 'location=yes,width=550,height=420,top=100,left=' + (window.screen ? Math.round(screen.width / 2 - 275) : 100) ); if(!_shareModalHidden) { _toggleShareModal(); } return false; }, _updateShareURLs = function() { var shareButtonOut = '', shareButtonData, shareURL, image_url, page_url, share_text; for(var i = 0; i < _options.shareButtons.length; i++) { shareButtonData = _options.shareButtons[i]; image_url = _options.getImageURLForShare(shareButtonData); page_url = _options.getPageURLForShare(shareButtonData); share_text = _options.getTextForShare(shareButtonData); shareURL = shareButtonData.url.replace('{{url}}', encodeURIComponent(page_url) ) .replace('{{image_url}}', encodeURIComponent(image_url) ) .replace('{{raw_image_url}}', image_url ) .replace('{{text}}', encodeURIComponent(share_text) ); shareButtonOut += '' + shareButtonData.label + ''; if(_options.parseShareButtonOut) { shareButtonOut = _options.parseShareButtonOut(shareButtonData, shareButtonOut); } } _shareModal.children[0].innerHTML = shareButtonOut; _shareModal.children[0].onclick = _openWindowPopup; }, _hasCloseClass = function(target) { for(var i = 0; i < _options.closeElClasses.length; i++) { if( framework.hasClass(target, 'pswp__' + _options.closeElClasses[i]) ) { return true; } } }, _idleInterval, _idleTimer, _idleIncrement = 0, _onIdleMouseMove = function() { clearTimeout(_idleTimer); _idleIncrement = 0; if(_isIdle) { ui.setIdle(false); } }, _onMouseLeaveWindow = function(e) { e = e ? e : window.event; var from = e.relatedTarget || e.toElement; if (!from || from.nodeName === 'HTML') { clearTimeout(_idleTimer); _idleTimer = setTimeout(function() { ui.setIdle(true); }, _options.timeToIdleOutside); } }, _setupFullscreenAPI = function() { if(_options.fullscreenEl && !framework.features.isOldAndroid) { if(!_fullscrenAPI) { _fullscrenAPI = ui.getFullscreenAPI(); } if(_fullscrenAPI) { framework.bind(document, _fullscrenAPI.eventK, ui.updateFullscreen); ui.updateFullscreen(); framework.addClass(pswp.template, 'pswp--supports-fs'); } else { framework.removeClass(pswp.template, 'pswp--supports-fs'); } } }, _setupLoadingIndicator = function() { // Setup loading indicator if(_options.preloaderEl) { _toggleLoadingIndicator(true); _listen('beforeChange', function() { clearTimeout(_loadingIndicatorTimeout); // display loading indicator with delay _loadingIndicatorTimeout = setTimeout(function() { if(pswp.currItem && pswp.currItem.loading) { if( !pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWidth) ) { // show preloader if progressive loading is not enabled, // or image width is not defined yet (because of slow connection) _toggleLoadingIndicator(false); // items-controller.js function allowProgressiveImg } } else { _toggleLoadingIndicator(true); // hide preloader } }, _options.loadingIndicatorDelay); }); _listen('imageLoadComplete', function(index, item) { if(pswp.currItem === item) { _toggleLoadingIndicator(true); } }); } }, _toggleLoadingIndicator = function(hide) { if( _loadingIndicatorHidden !== hide ) { _togglePswpClass(_loadingIndicator, 'preloader--active', !hide); _loadingIndicatorHidden = hide; } }, _applyNavBarGaps = function(item) { var gap = item.vGap; if( _fitControlsInViewport() ) { var bars = _options.barsSize; if(_options.captionEl && bars.bottom === 'auto') { if(!_fakeCaptionContainer) { _fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake'); _fakeCaptionContainer.appendChild( framework.createEl('pswp__caption__center') ); _controls.insertBefore(_fakeCaptionContainer, _captionContainer); framework.addClass(_controls, 'pswp__ui--fit'); } if( _options.addCaptionHTMLFn(item, _fakeCaptionContainer, true) ) { var captionSize = _fakeCaptionContainer.clientHeight; gap.bottom = parseInt(captionSize,10) || 44; } else { gap.bottom = bars.top; // if no caption, set size of bottom gap to size of top } } else { gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom; } // height of top bar is static, no need to calculate it gap.top = bars.top; } else { gap.top = gap.bottom = 0; } }, _setupIdle = function() { // Hide controls when mouse is used if(_options.timeToIdle) { _listen('mouseUsed', function() { framework.bind(document, 'mousemove', _onIdleMouseMove); framework.bind(document, 'mouseout', _onMouseLeaveWindow); _idleInterval = setInterval(function() { _idleIncrement++; if(_idleIncrement === 2) { ui.setIdle(true); } }, _options.timeToIdle / 2); }); } }, _setupHidingControlsDuringGestures = function() { // Hide controls on vertical drag _listen('onVerticalDrag', function(now) { if(_controlsVisible && now < 0.95) { ui.hideControls(); } else if(!_controlsVisible && now >= 0.95) { ui.showControls(); } }); // Hide controls when pinching to close var pinchControlsHidden; _listen('onPinchClose' , function(now) { if(_controlsVisible && now < 0.9) { ui.hideControls(); pinchControlsHidden = true; } else if(pinchControlsHidden && !_controlsVisible && now > 0.9) { ui.showControls(); } }); _listen('zoomGestureEnded', function() { pinchControlsHidden = false; if(pinchControlsHidden && !_controlsVisible) { ui.showControls(); } }); }; var _uiElements = [ { name: 'caption', option: 'captionEl', onInit: function(el) { _captionContainer = el; } }, { name: 'share-modal', option: 'shareEl', onInit: function(el) { _shareModal = el; }, onTap: function() { _toggleShareModal(); } }, { name: 'button--share', option: 'shareEl', onInit: function(el) { _shareButton = el; }, onTap: function() { _toggleShareModal(); } }, { name: 'button--zoom', option: 'zoomEl', onTap: pswp.toggleDesktopZoom }, { name: 'counter', option: 'counterEl', onInit: function(el) { _indexIndicator = el; } }, { name: 'button--close', option: 'closeEl', onTap: pswp.close }, { name: 'button--arrow--left', option: 'arrowEl', onTap: pswp.prev }, { name: 'button--arrow--right', option: 'arrowEl', onTap: pswp.next }, { name: 'button--fs', option: 'fullscreenEl', onTap: function() { if(_fullscrenAPI.isFullscreen()) { _fullscrenAPI.exit(); } else { _fullscrenAPI.enter(); } } }, { name: 'preloader', option: 'preloaderEl', onInit: function(el) { _loadingIndicator = el; } } ]; var _setupUIElements = function() { var item, classAttr, uiElement; var loopThroughChildElements = function(sChildren) { if(!sChildren) { return; } var l = sChildren.length; for(var i = 0; i < l; i++) { item = sChildren[i]; classAttr = item.className; for(var a = 0; a < _uiElements.length; a++) { uiElement = _uiElements[a]; if(classAttr.indexOf('pswp__' + uiElement.name) > -1 ) { if( _options[uiElement.option] ) { // if element is not disabled from options framework.removeClass(item, 'pswp__element--disabled'); if(uiElement.onInit) { uiElement.onInit(item); } //item.style.display = 'block'; } else { framework.addClass(item, 'pswp__element--disabled'); //item.style.display = 'none'; } } } } }; loopThroughChildElements(_controls.children); var topBar = framework.getChildByClass(_controls, 'pswp__top-bar'); if(topBar) { loopThroughChildElements( topBar.children ); } }; ui.init = function() { // extend options framework.extend(pswp.options, _defaultUIOptions, true); // create local link for fast access _options = pswp.options; // find pswp__ui element _controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui'); // create local link _listen = pswp.listen; _setupHidingControlsDuringGestures(); // update controls when slides change _listen('beforeChange', ui.update); // toggle zoom on double-tap _listen('doubleTap', function(point) { var initialZoomLevel = pswp.currItem.initialZoomLevel; if(pswp.getZoomLevel() !== initialZoomLevel) { pswp.zoomTo(initialZoomLevel, point, 333); } else { pswp.zoomTo(_options.getDoubleTapZoom(false, pswp.currItem), point, 333); } }); // Allow text selection in caption _listen('preventDragEvent', function(e, isDown, preventObj) { var t = e.target || e.srcElement; if( t && t.getAttribute('class') && e.type.indexOf('mouse') > -1 && ( t.getAttribute('class').indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) ) ) { preventObj.prevent = false; } }); // bind events for UI _listen('bindEvents', function() { framework.bind(_controls, 'pswpTap click', _onControlsTap); framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap); if(!pswp.likelyTouchDevice) { framework.bind(pswp.scrollWrap, 'mouseover', ui.onMouseOver); } }); // unbind events for UI _listen('unbindEvents', function() { if(!_shareModalHidden) { _toggleShareModal(); } if(_idleInterval) { clearInterval(_idleInterval); } framework.unbind(document, 'mouseout', _onMouseLeaveWindow); framework.unbind(document, 'mousemove', _onIdleMouseMove); framework.unbind(_controls, 'pswpTap click', _onControlsTap); framework.unbind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap); framework.unbind(pswp.scrollWrap, 'mouseover', ui.onMouseOver); if(_fullscrenAPI) { framework.unbind(document, _fullscrenAPI.eventK, ui.updateFullscreen); if(_fullscrenAPI.isFullscreen()) { _options.hideAnimationDuration = 0; _fullscrenAPI.exit(); } _fullscrenAPI = null; } }); // clean up things when gallery is destroyed _listen('destroy', function() { if(_options.captionEl) { if(_fakeCaptionContainer) { _controls.removeChild(_fakeCaptionContainer); } framework.removeClass(_captionContainer, 'pswp__caption--empty'); } if(_shareModal) { _shareModal.children[0].onclick = null; } framework.removeClass(_controls, 'pswp__ui--over-close'); framework.addClass( _controls, 'pswp__ui--hidden'); ui.setIdle(false); }); if(!_options.showAnimationDuration) { framework.removeClass( _controls, 'pswp__ui--hidden'); } _listen('initialZoomIn', function() { if(_options.showAnimationDuration) { framework.removeClass( _controls, 'pswp__ui--hidden'); } }); _listen('initialZoomOut', function() { framework.addClass( _controls, 'pswp__ui--hidden'); }); _listen('parseVerticalMargin', _applyNavBarGaps); _setupUIElements(); if(_options.shareEl && _shareButton && _shareModal) { _shareModalHidden = true; } _countNumItems(); _setupIdle(); _setupFullscreenAPI(); _setupLoadingIndicator(); }; ui.setIdle = function(isIdle) { _isIdle = isIdle; _togglePswpClass(_controls, 'ui--idle', isIdle); }; ui.update = function() { // Don't update UI if it's hidden if(_controlsVisible && pswp.currItem) { ui.updateIndexIndicator(); if(_options.captionEl) { _options.addCaptionHTMLFn(pswp.currItem, _captionContainer); _togglePswpClass(_captionContainer, 'caption--empty', !pswp.currItem.title); } _overlayUIUpdated = true; } else { _overlayUIUpdated = false; } if(!_shareModalHidden) { _toggleShareModal(); } _countNumItems(); }; ui.updateFullscreen = function(e) { if(e) { // some browsers change window scroll position during the fullscreen // so PhotoSwipe updates it just in case setTimeout(function() { pswp.setScrollOffset( 0, framework.getScrollY() ); }, 50); } // toogle pswp--fs class on root element framework[ (_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class' ](pswp.template, 'pswp--fs'); }; ui.updateIndexIndicator = function() { if(_options.counterEl) { _indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) + _options.indexIndicatorSep + _options.getNumItemsFn(); } }; ui.onGlobalTap = function(e) { e = e || window.event; var target = e.target || e.srcElement; if(_blockControlsTap) { return; } if(e.detail && e.detail.pointerType === 'mouse') { // close gallery if clicked outside of the image if(_hasCloseClass(target)) { pswp.close(); return; } if(framework.hasClass(target, 'pswp__img')) { if(pswp.getZoomLevel() === 1 && pswp.getZoomLevel() <= pswp.currItem.fitRatio) { if(_options.clickToCloseNonZoomable) { pswp.close(); } } else { pswp.toggleDesktopZoom(e.detail.releasePoint); } } } else { // tap anywhere (except buttons) to toggle visibility of controls if(_options.tapToToggleControls) { if(_controlsVisible) { ui.hideControls(); } else { ui.showControls(); } } // tap to close gallery if(_options.tapToClose && (framework.hasClass(target, 'pswp__img') || _hasCloseClass(target)) ) { pswp.close(); return; } } }; ui.onMouseOver = function(e) { e = e || window.event; var target = e.target || e.srcElement; // add class when mouse is over an element that should close the gallery _togglePswpClass(_controls, 'ui--over-close', _hasCloseClass(target)); }; ui.hideControls = function() { framework.addClass(_controls,'pswp__ui--hidden'); _controlsVisible = false; }; ui.showControls = function() { _controlsVisible = true; if(!_overlayUIUpdated) { ui.update(); } framework.removeClass(_controls,'pswp__ui--hidden'); }; ui.supportsFullscreen = function() { var d = document; return !!(d.exitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen || d.msExitFullscreen); }; ui.getFullscreenAPI = function() { var dE = document.documentElement, api, tF = 'fullscreenchange'; if (dE.requestFullscreen) { api = { enterK: 'requestFullscreen', exitK: 'exitFullscreen', elementK: 'fullscreenElement', eventK: tF }; } else if(dE.mozRequestFullScreen ) { api = { enterK: 'mozRequestFullScreen', exitK: 'mozCancelFullScreen', elementK: 'mozFullScreenElement', eventK: 'moz' + tF }; } else if(dE.webkitRequestFullscreen) { api = { enterK: 'webkitRequestFullscreen', exitK: 'webkitExitFullscreen', elementK: 'webkitFullscreenElement', eventK: 'webkit' + tF }; } else if(dE.msRequestFullscreen) { api = { enterK: 'msRequestFullscreen', exitK: 'msExitFullscreen', elementK: 'msFullscreenElement', eventK: 'MSFullscreenChange' }; } if(api) { api.enter = function() { // disable close-on-scroll in fullscreen _initalCloseOnScrollValue = _options.closeOnScroll; _options.closeOnScroll = false; if(this.enterK === 'webkitRequestFullscreen') { pswp.template[this.enterK]( Element.ALLOW_KEYBOARD_INPUT ); } else { return pswp.template[this.enterK](); } }; api.exit = function() { _options.closeOnScroll = _initalCloseOnScrollValue; return document[this.exitK](); }; api.isFullscreen = function() { return document[this.elementK]; }; } return api; }; }; return PhotoSwipeUI_Default; });