');
}
item.inlineElement = el;
return el;
}
mfp.updateStatus('ready');
mfp._parseMarkup(template, {}, item);
return template;
}
}
});
/*>>inline*/
/*>>ajax*/
var AJAX_NS = 'ajax',
_ajaxCur,
_removeAjaxCursor = function() {
if(_ajaxCur) {
$(document.body).removeClass(_ajaxCur);
}
},
_destroyAjaxRequest = function() {
_removeAjaxCursor();
if(mfp.req) {
mfp.req.abort();
}
};
$.magnificPopup.registerModule(AJAX_NS, {
options: {
settings: null,
cursor: 'mfp-ajax-cur',
tError: '
The content could not be loaded.'
},
proto: {
initAjax: function() {
mfp.types.push(AJAX_NS);
_ajaxCur = mfp.st.ajax.cursor;
_mfpOn(CLOSE_EVENT+'.'+AJAX_NS, _destroyAjaxRequest);
_mfpOn('BeforeChange.' + AJAX_NS, _destroyAjaxRequest);
},
getAjax: function(item) {
if(_ajaxCur) {
$(document.body).addClass(_ajaxCur);
}
mfp.updateStatus('loading');
var opts = $.extend({
url: item.src,
success: function(data, textStatus, jqXHR) {
var temp = {
data:data,
xhr:jqXHR
};
_mfpTrigger('ParseAjax', temp);
mfp.appendContent( $(temp.data), AJAX_NS );
item.finished = true;
_removeAjaxCursor();
mfp._setFocus();
setTimeout(function() {
mfp.wrap.addClass(READY_CLASS);
}, 16);
mfp.updateStatus('ready');
_mfpTrigger('AjaxContentAdded');
},
error: function() {
_removeAjaxCursor();
item.finished = item.loadError = true;
mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src));
}
}, mfp.st.ajax.settings);
mfp.req = $.ajax(opts);
return '';
}
}
});
/*>>ajax*/
/*>>image*/
var _imgInterval,
_getTitle = function(item) {
if(item.data && item.data.title !== undefined)
return item.data.title;
var src = mfp.st.image.titleSrc;
if(src) {
if(typeof src === "function") {
return src.call(mfp, item);
} else if(item.el) {
return item.el.attr(src) || '';
}
}
return '';
};
$.magnificPopup.registerModule('image', {
options: {
markup: '
',
cursor: 'mfp-zoom-out-cur',
titleSrc: 'title',
verticalFit: true,
tError: '
The image could not be loaded.'
},
proto: {
initImage: function() {
var imgSt = mfp.st.image,
ns = '.image';
mfp.types.push('image');
_mfpOn(OPEN_EVENT+ns, function() {
if(mfp.currItem.type === 'image' && imgSt.cursor) {
$(document.body).addClass(imgSt.cursor);
}
});
_mfpOn(CLOSE_EVENT+ns, function() {
if(imgSt.cursor) {
$(document.body).removeClass(imgSt.cursor);
}
_window.off('resize' + EVENT_NS);
});
_mfpOn('Resize'+ns, mfp.resizeImage);
if(mfp.isLowIE) {
_mfpOn('AfterChange', mfp.resizeImage);
}
},
resizeImage: function() {
var item = mfp.currItem;
if(!item || !item.img) return;
if(mfp.st.image.verticalFit) {
var decr = 0;
// fix box-sizing in ie7/8
if(mfp.isLowIE) {
decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'),10);
}
item.img.css('max-height', mfp.wH-decr);
}
},
_onImageHasSize: function(item) {
if(item.img) {
item.hasSize = true;
if(_imgInterval) {
clearInterval(_imgInterval);
}
item.isCheckingImgSize = false;
_mfpTrigger('ImageHasSize', item);
if(item.imgHidden) {
if(mfp.content)
mfp.content.removeClass('mfp-loading');
item.imgHidden = false;
}
}
},
/**
* Function that loops until the image has size to display elements that rely on it asap
*/
findImageSize: function(item) {
var counter = 0,
img = item.img[0],
mfpSetInterval = function(delay) {
if(_imgInterval) {
clearInterval(_imgInterval);
}
// decelerating interval that checks for size of an image
_imgInterval = setInterval(function() {
if(img.naturalWidth > 0) {
mfp._onImageHasSize(item);
return;
}
if(counter > 200) {
clearInterval(_imgInterval);
}
counter++;
if(counter === 3) {
mfpSetInterval(10);
} else if(counter === 40) {
mfpSetInterval(50);
} else if(counter === 100) {
mfpSetInterval(500);
}
}, delay);
};
mfpSetInterval(1);
},
getImage: function(item, template) {
var guard = 0,
// image load complete handler
onLoadComplete = function() {
if(item) {
if (item.img[0].complete) {
item.img.off('.mfploader');
if(item === mfp.currItem){
mfp._onImageHasSize(item);
mfp.updateStatus('ready');
}
item.hasSize = true;
item.loaded = true;
_mfpTrigger('ImageLoadComplete');
}
else {
// if image complete check fails 200 times (20 sec), we assume that there was an error.
guard++;
if(guard < 200) {
setTimeout(onLoadComplete,100);
} else {
onLoadError();
}
}
}
},
// image error handler
onLoadError = function() {
if(item) {
item.img.off('.mfploader');
if(item === mfp.currItem){
mfp._onImageHasSize(item);
mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
}
item.hasSize = true;
item.loaded = true;
item.loadError = true;
}
},
imgSt = mfp.st.image;
var el = template.find('.mfp-img');
if(el.length) {
var img = document.createElement('img');
img.className = 'mfp-img';
if(item.el && item.el.find('img').length) {
img.alt = item.el.find('img').attr('alt');
}
item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError);
img.src = item.src;
// without clone() "error" event is not firing when IMG is replaced by new IMG
// TODO: find a way to avoid such cloning
if(el.is('img')) {
item.img = item.img.clone();
}
img = item.img[0];
if(img.naturalWidth > 0) {
item.hasSize = true;
} else if(!img.width) {
item.hasSize = false;
}
}
mfp._parseMarkup(template, {
title: _getTitle(item),
img_replaceWith: item.img
}, item);
mfp.resizeImage();
if(item.hasSize) {
if(_imgInterval) clearInterval(_imgInterval);
if(item.loadError) {
template.addClass('mfp-loading');
mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
} else {
template.removeClass('mfp-loading');
mfp.updateStatus('ready');
}
return template;
}
mfp.updateStatus('loading');
item.loading = true;
if(!item.hasSize) {
item.imgHidden = true;
template.addClass('mfp-loading');
mfp.findImageSize(item);
}
return template;
}
}
});
/*>>image*/
/*>>zoom*/
var hasMozTransform,
getHasMozTransform = function() {
if(hasMozTransform === undefined) {
hasMozTransform = document.createElement('p').style.MozTransform !== undefined;
}
return hasMozTransform;
};
$.magnificPopup.registerModule('zoom', {
options: {
enabled: false,
easing: 'ease-in-out',
duration: 300,
opener: function(element) {
return element.is('img') ? element : element.find('img');
}
},
proto: {
initZoom: function() {
var zoomSt = mfp.st.zoom,
ns = '.zoom',
image;
if(!zoomSt.enabled || !mfp.supportsTransition) {
return;
}
var duration = zoomSt.duration,
getElToAnimate = function(image) {
var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'),
transition = 'all '+(zoomSt.duration/1000)+'s ' + zoomSt.easing,
cssObj = {
position: 'fixed',
zIndex: 9999,
left: 0,
top: 0,
'-webkit-backface-visibility': 'hidden'
},
t = 'transition';
cssObj['-webkit-'+t] = cssObj['-moz-'+t] = cssObj['-o-'+t] = cssObj[t] = transition;
newImg.css(cssObj);
return newImg;
},
showMainContent = function() {
mfp.content.css('visibility', 'visible');
},
openTimeout,
animatedImg;
_mfpOn('BuildControls'+ns, function() {
if(mfp._allowZoom()) {
clearTimeout(openTimeout);
mfp.content.css('visibility', 'hidden');
// Basically, all code below does is clones existing image, puts in on top of the current one and animated it
image = mfp._getItemToZoom();
if(!image) {
showMainContent();
return;
}
animatedImg = getElToAnimate(image);
animatedImg.css( mfp._getOffset() );
mfp.wrap.append(animatedImg);
openTimeout = setTimeout(function() {
animatedImg.css( mfp._getOffset( true ) );
openTimeout = setTimeout(function() {
showMainContent();
setTimeout(function() {
animatedImg.remove();
image = animatedImg = null;
_mfpTrigger('ZoomAnimationEnded');
}, 16); // avoid blink when switching images
}, duration); // this timeout equals animation duration
}, 16); // by adding this timeout we avoid short glitch at the beginning of animation
// Lots of timeouts...
}
});
_mfpOn(BEFORE_CLOSE_EVENT+ns, function() {
if(mfp._allowZoom()) {
clearTimeout(openTimeout);
mfp.st.removalDelay = duration;
if(!image) {
image = mfp._getItemToZoom();
if(!image) {
return;
}
animatedImg = getElToAnimate(image);
}
animatedImg.css( mfp._getOffset(true) );
mfp.wrap.append(animatedImg);
mfp.content.css('visibility', 'hidden');
setTimeout(function() {
animatedImg.css( mfp._getOffset() );
}, 16);
}
});
_mfpOn(CLOSE_EVENT+ns, function() {
if(mfp._allowZoom()) {
showMainContent();
if(animatedImg) {
animatedImg.remove();
}
image = null;
}
});
},
_allowZoom: function() {
return mfp.currItem.type === 'image';
},
_getItemToZoom: function() {
if(mfp.currItem.hasSize) {
return mfp.currItem.img;
} else {
return false;
}
},
// Get element postion relative to viewport
_getOffset: function(isLarge) {
var el;
if(isLarge) {
el = mfp.currItem.img;
} else {
el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem);
}
var offset = el.offset();
var paddingTop = parseInt(el.css('padding-top'),10);
var paddingBottom = parseInt(el.css('padding-bottom'),10);
offset.top -= ( $(window).scrollTop() - paddingTop );
/*
Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa.
*/
var obj = {
width: el.width(),
// fix Zepto height+padding issue
height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop
};
// I hate to do this, but there is no another option
if( getHasMozTransform() ) {
obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)';
} else {
obj.left = offset.left;
obj.top = offset.top;
}
return obj;
}
}
});
/*>>zoom*/
/*>>iframe*/
var IFRAME_NS = 'iframe',
_emptyPage = '//about:blank',
_fixIframeBugs = function(isShowing) {
if(mfp.currTemplate[IFRAME_NS]) {
var el = mfp.currTemplate[IFRAME_NS].find('iframe');
if(el.length) {
// reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug
if(!isShowing) {
el[0].src = _emptyPage;
}
// IE8 black screen bug fix
if(mfp.isIE8) {
el.css('display', isShowing ? 'block' : 'none');
}
}
}
};
$.magnificPopup.registerModule(IFRAME_NS, {
options: {
markup: '
',
srcAction: 'iframe_src',
// we don't care and support only one default type of URL by default
patterns: {
youtube: {
index: 'youtube.com',
id: 'v=',
src: '//www.youtube.com/embed/%id%?autoplay=1'
},
vimeo: {
index: 'vimeo.com/',
id: '/',
src: '//player.vimeo.com/video/%id%?autoplay=1'
},
gmaps: {
index: '//maps.google.',
src: '%id%&output=embed'
}
}
},
proto: {
initIframe: function() {
mfp.types.push(IFRAME_NS);
_mfpOn('BeforeChange', function(e, prevType, newType) {
if(prevType !== newType) {
if(prevType === IFRAME_NS) {
_fixIframeBugs(); // iframe if removed
} else if(newType === IFRAME_NS) {
_fixIframeBugs(true); // iframe is showing
}
}// else {
// iframe source is switched, don't do anything
//}
});
_mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function() {
_fixIframeBugs();
});
},
getIframe: function(item, template) {
var embedSrc = item.src;
var iframeSt = mfp.st.iframe;
$.each(iframeSt.patterns, function() {
if(embedSrc.indexOf( this.index ) > -1) {
if(this.id) {
if(typeof this.id === 'string') {
embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id)+this.id.length, embedSrc.length);
} else {
embedSrc = this.id.call( this, embedSrc );
}
}
embedSrc = this.src.replace('%id%', embedSrc );
return false; // break;
}
});
var dataObj = {};
if(iframeSt.srcAction) {
dataObj[iframeSt.srcAction] = embedSrc;
}
mfp._parseMarkup(template, dataObj, item);
mfp.updateStatus('ready');
return template;
}
}
});
/*>>iframe*/
/*>>gallery*/
/**
* Get looped index depending on number of slides
*/
var _getLoopedId = function(index) {
var numSlides = mfp.items.length;
if(index > numSlides - 1) {
return index - numSlides;
} else if(index < 0) {
return numSlides + index;
}
return index;
},
_replaceCurrTotal = function(text, curr, total) {
return text.replace(/%curr%/gi, curr + 1).replace(/%total%/gi, total);
};
$.magnificPopup.registerModule('gallery', {
options: {
enabled: false,
arrowMarkup: '
',
preload: [0,2],
navigateByImgClick: true,
arrows: true,
tPrev: 'Previous (Left arrow key)',
tNext: 'Next (Right arrow key)',
tCounter: '%curr% of %total%'
},
proto: {
initGallery: function() {
var gSt = mfp.st.gallery,
ns = '.mfp-gallery',
supportsFastClick = Boolean($.fn.mfpFastClick);
mfp.direction = true; // true - next, false - prev
if(!gSt || !gSt.enabled ) return false;
_wrapClasses += ' mfp-gallery';
_mfpOn(OPEN_EVENT+ns, function() {
if(gSt.navigateByImgClick) {
mfp.wrap.on('click'+ns, '.mfp-img', function() {
if(mfp.items.length > 1) {
mfp.next();
return false;
}
});
}
_document.on('keydown'+ns, function(e) {
if (e.keyCode === 37) {
mfp.prev();
} else if (e.keyCode === 39) {
mfp.next();
}
});
});
_mfpOn('UpdateStatus'+ns, function(e, data) {
if(data.text) {
data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length);
}
});
_mfpOn(MARKUP_PARSE_EVENT+ns, function(e, element, values, item) {
var l = mfp.items.length;
values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : '';
});
_mfpOn('BuildControls' + ns, function() {
if(mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) {
var markup = gSt.arrowMarkup,
arrowLeft = mfp.arrowLeft = $( markup.replace(/%title%/gi, gSt.tPrev).replace(/%dir%/gi, 'left') ).addClass(PREVENT_CLOSE_CLASS),
arrowRight = mfp.arrowRight = $( markup.replace(/%title%/gi, gSt.tNext).replace(/%dir%/gi, 'right') ).addClass(PREVENT_CLOSE_CLASS);
var eName = supportsFastClick ? 'mfpFastClick' : 'click';
arrowLeft[eName](function() {
mfp.prev();
});
arrowRight[eName](function() {
mfp.next();
});
// Polyfill for :before and :after (adds elements with classes mfp-a and mfp-b)
if(mfp.isIE7) {
_getEl('b', arrowLeft[0], false, true);
_getEl('a', arrowLeft[0], false, true);
_getEl('b', arrowRight[0], false, true);
_getEl('a', arrowRight[0], false, true);
}
mfp.container.append(arrowLeft.add(arrowRight));
}
});
_mfpOn(CHANGE_EVENT+ns, function() {
if(mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout);
mfp._preloadTimeout = setTimeout(function() {
mfp.preloadNearbyImages();
mfp._preloadTimeout = null;
}, 16);
});
_mfpOn(CLOSE_EVENT+ns, function() {
_document.off(ns);
mfp.wrap.off('click'+ns);
if(mfp.arrowLeft && supportsFastClick) {
mfp.arrowLeft.add(mfp.arrowRight).destroyMfpFastClick();
}
mfp.arrowRight = mfp.arrowLeft = null;
});
},
next: function() {
mfp.direction = true;
mfp.index = _getLoopedId(mfp.index + 1);
mfp.updateItemHTML();
},
prev: function() {
mfp.direction = false;
mfp.index = _getLoopedId(mfp.index - 1);
mfp.updateItemHTML();
},
goTo: function(newIndex) {
mfp.direction = (newIndex >= mfp.index);
mfp.index = newIndex;
mfp.updateItemHTML();
},
preloadNearbyImages: function() {
var p = mfp.st.gallery.preload,
preloadBefore = Math.min(p[0], mfp.items.length),
preloadAfter = Math.min(p[1], mfp.items.length),
i;
for(i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) {
mfp._preloadItem(mfp.index+i);
}
for(i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) {
mfp._preloadItem(mfp.index-i);
}
},
_preloadItem: function(index) {
index = _getLoopedId(index);
if(mfp.items[index].preloaded) {
return;
}
var item = mfp.items[index];
if(!item.parsed) {
item = mfp.parseEl( index );
}
_mfpTrigger('LazyLoad', item);
if(item.type === 'image') {
item.img = $('
![]()
').on('load.mfploader', function() {
item.hasSize = true;
}).on('error.mfploader', function() {
item.hasSize = true;
item.loadError = true;
_mfpTrigger('LazyLoadError', item);
}).attr('src', item.src);
}
item.preloaded = true;
}
}
});
/*
Touch Support that might be implemented some day
addSwipeGesture: function() {
var startX,
moved,
multipleTouches;
return;
var namespace = '.mfp',
addEventNames = function(pref, down, move, up, cancel) {
mfp._tStart = pref + down + namespace;
mfp._tMove = pref + move + namespace;
mfp._tEnd = pref + up + namespace;
mfp._tCancel = pref + cancel + namespace;
};
if(window.navigator.msPointerEnabled) {
addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
} else if('ontouchstart' in window) {
addEventNames('touch', 'start', 'move', 'end', 'cancel');
} else {
return;
}
_window.on(mfp._tStart, function(e) {
var oE = e.originalEvent;
multipleTouches = moved = false;
startX = oE.pageX || oE.changedTouches[0].pageX;
}).on(mfp._tMove, function(e) {
if(e.originalEvent.touches.length > 1) {
multipleTouches = e.originalEvent.touches.length;
} else {
//e.preventDefault();
moved = true;
}
}).on(mfp._tEnd + ' ' + mfp._tCancel, function(e) {
if(moved && !multipleTouches) {
var oE = e.originalEvent,
diff = startX - (oE.pageX || oE.changedTouches[0].pageX);
if(diff > 20) {
mfp.next();
} else if(diff < -20) {
mfp.prev();
}
}
});
},
*/
/*>>gallery*/
/*>>retina*/
var RETINA_NS = 'retina';
$.magnificPopup.registerModule(RETINA_NS, {
options: {
replaceSrc: function(item) {
return item.src.replace(/\.\w+$/, function(m) { return '@2x' + m; });
},
ratio: 1 // Function or number. Set to 1 to disable.
},
proto: {
initRetina: function() {
if(window.devicePixelRatio > 1) {
var st = mfp.st.retina,
ratio = st.ratio;
ratio = !isNaN(ratio) ? ratio : ratio();
if(ratio > 1) {
_mfpOn('ImageHasSize' + '.' + RETINA_NS, function(e, item) {
item.img.css({
'max-width': item.img[0].naturalWidth / ratio,
'width': '100%'
});
});
_mfpOn('ElementParse' + '.' + RETINA_NS, function(e, item) {
item.src = st.replaceSrc(item, ratio);
});
}
}
}
}
});
/*>>retina*/
/*>>fastclick*/
/**
* FastClick event implementation. (removes 300ms delay on touch devices)
* Based on https://developers.google.com/mobile/articles/fast_buttons
*
* You may use it outside the Magnific Popup by calling just:
*
* $('.your-el').mfpFastClick(function() {
* console.log('Clicked!');
* });
*
* To unbind:
* $('.your-el').destroyMfpFastClick();
*
*
* Note that it's a very basic and simple implementation, it blocks ghost click on the same element where it was bound.
* If you need something more advanced, use plugin by FT Labs https://github.com/ftlabs/fastclick
*
*/
(function() {
var ghostClickDelay = 1000,
supportsTouch = 'ontouchstart' in window,
unbindTouchMove = function() {
_window.off('touchmove'+ns+' touchend'+ns);
},
eName = 'mfpFastClick',
ns = '.'+eName;
// As Zepto.js doesn't have an easy way to add custom events (like jQuery), so we implement it in this way
$.fn.mfpFastClick = function(callback) {
return $(this).each(function() {
var elem = $(this),
lock;
if( supportsTouch ) {
var timeout,
startX,
startY,
pointerMoved,
point,
numPointers;
elem.on('touchstart' + ns, function(e) {
pointerMoved = false;
numPointers = 1;
point = e.originalEvent ? e.originalEvent.touches[0] : e.touches[0];
startX = point.clientX;
startY = point.clientY;
_window.on('touchmove'+ns, function(e) {
point = e.originalEvent ? e.originalEvent.touches : e.touches;
numPointers = point.length;
point = point[0];
if (Math.abs(point.clientX - startX) > 10 ||
Math.abs(point.clientY - startY) > 10) {
pointerMoved = true;
unbindTouchMove();
}
}).on('touchend'+ns, function(e) {
unbindTouchMove();
if(pointerMoved || numPointers > 1) {
return;
}
lock = true;
e.preventDefault();
clearTimeout(timeout);
timeout = setTimeout(function() {
lock = false;
}, ghostClickDelay);
callback();
});
});
}
elem.on('click' + ns, function() {
if(!lock) {
callback();
}
});
});
};
$.fn.destroyMfpFastClick = function() {
$(this).off('touchstart' + ns + ' click' + ns);
if(supportsTouch) _window.off('touchmove'+ns+' touchend'+ns);
};
})();
/*>>fastclick*/
_checkInstance(); }));
/**
* Owl Carousel v2.2.0
* Copyright 2013-2016 David Deutsch
* Licensed under MIT (https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE)
*/
/**
* Owl carousel
* @version 2.1.6
* @author Bartosz Wojciechowski
* @author David Deutsch
* @license The MIT License (MIT)
* @todo Lazy Load Icon
* @todo prevent animationend bubling
* @todo itemsScaleUp
* @todo Test Zepto
* @todo stagePadding calculate wrong active classes
*/
;(function($, window, document, undefined) {
/**
* Creates a carousel.
* @class The Owl Carousel.
* @public
* @param {HTMLElement|jQuery} element - The element to create the carousel for.
* @param {Object} [options] - The options
*/
function Owl(element, options) {
/**
* Current settings for the carousel.
* @public
*/
this.settings = null;
/**
* Current options set by the caller including defaults.
* @public
*/
this.options = $.extend({}, Owl.Defaults, options);
/**
* Plugin element.
* @public
*/
this.$element = $(element);
/**
* Proxied event handlers.
* @protected
*/
this._handlers = {};
/**
* References to the running plugins of this carousel.
* @protected
*/
this._plugins = {};
/**
* Currently suppressed events to prevent them from beeing retriggered.
* @protected
*/
this._supress = {};
/**
* Absolute current position.
* @protected
*/
this._current = null;
/**
* Animation speed in milliseconds.
* @protected
*/
this._speed = null;
/**
* Coordinates of all items in pixel.
* @todo The name of this member is missleading.
* @protected
*/
this._coordinates = [];
/**
* Current breakpoint.
* @todo Real media queries would be nice.
* @protected
*/
this._breakpoint = null;
/**
* Current width of the plugin element.
*/
this._width = null;
/**
* All real items.
* @protected
*/
this._items = [];
/**
* All cloned items.
* @protected
*/
this._clones = [];
/**
* Merge values of all items.
* @todo Maybe this could be part of a plugin.
* @protected
*/
this._mergers = [];
/**
* Widths of all items.
*/
this._widths = [];
/**
* Invalidated parts within the update process.
* @protected
*/
this._invalidated = {};
/**
* Ordered list of workers for the update process.
* @protected
*/
this._pipe = [];
/**
* Current state information for the drag operation.
* @todo #261
* @protected
*/
this._drag = {
time: null,
target: null,
pointer: null,
stage: {
start: null,
current: null
},
direction: null
};
/**
* Current state information and their tags.
* @type {Object}
* @protected
*/
this._states = {
current: {},
tags: {
'initializing': [ 'busy' ],
'animating': [ 'busy' ],
'dragging': [ 'interacting' ]
}
};
$.each([ 'onResize', 'onThrottledResize' ], $.proxy(function(i, handler) {
this._handlers[handler] = $.proxy(this[handler], this);
}, this));
$.each(Owl.Plugins, $.proxy(function(key, plugin) {
this._plugins[key.charAt(0).toLowerCase() + key.slice(1)]
= new plugin(this);
}, this));
$.each(Owl.Workers, $.proxy(function(priority, worker) {
this._pipe.push({
'filter': worker.filter,
'run': $.proxy(worker.run, this)
});
}, this));
this.setup();
this.initialize();
}
/**
* Default options for the carousel.
* @public
*/
Owl.Defaults = {
items: 3,
loop: false,
center: false,
rewind: false,
mouseDrag: true,
touchDrag: true,
pullDrag: true,
freeDrag: false,
margin: 0,
stagePadding: 0,
merge: false,
mergeFit: true,
autoWidth: false,
startPosition: 0,
rtl: false,
smartSpeed: 250,
fluidSpeed: false,
dragEndSpeed: false,
responsive: {},
responsiveRefreshRate: 200,
responsiveBaseElement: window,
fallbackEasing: 'swing',
info: false,
nestedItemSelector: false,
itemElement: 'div',
stageElement: 'div',
refreshClass: 'owl-refresh',
loadedClass: 'owl-loaded',
loadingClass: 'owl-loading',
rtlClass: 'owl-rtl',
responsiveClass: 'owl-responsive',
dragClass: 'owl-drag',
itemClass: 'owl-item',
stageClass: 'owl-stage',
stageOuterClass: 'owl-stage-outer',
grabClass: 'owl-grab'
};
/**
* Enumeration for width.
* @public
* @readonly
* @enum {String}
*/
Owl.Width = {
Default: 'default',
Inner: 'inner',
Outer: 'outer'
};
/**
* Enumeration for types.
* @public
* @readonly
* @enum {String}
*/
Owl.Type = {
Event: 'event',
State: 'state'
};
/**
* Contains all registered plugins.
* @public
*/
Owl.Plugins = {};
/**
* List of workers involved in the update process.
*/
Owl.Workers = [ {
filter: [ 'width', 'settings' ],
run: function() {
this._width = this.$element.width();
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function(cache) {
cache.current = this._items && this._items[this.relative(this._current)];
}
}, {
filter: [ 'items', 'settings' ],
run: function() {
this.$stage.children('.cloned').remove();
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function(cache) {
var margin = this.settings.margin || '',
grid = !this.settings.autoWidth,
rtl = this.settings.rtl,
css = {
'width': 'auto',
'margin-left': rtl ? margin : '',
'margin-right': rtl ? '' : margin
};
!grid && this.$stage.children().css(css);
cache.css = css;
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function(cache) {
var width = (this.width() / this.settings.items).toFixed(3) - this.settings.margin,
merge = null,
iterator = this._items.length,
grid = !this.settings.autoWidth,
widths = [];
cache.items = {
merge: false,
width: width
};
while (iterator--) {
merge = this._mergers[iterator];
merge = this.settings.mergeFit && Math.min(merge, this.settings.items) || merge;
cache.items.merge = merge > 1 || cache.items.merge;
widths[iterator] = !grid ? this._items[iterator].width() : width * merge;
}
this._widths = widths;
}
}, {
filter: [ 'items', 'settings' ],
run: function() {
var clones = [],
items = this._items,
settings = this.settings,
// TODO: Should be computed from number of min width items in stage
view = Math.max(settings.items * 2, 4),
size = Math.ceil(items.length / 2) * 2,
repeat = settings.loop && items.length ? settings.rewind ? view : Math.max(view, size) : 0,
append = '',
prepend = '';
repeat /= 2;
while (repeat--) {
// Switch to only using appended clones
clones.push(this.normalize(clones.length / 2, true));
append = append + items[clones[clones.length - 1]][0].outerHTML;
clones.push(this.normalize(items.length - 1 - (clones.length - 1) / 2, true));
prepend = items[clones[clones.length - 1]][0].outerHTML + prepend;
}
this._clones = clones;
$(append).addClass('cloned').appendTo(this.$stage);
$(prepend).addClass('cloned').prependTo(this.$stage);
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function() {
var rtl = this.settings.rtl ? 1 : -1,
size = this._clones.length + this._items.length,
iterator = -1,
previous = 0,
current = 0,
coordinates = [];
while (++iterator < size) {
previous = coordinates[iterator - 1] || 0;
current = this._widths[this.relative(iterator)] + this.settings.margin;
coordinates.push(previous + current * rtl);
}
this._coordinates = coordinates;
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function() {
var padding = this.settings.stagePadding,
coordinates = this._coordinates,
css = {
'width': Math.ceil(Math.abs(coordinates[coordinates.length - 1])) + padding * 2,
'padding-left': padding || '',
'padding-right': padding || ''
};
this.$stage.css(css);
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function(cache) {
var iterator = this._coordinates.length,
grid = !this.settings.autoWidth,
items = this.$stage.children();
if (grid && cache.items.merge) {
while (iterator--) {
cache.css.width = this._widths[this.relative(iterator)];
items.eq(iterator).css(cache.css);
}
} else if (grid) {
cache.css.width = cache.items.width;
items.css(cache.css);
}
}
}, {
filter: [ 'items' ],
run: function() {
this._coordinates.length < 1 && this.$stage.removeAttr('style');
}
}, {
filter: [ 'width', 'items', 'settings' ],
run: function(cache) {
cache.current = cache.current ? this.$stage.children().index(cache.current) : 0;
cache.current = Math.max(this.minimum(), Math.min(this.maximum(), cache.current));
this.reset(cache.current);
}
}, {
filter: [ 'position' ],
run: function() {
this.animate(this.coordinates(this._current));
}
}, {
filter: [ 'width', 'position', 'items', 'settings' ],
run: function() {
var rtl = this.settings.rtl ? 1 : -1,
padding = this.settings.stagePadding * 2,
begin = this.coordinates(this.current()) + padding,
end = begin + this.width() * rtl,
inner, outer, matches = [], i, n;
for (i = 0, n = this._coordinates.length; i < n; i++) {
inner = this._coordinates[i - 1] || 0;
outer = Math.abs(this._coordinates[i]) + padding * rtl;
if ((this.op(inner, '<=', begin) && (this.op(inner, '>', end)))
|| (this.op(outer, '<', begin) && this.op(outer, '>', end))) {
matches.push(i);
}
}
this.$stage.children('.active').removeClass('active');
this.$stage.children(':eq(' + matches.join('), :eq(') + ')').addClass('active');
if (this.settings.center) {
this.$stage.children('.center').removeClass('center');
this.$stage.children().eq(this.current()).addClass('center');
}
}
} ];
/**
* Initializes the carousel.
* @protected
*/
Owl.prototype.initialize = function() {
this.enter('initializing');
this.trigger('initialize');
this.$element.toggleClass(this.settings.rtlClass, this.settings.rtl);
if (this.settings.autoWidth && !this.is('pre-loading')) {
var imgs, nestedSelector, width;
imgs = this.$element.find('img');
nestedSelector = this.settings.nestedItemSelector ? '.' + this.settings.nestedItemSelector : undefined;
width = this.$element.children(nestedSelector).width();
if (imgs.length && width <= 0) {
this.preloadAutoWidthImages(imgs);
}
}
this.$element.addClass(this.options.loadingClass);
// create stage
this.$stage = $('<' + this.settings.stageElement + ' class="' + this.settings.stageClass + '"/>')
.wrap('
');
// append stage
this.$element.append(this.$stage.parent());
// append content
this.replace(this.$element.children().not(this.$stage.parent()));
// check visibility
if (this.$element.is(':visible')) {
// update view
this.refresh();
} else {
// invalidate width
this.invalidate('width');
}
this.$element
.removeClass(this.options.loadingClass)
.addClass(this.options.loadedClass);
// register event handlers
this.registerEventHandlers();
this.leave('initializing');
this.trigger('initialized');
};
/**
* Setups the current settings.
* @todo Remove responsive classes. Why should adaptive designs be brought into IE8?
* @todo Support for media queries by using `matchMedia` would be nice.
* @public
*/
Owl.prototype.setup = function() {
var viewport = this.viewport(),
overwrites = this.options.responsive,
match = -1,
settings = null;
if (!overwrites) {
settings = $.extend({}, this.options);
} else {
$.each(overwrites, function(breakpoint) {
if (breakpoint <= viewport && breakpoint > match) {
match = Number(breakpoint);
}
});
settings = $.extend({}, this.options, overwrites[match]);
if (typeof settings.stagePadding === 'function') {
settings.stagePadding = settings.stagePadding();
}
delete settings.responsive;
// responsive class
if (settings.responsiveClass) {
this.$element.attr('class',
this.$element.attr('class').replace(new RegExp('(' + this.options.responsiveClass + '-)\\S+\\s', 'g'), '$1' + match)
);
}
}
this.trigger('change', { property: { name: 'settings', value: settings } });
this._breakpoint = match;
this.settings = settings;
this.invalidate('settings');
this.trigger('changed', { property: { name: 'settings', value: this.settings } });
};
/**
* Updates option logic if necessery.
* @protected
*/
Owl.prototype.optionsLogic = function() {
if (this.settings.autoWidth) {
this.settings.stagePadding = false;
this.settings.merge = false;
}
};
/**
* Prepares an item before add.
* @todo Rename event parameter `content` to `item`.
* @protected
* @returns {jQuery|HTMLElement} - The item container.
*/
Owl.prototype.prepare = function(item) {
var event = this.trigger('prepare', { content: item });
if (!event.data) {
event.data = $('<' + this.settings.itemElement + '/>')
.addClass(this.options.itemClass).append(item)
}
this.trigger('prepared', { content: event.data });
return event.data;
};
/**
* Updates the view.
* @public
*/
Owl.prototype.update = function() {
var i = 0,
n = this._pipe.length,
filter = $.proxy(function(p) { return this[p] }, this._invalidated),
cache = {};
while (i < n) {
if (this._invalidated.all || $.grep(this._pipe[i].filter, filter).length > 0) {
this._pipe[i].run(cache);
}
i++;
}
this._invalidated = {};
!this.is('valid') && this.enter('valid');
};
/**
* Gets the width of the view.
* @public
* @param {Owl.Width} [dimension=Owl.Width.Default] - The dimension to return.
* @returns {Number} - The width of the view in pixel.
*/
Owl.prototype.width = function(dimension) {
dimension = dimension || Owl.Width.Default;
switch (dimension) {
case Owl.Width.Inner:
case Owl.Width.Outer:
return this._width;
default:
return this._width - this.settings.stagePadding * 2 + this.settings.margin;
}
};
/**
* Refreshes the carousel primarily for adaptive purposes.
* @public
*/
Owl.prototype.refresh = function() {
this.enter('refreshing');
this.trigger('refresh');
this.setup();
this.optionsLogic();
this.$element.addClass(this.options.refreshClass);
this.update();
this.$element.removeClass(this.options.refreshClass);
this.leave('refreshing');
this.trigger('refreshed');
};
/**
* Checks window `resize` event.
* @protected
*/
Owl.prototype.onThrottledResize = function() {
window.clearTimeout(this.resizeTimer);
this.resizeTimer = window.setTimeout(this._handlers.onResize, this.settings.responsiveRefreshRate);
};
/**
* Checks window `resize` event.
* @protected
*/
Owl.prototype.onResize = function() {
if (!this._items.length) {
return false;
}
if (this._width === this.$element.width()) {
return false;
}
if (!this.$element.is(':visible')) {
return false;
}
this.enter('resizing');
if (this.trigger('resize').isDefaultPrevented()) {
this.leave('resizing');
return false;
}
this.invalidate('width');
this.refresh();
this.leave('resizing');
this.trigger('resized');
};
/**
* Registers event handlers.
* @todo Check `msPointerEnabled`
* @todo #261
* @protected
*/
Owl.prototype.registerEventHandlers = function() {
if ($.support.transition) {
this.$stage.on($.support.transition.end + '.owl.core', $.proxy(this.onTransitionEnd, this));
}
if (this.settings.responsive !== false) {
this.on(window, 'resize', this._handlers.onThrottledResize);
}
if (this.settings.mouseDrag) {
this.$element.addClass(this.options.dragClass);
this.$stage.on('mousedown.owl.core', $.proxy(this.onDragStart, this));
this.$stage.on('dragstart.owl.core selectstart.owl.core', function() { return false });
}
if (this.settings.touchDrag){
this.$stage.on('touchstart.owl.core', $.proxy(this.onDragStart, this));
this.$stage.on('touchcancel.owl.core', $.proxy(this.onDragEnd, this));
}
};
/**
* Handles `touchstart` and `mousedown` events.
* @todo Horizontal swipe threshold as option
* @todo #261
* @protected
* @param {Event} event - The event arguments.
*/
Owl.prototype.onDragStart = function(event) {
var stage = null;
if (event.which === 3) {
return;
}
if ($.support.transform) {
stage = this.$stage.css('transform').replace(/.*\(|\)| /g, '').split(',');
stage = {
x: stage[stage.length === 16 ? 12 : 4],
y: stage[stage.length === 16 ? 13 : 5]
};
} else {
stage = this.$stage.position();
stage = {
x: this.settings.rtl ?
stage.left + this.$stage.width() - this.width() + this.settings.margin :
stage.left,
y: stage.top
};
}
if (this.is('animating')) {
$.support.transform ? this.animate(stage.x) : this.$stage.stop()
this.invalidate('position');
}
this.$element.toggleClass(this.options.grabClass, event.type === 'mousedown');
this.speed(0);
this._drag.time = new Date().getTime();
this._drag.target = $(event.target);
this._drag.stage.start = stage;
this._drag.stage.current = stage;
this._drag.pointer = this.pointer(event);
$(document).on('mouseup.owl.core touchend.owl.core', $.proxy(this.onDragEnd, this));
$(document).one('mousemove.owl.core touchmove.owl.core', $.proxy(function(event) {
var delta = this.difference(this._drag.pointer, this.pointer(event));
$(document).on('mousemove.owl.core touchmove.owl.core', $.proxy(this.onDragMove, this));
if (Math.abs(delta.x) < Math.abs(delta.y) && this.is('valid')) {
return;
}
event.preventDefault();
this.enter('dragging');
this.trigger('drag');
}, this));
};
/**
* Handles the `touchmove` and `mousemove` events.
* @todo #261
* @protected
* @param {Event} event - The event arguments.
*/
Owl.prototype.onDragMove = function(event) {
var minimum = null,
maximum = null,
pull = null,
delta = this.difference(this._drag.pointer, this.pointer(event)),
stage = this.difference(this._drag.stage.start, delta);
if (!this.is('dragging')) {
return;
}
event.preventDefault();
if (this.settings.loop) {
minimum = this.coordinates(this.minimum());
maximum = this.coordinates(this.maximum() + 1) - minimum;
stage.x = (((stage.x - minimum) % maximum + maximum) % maximum) + minimum;
} else {
minimum = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum());
maximum = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum());
pull = this.settings.pullDrag ? -1 * delta.x / 5 : 0;
stage.x = Math.max(Math.min(stage.x, minimum + pull), maximum + pull);
}
this._drag.stage.current = stage;
this.animate(stage.x);
};
/**
* Handles the `touchend` and `mouseup` events.
* @todo #261
* @todo Threshold for click event
* @protected
* @param {Event} event - The event arguments.
*/
Owl.prototype.onDragEnd = function(event) {
var delta = this.difference(this._drag.pointer, this.pointer(event)),
stage = this._drag.stage.current,
direction = delta.x > 0 ^ this.settings.rtl ? 'left' : 'right';
$(document).off('.owl.core');
this.$element.removeClass(this.options.grabClass);
if (delta.x !== 0 && this.is('dragging') || !this.is('valid')) {
this.speed(this.settings.dragEndSpeed || this.settings.smartSpeed);
this.current(this.closest(stage.x, delta.x !== 0 ? direction : this._drag.direction));
this.invalidate('position');
this.update();
this._drag.direction = direction;
if (Math.abs(delta.x) > 3 || new Date().getTime() - this._drag.time > 300) {
this._drag.target.one('click.owl.core', function() { return false; });
}
}
if (!this.is('dragging')) {
return;
}
this.leave('dragging');
this.trigger('dragged');
};
/**
* Gets absolute position of the closest item for a coordinate.
* @todo Setting `freeDrag` makes `closest` not reusable. See #165.
* @protected
* @param {Number} coordinate - The coordinate in pixel.
* @param {String} direction - The direction to check for the closest item. Ether `left` or `right`.
* @return {Number} - The absolute position of the closest item.
*/
Owl.prototype.closest = function(coordinate, direction) {
var position = -1,
pull = 30,
width = this.width(),
coordinates = this.coordinates();
if (!this.settings.freeDrag) {
// check closest item
$.each(coordinates, $.proxy(function(index, value) {
// on a left pull, check on current index
if (direction === 'left' && coordinate > value - pull && coordinate < value + pull) {
position = index;
// on a right pull, check on previous index
// to do so, subtract width from value and set position = index + 1
} else if (direction === 'right' && coordinate > value - width - pull && coordinate < value - width + pull) {
position = index + 1;
} else if (this.op(coordinate, '<', value)
&& this.op(coordinate, '>', coordinates[index + 1] || value - width)) {
position = direction === 'left' ? index + 1 : index;
}
return position === -1;
}, this));
}
if (!this.settings.loop) {
// non loop boundries
if (this.op(coordinate, '>=', coordinates[this.minimum()])) {
position = coordinate = this.minimum();
} else if (this.op(coordinate, '<', coordinates[this.maximum()])) {
position = coordinate = this.maximum();
}
}
return position;
};
/**
* Animates the stage.
* @todo #270
* @public
* @param {Number} coordinate - The coordinate in pixels.
*/
Owl.prototype.animate = function(coordinate) {
var animate = this.speed() > 0;
this.is('animating') && this.onTransitionEnd();
if (animate) {
this.enter('animating');
this.trigger('translate');
}
if ($.support.transform3d && $.support.transition) {
this.$stage.css({
transform: 'translate3d(' + coordinate + 'px,0px,0px)',
transition: (this.speed() / 1000) + 's'
});
} else if (animate) {
this.$stage.animate({
left: coordinate + 'px'
}, this.speed(), this.settings.fallbackEasing, $.proxy(this.onTransitionEnd, this));
} else {
this.$stage.css({
left: coordinate + 'px'
});
}
};
/**
* Checks whether the carousel is in a specific state or not.
* @param {String} state - The state to check.
* @returns {Boolean} - The flag which indicates if the carousel is busy.
*/
Owl.prototype.is = function(state) {
return this._states.current[state] && this._states.current[state] > 0;
};
/**
* Sets the absolute position of the current item.
* @public
* @param {Number} [position] - The new absolute position or nothing to leave it unchanged.
* @returns {Number} - The absolute position of the current item.
*/
Owl.prototype.current = function(position) {
if (position === undefined) {
return this._current;
}
if (this._items.length === 0) {
return undefined;
}
position = this.normalize(position);
if (this._current !== position) {
var event = this.trigger('change', { property: { name: 'position', value: position } });
if (event.data !== undefined) {
position = this.normalize(event.data);
}
this._current = position;
this.invalidate('position');
this.trigger('changed', { property: { name: 'position', value: this._current } });
}
return this._current;
};
/**
* Invalidates the given part of the update routine.
* @param {String} [part] - The part to invalidate.
* @returns {Array.
} - The invalidated parts.
*/
Owl.prototype.invalidate = function(part) {
if (typeof part === 'string') {
this._invalidated[part] = true;
this.is('valid') && this.leave('valid');
}
return $.map(this._invalidated, function(v, i) { return i });
};
/**
* Resets the absolute position of the current item.
* @public
* @param {Number} position - The absolute position of the new item.
*/
Owl.prototype.reset = function(position) {
position = this.normalize(position);
if (position === undefined) {
return;
}
this._speed = 0;
this._current = position;
this.suppress([ 'translate', 'translated' ]);
this.animate(this.coordinates(position));
this.release([ 'translate', 'translated' ]);
};
/**
* Normalizes an absolute or a relative position of an item.
* @public
* @param {Number} position - The absolute or relative position to normalize.
* @param {Boolean} [relative=false] - Whether the given position is relative or not.
* @returns {Number} - The normalized position.
*/
Owl.prototype.normalize = function(position, relative) {
var n = this._items.length,
m = relative ? 0 : this._clones.length;
if (!this.isNumeric(position) || n < 1) {
position = undefined;
} else if (position < 0 || position >= n + m) {
position = ((position - m / 2) % n + n) % n + m / 2;
}
return position;
};
/**
* Converts an absolute position of an item into a relative one.
* @public
* @param {Number} position - The absolute position to convert.
* @returns {Number} - The converted position.
*/
Owl.prototype.relative = function(position) {
position -= this._clones.length / 2;
return this.normalize(position, true);
};
/**
* Gets the maximum position for the current item.
* @public
* @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position.
* @returns {Number}
*/
Owl.prototype.maximum = function(relative) {
var settings = this.settings,
maximum = this._coordinates.length,
iterator,
reciprocalItemsWidth,
elementWidth;
if (settings.loop) {
maximum = this._clones.length / 2 + this._items.length - 1;
} else if (settings.autoWidth || settings.merge) {
iterator = this._items.length;
reciprocalItemsWidth = this._items[--iterator].width();
elementWidth = this.$element.width();
while (iterator--) {
reciprocalItemsWidth += this._items[iterator].width() + this.settings.margin;
if (reciprocalItemsWidth > elementWidth) {
break;
}
}
maximum = iterator + 1;
} else if (settings.center) {
maximum = this._items.length - 1;
} else {
maximum = this._items.length - settings.items;
}
if (relative) {
maximum -= this._clones.length / 2;
}
return Math.max(maximum, 0);
};
/**
* Gets the minimum position for the current item.
* @public
* @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position.
* @returns {Number}
*/
Owl.prototype.minimum = function(relative) {
return relative ? 0 : this._clones.length / 2;
};
/**
* Gets an item at the specified relative position.
* @public
* @param {Number} [position] - The relative position of the item.
* @return {jQuery|Array.} - The item at the given position or all items if no position was given.
*/
Owl.prototype.items = function(position) {
if (position === undefined) {
return this._items.slice();
}
position = this.normalize(position, true);
return this._items[position];
};
/**
* Gets an item at the specified relative position.
* @public
* @param {Number} [position] - The relative position of the item.
* @return {jQuery|Array.} - The item at the given position or all items if no position was given.
*/
Owl.prototype.mergers = function(position) {
if (position === undefined) {
return this._mergers.slice();
}
position = this.normalize(position, true);
return this._mergers[position];
};
/**
* Gets the absolute positions of clones for an item.
* @public
* @param {Number} [position] - The relative position of the item.
* @returns {Array.} - The absolute positions of clones for the item or all if no position was given.
*/
Owl.prototype.clones = function(position) {
var odd = this._clones.length / 2,
even = odd + this._items.length,
map = function(index) { return index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2 };
if (position === undefined) {
return $.map(this._clones, function(v, i) { return map(i) });
}
return $.map(this._clones, function(v, i) { return v === position ? map(i) : null });
};
/**
* Sets the current animation speed.
* @public
* @param {Number} [speed] - The animation speed in milliseconds or nothing to leave it unchanged.
* @returns {Number} - The current animation speed in milliseconds.
*/
Owl.prototype.speed = function(speed) {
if (speed !== undefined) {
this._speed = speed;
}
return this._speed;
};
/**
* Gets the coordinate of an item.
* @todo The name of this method is missleanding.
* @public
* @param {Number} position - The absolute position of the item within `minimum()` and `maximum()`.
* @returns {Number|Array.} - The coordinate of the item in pixel or all coordinates.
*/
Owl.prototype.coordinates = function(position) {
var multiplier = 1,
newPosition = position - 1,
coordinate;
if (position === undefined) {
return $.map(this._coordinates, $.proxy(function(coordinate, index) {
return this.coordinates(index);
}, this));
}
if (this.settings.center) {
if (this.settings.rtl) {
multiplier = -1;
newPosition = position + 1;
}
coordinate = this._coordinates[position];
var delta = (this.settings.rtl) ? (this._coordinates[0] + this._coordinates[this._coordinates.length - 1]) : 0;
coordinate += (this.width() - coordinate + (this._coordinates[newPosition] || delta)) / 2 * multiplier;
} else {
coordinate = this._coordinates[newPosition] || 0;
}
coordinate = Math.ceil(coordinate);
return coordinate;
};
/**
* Calculates the speed for a translation.
* @protected
* @param {Number} from - The absolute position of the start item.
* @param {Number} to - The absolute position of the target item.
* @param {Number} [factor=undefined] - The time factor in milliseconds.
* @returns {Number} - The time in milliseconds for the translation.
*/
Owl.prototype.duration = function(from, to, factor) {
if (factor === 0) {
return 0;
}
return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((factor || this.settings.smartSpeed));
};
/**
* Slides to the specified item.
* @public
* @param {Number} position - The position of the item.
* @param {Number} [speed] - The time in milliseconds for the transition.
*/
Owl.prototype.to = function(position, speed) {
var current = this.current(),
revert = null,
distance = position - this.relative(current),
direction = (distance > 0) - (distance < 0),
items = this._items.length,
minimum = this.minimum(),
maximum = this.maximum();
if (this.settings.loop) {
if (!this.settings.rewind && Math.abs(distance) > items / 2) {
distance += direction * -1 * items;
}
position = current + distance;
revert = ((position - minimum) % items + items) % items + minimum;
if (revert !== position && revert - distance <= maximum && revert - distance > 0) {
current = revert - distance;
position = revert;
this.reset(current);
}
} else if (this.settings.rewind) {
maximum += 1;
position = (position % maximum + maximum) % maximum;
} else {
position = Math.max(minimum, Math.min(maximum, position));
}
this.speed(this.duration(current, position, speed));
this.current(position);
if (this.$element.is(':visible')) {
this.update();
}
};
/**
* Slides to the next item.
* @public
* @param {Number} [speed] - The time in milliseconds for the transition.
*/
Owl.prototype.next = function(speed) {
speed = speed || false;
this.to(this.relative(this.current()) + 1, speed);
};
/**
* Slides to the previous item.
* @public
* @param {Number} [speed] - The time in milliseconds for the transition.
*/
Owl.prototype.prev = function(speed) {
speed = speed || false;
this.to(this.relative(this.current()) - 1, speed);
};
/**
* Handles the end of an animation.
* @protected
* @param {Event} event - The event arguments.
*/
Owl.prototype.onTransitionEnd = function(event) {
// if css2 animation then event object is undefined
if (event !== undefined) {
event.stopPropagation();
// Catch only owl-stage transitionEnd event
if ((event.target || event.srcElement || event.originalTarget) !== this.$stage.get(0)) {
return false;
}
}
this.leave('animating');
this.trigger('translated');
};
/**
* Gets viewport width.
* @protected
* @return {Number} - The width in pixel.
*/
Owl.prototype.viewport = function() {
var width;
if (this.options.responsiveBaseElement !== window) {
width = $(this.options.responsiveBaseElement).width();
} else if (window.innerWidth) {
width = window.innerWidth;
} else if (document.documentElement && document.documentElement.clientWidth) {
width = document.documentElement.clientWidth;
} else {
throw 'Can not detect viewport width.';
}
return width;
};
/**
* Replaces the current content.
* @public
* @param {HTMLElement|jQuery|String} content - The new content.
*/
Owl.prototype.replace = function(content) {
this.$stage.empty();
this._items = [];
if (content) {
content = (content instanceof jQuery) ? content : $(content);
}
if (this.settings.nestedItemSelector) {
content = content.find('.' + this.settings.nestedItemSelector);
}
content.filter(function() {
return this.nodeType === 1;
}).each($.proxy(function(index, item) {
item = this.prepare(item);
this.$stage.append(item);
this._items.push(item);
this._mergers.push(item.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1);
}, this));
this.reset(this.isNumeric(this.settings.startPosition) ? this.settings.startPosition : 0);
this.invalidate('items');
};
/**
* Adds an item.
* @todo Use `item` instead of `content` for the event arguments.
* @public
* @param {HTMLElement|jQuery|String} content - The item content to add.
* @param {Number} [position] - The relative position at which to insert the item otherwise the item will be added to the end.
*/
Owl.prototype.add = function(content, position) {
var current = this.relative(this._current);
position = position === undefined ? this._items.length : this.normalize(position, true);
content = content instanceof jQuery ? content : $(content);
this.trigger('add', { content: content, position: position });
content = this.prepare(content);
if (this._items.length === 0 || position === this._items.length) {
this._items.length === 0 && this.$stage.append(content);
this._items.length !== 0 && this._items[position - 1].after(content);
this._items.push(content);
this._mergers.push(content.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1);
} else {
this._items[position].before(content);
this._items.splice(position, 0, content);
this._mergers.splice(position, 0, content.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1);
}
this._items[current] && this.reset(this._items[current].index());
this.invalidate('items');
this.trigger('added', { content: content, position: position });
};
/**
* Removes an item by its position.
* @todo Use `item` instead of `content` for the event arguments.
* @public
* @param {Number} position - The relative position of the item to remove.
*/
Owl.prototype.remove = function(position) {
position = this.normalize(position, true);
if (position === undefined) {
return;
}
this.trigger('remove', { content: this._items[position], position: position });
this._items[position].remove();
this._items.splice(position, 1);
this._mergers.splice(position, 1);
this.invalidate('items');
this.trigger('removed', { content: null, position: position });
};
/**
* Preloads images with auto width.
* @todo Replace by a more generic approach
* @protected
*/
Owl.prototype.preloadAutoWidthImages = function(images) {
images.each($.proxy(function(i, element) {
this.enter('pre-loading');
element = $(element);
$(new Image()).one('load', $.proxy(function(e) {
element.attr('src', e.target.src);
element.css('opacity', 1);
this.leave('pre-loading');
!this.is('pre-loading') && !this.is('initializing') && this.refresh();
}, this)).attr('src', element.attr('src') || element.attr('data-src') || element.attr('data-src-retina'));
}, this));
};
/**
* Destroys the carousel.
* @public
*/
Owl.prototype.destroy = function() {
this.$element.off('.owl.core');
this.$stage.off('.owl.core');
$(document).off('.owl.core');
if (this.settings.responsive !== false) {
window.clearTimeout(this.resizeTimer);
this.off(window, 'resize', this._handlers.onThrottledResize);
}
for (var i in this._plugins) {
this._plugins[i].destroy();
}
this.$stage.children('.cloned').remove();
this.$stage.unwrap();
this.$stage.children().contents().unwrap();
this.$stage.children().unwrap();
this.$element
.removeClass(this.options.refreshClass)
.removeClass(this.options.loadingClass)
.removeClass(this.options.loadedClass)
.removeClass(this.options.rtlClass)
.removeClass(this.options.dragClass)
.removeClass(this.options.grabClass)
.attr('class', this.$element.attr('class').replace(new RegExp(this.options.responsiveClass + '-\\S+\\s', 'g'), ''))
.removeData('owl.carousel');
};
/**
* Operators to calculate right-to-left and left-to-right.
* @protected
* @param {Number} [a] - The left side operand.
* @param {String} [o] - The operator.
* @param {Number} [b] - The right side operand.
*/
Owl.prototype.op = function(a, o, b) {
var rtl = this.settings.rtl;
switch (o) {
case '<':
return rtl ? a > b : a < b;
case '>':
return rtl ? a < b : a > b;
case '>=':
return rtl ? a <= b : a >= b;
case '<=':
return rtl ? a >= b : a <= b;
default:
break;
}
};
/**
* Attaches to an internal event.
* @protected
* @param {HTMLElement} element - The event source.
* @param {String} event - The event name.
* @param {Function} listener - The event handler to attach.
* @param {Boolean} capture - Wether the event should be handled at the capturing phase or not.
*/
Owl.prototype.on = function(element, event, listener, capture) {
if (element.addEventListener) {
element.addEventListener(event, listener, capture);
} else if (element.attachEvent) {
element.attachEvent('on' + event, listener);
}
};
/**
* Detaches from an internal event.
* @protected
* @param {HTMLElement} element - The event source.
* @param {String} event - The event name.
* @param {Function} listener - The attached event handler to detach.
* @param {Boolean} capture - Wether the attached event handler was registered as a capturing listener or not.
*/
Owl.prototype.off = function(element, event, listener, capture) {
if (element.removeEventListener) {
element.removeEventListener(event, listener, capture);
} else if (element.detachEvent) {
element.detachEvent('on' + event, listener);
}
};
/**
* Triggers a public event.
* @todo Remove `status`, `relatedTarget` should be used instead.
* @protected
* @param {String} name - The event name.
* @param {*} [data=null] - The event data.
* @param {String} [namespace=carousel] - The event namespace.
* @param {String} [state] - The state which is associated with the event.
* @param {Boolean} [enter=false] - Indicates if the call enters the specified state or not.
* @returns {Event} - The event arguments.
*/
Owl.prototype.trigger = function(name, data, namespace, state, enter) {
var status = {
item: { count: this._items.length, index: this.current() }
}, handler = $.camelCase(
$.grep([ 'on', name, namespace ], function(v) { return v })
.join('-').toLowerCase()
), event = $.Event(
[ name, 'owl', namespace || 'carousel' ].join('.').toLowerCase(),
$.extend({ relatedTarget: this }, status, data)
);
if (!this._supress[name]) {
$.each(this._plugins, function(name, plugin) {
if (plugin.onTrigger) {
plugin.onTrigger(event);
}
});
this.register({ type: Owl.Type.Event, name: name });
this.$element.trigger(event);
if (this.settings && typeof this.settings[handler] === 'function') {
this.settings[handler].call(this, event);
}
}
return event;
};
/**
* Enters a state.
* @param name - The state name.
*/
Owl.prototype.enter = function(name) {
$.each([ name ].concat(this._states.tags[name] || []), $.proxy(function(i, name) {
if (this._states.current[name] === undefined) {
this._states.current[name] = 0;
}
this._states.current[name]++;
}, this));
};
/**
* Leaves a state.
* @param name - The state name.
*/
Owl.prototype.leave = function(name) {
$.each([ name ].concat(this._states.tags[name] || []), $.proxy(function(i, name) {
this._states.current[name]--;
}, this));
};
/**
* Registers an event or state.
* @public
* @param {Object} object - The event or state to register.
*/
Owl.prototype.register = function(object) {
if (object.type === Owl.Type.Event) {
if (!$.event.special[object.name]) {
$.event.special[object.name] = {};
}
if (!$.event.special[object.name].owl) {
var _default = $.event.special[object.name]._default;
$.event.special[object.name]._default = function(e) {
if (_default && _default.apply && (!e.namespace || e.namespace.indexOf('owl') === -1)) {
return _default.apply(this, arguments);
}
return e.namespace && e.namespace.indexOf('owl') > -1;
};
$.event.special[object.name].owl = true;
}
} else if (object.type === Owl.Type.State) {
if (!this._states.tags[object.name]) {
this._states.tags[object.name] = object.tags;
} else {
this._states.tags[object.name] = this._states.tags[object.name].concat(object.tags);
}
this._states.tags[object.name] = $.grep(this._states.tags[object.name], $.proxy(function(tag, i) {
return $.inArray(tag, this._states.tags[object.name]) === i;
}, this));
}
};
/**
* Suppresses events.
* @protected
* @param {Array.} events - The events to suppress.
*/
Owl.prototype.suppress = function(events) {
$.each(events, $.proxy(function(index, event) {
this._supress[event] = true;
}, this));
};
/**
* Releases suppressed events.
* @protected
* @param {Array.} events - The events to release.
*/
Owl.prototype.release = function(events) {
$.each(events, $.proxy(function(index, event) {
delete this._supress[event];
}, this));
};
/**
* Gets unified pointer coordinates from event.
* @todo #261
* @protected
* @param {Event} - The `mousedown` or `touchstart` event.
* @returns {Object} - Contains `x` and `y` coordinates of current pointer position.
*/
Owl.prototype.pointer = function(event) {
var result = { x: null, y: null };
event = event.originalEvent || event || window.event;
event = event.touches && event.touches.length ?
event.touches[0] : event.changedTouches && event.changedTouches.length ?
event.changedTouches[0] : event;
if (event.pageX) {
result.x = event.pageX;
result.y = event.pageY;
} else {
result.x = event.clientX;
result.y = event.clientY;
}
return result;
};
/**
* Determines if the input is a Number or something that can be coerced to a Number
* @protected
* @param {Number|String|Object|Array|Boolean|RegExp|Function|Symbol} - The input to be tested
* @returns {Boolean} - An indication if the input is a Number or can be coerced to a Number
*/
Owl.prototype.isNumeric = function(number) {
return !isNaN(parseFloat(number));
};
/**
* Gets the difference of two vectors.
* @todo #261
* @protected
* @param {Object} - The first vector.
* @param {Object} - The second vector.
* @returns {Object} - The difference.
*/
Owl.prototype.difference = function(first, second) {
return {
x: first.x - second.x,
y: first.y - second.y
};
};
/**
* The jQuery Plugin for the Owl Carousel
* @todo Navigation plugin `next` and `prev`
* @public
*/
$.fn.owlCarousel = function(option) {
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var $this = $(this),
data = $this.data('owl.carousel');
if (!data) {
data = new Owl(this, typeof option == 'object' && option);
$this.data('owl.carousel', data);
$.each([
'next', 'prev', 'to', 'destroy', 'refresh', 'replace', 'add', 'remove'
], function(i, event) {
data.register({ type: Owl.Type.Event, name: event });
data.$element.on(event + '.owl.carousel.core', $.proxy(function(e) {
if (e.namespace && e.relatedTarget !== this) {
this.suppress([ event ]);
data[event].apply(this, [].slice.call(arguments, 1));
this.release([ event ]);
}
}, data));
});
}
if (typeof option == 'string' && option.charAt(0) !== '_') {
data[option].apply(data, args);
}
});
};
/**
* The constructor for the jQuery Plugin
* @public
*/
$.fn.owlCarousel.Constructor = Owl;
})(window.Zepto || window.jQuery, window, document);
/**
* AutoRefresh Plugin
* @version 2.1.0
* @author Artus Kolanowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
/**
* Creates the auto refresh plugin.
* @class The Auto Refresh Plugin
* @param {Owl} carousel - The Owl Carousel
*/
var AutoRefresh = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* Refresh interval.
* @protected
* @type {number}
*/
this._interval = null;
/**
* Whether the element is currently visible or not.
* @protected
* @type {Boolean}
*/
this._visible = null;
/**
* All event handlers.
* @protected
* @type {Object}
*/
this._handlers = {
'initialized.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.autoRefresh) {
this.watch();
}
}, this)
};
// set default options
this._core.options = $.extend({}, AutoRefresh.Defaults, this._core.options);
// register event handlers
this._core.$element.on(this._handlers);
};
/**
* Default options.
* @public
*/
AutoRefresh.Defaults = {
autoRefresh: true,
autoRefreshInterval: 500
};
/**
* Watches the element.
*/
AutoRefresh.prototype.watch = function() {
if (this._interval) {
return;
}
this._visible = this._core.$element.is(':visible');
this._interval = window.setInterval($.proxy(this.refresh, this), this._core.settings.autoRefreshInterval);
};
/**
* Refreshes the element.
*/
AutoRefresh.prototype.refresh = function() {
if (this._core.$element.is(':visible') === this._visible) {
return;
}
this._visible = !this._visible;
this._core.$element.toggleClass('owl-hidden', !this._visible);
this._visible && (this._core.invalidate('width') && this._core.refresh());
};
/**
* Destroys the plugin.
*/
AutoRefresh.prototype.destroy = function() {
var handler, property;
window.clearInterval(this._interval);
for (handler in this._handlers) {
this._core.$element.off(handler, this._handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.AutoRefresh = AutoRefresh;
})(window.Zepto || window.jQuery, window, document);
/**
* Lazy Plugin
* @version 2.1.0
* @author Bartosz Wojciechowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
/**
* Creates the lazy plugin.
* @class The Lazy Plugin
* @param {Owl} carousel - The Owl Carousel
*/
var Lazy = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* Already loaded items.
* @protected
* @type {Array.}
*/
this._loaded = [];
/**
* Event handlers.
* @protected
* @type {Object}
*/
this._handlers = {
'initialized.owl.carousel change.owl.carousel resized.owl.carousel': $.proxy(function(e) {
if (!e.namespace) {
return;
}
if (!this._core.settings || !this._core.settings.lazyLoad) {
return;
}
if ((e.property && e.property.name == 'position') || e.type == 'initialized') {
var settings = this._core.settings,
n = (settings.center && Math.ceil(settings.items / 2) || settings.items),
i = ((settings.center && n * -1) || 0),
position = (e.property && e.property.value !== undefined ? e.property.value : this._core.current()) + i,
clones = this._core.clones().length,
load = $.proxy(function(i, v) { this.load(v) }, this);
while (i++ < n) {
this.load(clones / 2 + this._core.relative(position));
clones && $.each(this._core.clones(this._core.relative(position)), load);
position++;
}
}
}, this)
};
// set the default options
this._core.options = $.extend({}, Lazy.Defaults, this._core.options);
// register event handler
this._core.$element.on(this._handlers);
};
/**
* Default options.
* @public
*/
Lazy.Defaults = {
lazyLoad: false
};
/**
* Loads all resources of an item at the specified position.
* @param {Number} position - The absolute position of the item.
* @protected
*/
Lazy.prototype.load = function(position) {
var $item = this._core.$stage.children().eq(position),
$elements = $item && $item.find('.owl-lazy');
if (!$elements || $.inArray($item.get(0), this._loaded) > -1) {
return;
}
$elements.each($.proxy(function(index, element) {
var $element = $(element), image,
url = (window.devicePixelRatio > 1 && $element.attr('data-src-retina')) || $element.attr('data-src');
this._core.trigger('load', { element: $element, url: url }, 'lazy');
if ($element.is('img')) {
$element.one('load.owl.lazy', $.proxy(function() {
$element.css('opacity', 1);
this._core.trigger('loaded', { element: $element, url: url }, 'lazy');
}, this)).attr('src', url);
} else {
image = new Image();
image.onload = $.proxy(function() {
$element.css({
'background-image': 'url(' + url + ')',
'opacity': '1'
});
this._core.trigger('loaded', { element: $element, url: url }, 'lazy');
}, this);
image.src = url;
}
}, this));
this._loaded.push($item.get(0));
};
/**
* Destroys the plugin.
* @public
*/
Lazy.prototype.destroy = function() {
var handler, property;
for (handler in this.handlers) {
this._core.$element.off(handler, this.handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.Lazy = Lazy;
})(window.Zepto || window.jQuery, window, document);
/**
* AutoHeight Plugin
* @version 2.1.0
* @author Bartosz Wojciechowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
/**
* Creates the auto height plugin.
* @class The Auto Height Plugin
* @param {Owl} carousel - The Owl Carousel
*/
var AutoHeight = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* All event handlers.
* @protected
* @type {Object}
*/
this._handlers = {
'initialized.owl.carousel refreshed.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.autoHeight) {
this.update();
}
}, this),
'changed.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.autoHeight && e.property.name == 'position'){
this.update();
}
}, this),
'loaded.owl.lazy': $.proxy(function(e) {
if (e.namespace && this._core.settings.autoHeight
&& e.element.closest('.' + this._core.settings.itemClass).index() === this._core.current()) {
this.update();
}
}, this)
};
// set default options
this._core.options = $.extend({}, AutoHeight.Defaults, this._core.options);
// register event handlers
this._core.$element.on(this._handlers);
};
/**
* Default options.
* @public
*/
AutoHeight.Defaults = {
autoHeight: false,
autoHeightClass: 'owl-height'
};
/**
* Updates the view.
*/
AutoHeight.prototype.update = function() {
var start = this._core._current,
end = start + this._core.settings.items,
visible = this._core.$stage.children().toArray().slice(start, end),
heights = [],
maxheight = 0;
$.each(visible, function(index, item) {
heights.push($(item).height());
});
maxheight = Math.max.apply(null, heights);
this._core.$stage.parent()
.height(maxheight)
.addClass(this._core.settings.autoHeightClass);
};
AutoHeight.prototype.destroy = function() {
var handler, property;
for (handler in this._handlers) {
this._core.$element.off(handler, this._handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.AutoHeight = AutoHeight;
})(window.Zepto || window.jQuery, window, document);
/**
* Video Plugin
* @version 2.1.0
* @author Bartosz Wojciechowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
/**
* Creates the video plugin.
* @class The Video Plugin
* @param {Owl} carousel - The Owl Carousel
*/
var Video = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* Cache all video URLs.
* @protected
* @type {Object}
*/
this._videos = {};
/**
* Current playing item.
* @protected
* @type {jQuery}
*/
this._playing = null;
/**
* All event handlers.
* @todo The cloned content removale is too late
* @protected
* @type {Object}
*/
this._handlers = {
'initialized.owl.carousel': $.proxy(function(e) {
if (e.namespace) {
this._core.register({ type: 'state', name: 'playing', tags: [ 'interacting' ] });
}
}, this),
'resize.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.video && this.isInFullScreen()) {
e.preventDefault();
}
}, this),
'refreshed.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.is('resizing')) {
this._core.$stage.find('.cloned .owl-video-frame').remove();
}
}, this),
'changed.owl.carousel': $.proxy(function(e) {
if (e.namespace && e.property.name === 'position' && this._playing) {
this.stop();
}
}, this),
'prepared.owl.carousel': $.proxy(function(e) {
if (!e.namespace) {
return;
}
var $element = $(e.content).find('.owl-video');
if ($element.length) {
$element.css('display', 'none');
this.fetch($element, $(e.content));
}
}, this)
};
// set default options
this._core.options = $.extend({}, Video.Defaults, this._core.options);
// register event handlers
this._core.$element.on(this._handlers);
this._core.$element.on('click.owl.video', '.owl-video-play-icon', $.proxy(function(e) {
this.play(e);
}, this));
};
/**
* Default options.
* @public
*/
Video.Defaults = {
video: false,
videoHeight: false,
videoWidth: false
};
/**
* Gets the video ID and the type (YouTube/Vimeo/vzaar only).
* @protected
* @param {jQuery} target - The target containing the video data.
* @param {jQuery} item - The item containing the video.
*/
Video.prototype.fetch = function(target, item) {
var type = (function() {
if (target.attr('data-vimeo-id')) {
return 'vimeo';
} else if (target.attr('data-vzaar-id')) {
return 'vzaar'
} else {
return 'youtube';
}
})(),
id = target.attr('data-vimeo-id') || target.attr('data-youtube-id') || target.attr('data-vzaar-id'),
width = target.attr('data-width') || this._core.settings.videoWidth,
height = target.attr('data-height') || this._core.settings.videoHeight,
url = target.attr('href');
if (url) {
/*
Parses the id's out of the following urls (and probably more):
https://www.youtube.com/watch?v=:id
https://youtu.be/:id
https://vimeo.com/:id
https://vimeo.com/channels/:channel/:id
https://vimeo.com/groups/:group/videos/:id
https://app.vzaar.com/videos/:id
Visual example: https://regexper.com/#(http%3A%7Chttps%3A%7C)%5C%2F%5C%2F(player.%7Cwww.%7Capp.)%3F(vimeo%5C.com%7Cyoutu(be%5C.com%7C%5C.be%7Cbe%5C.googleapis%5C.com)%7Cvzaar%5C.com)%5C%2F(video%5C%2F%7Cvideos%5C%2F%7Cembed%5C%2F%7Cchannels%5C%2F.%2B%5C%2F%7Cgroups%5C%2F.%2B%5C%2F%7Cwatch%5C%3Fv%3D%7Cv%5C%2F)%3F(%5BA-Za-z0-9._%25-%5D*)(%5C%26%5CS%2B)%3F
*/
id = url.match(/(http:|https:|)\/\/(player.|www.|app.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com)|vzaar\.com)\/(video\/|videos\/|embed\/|channels\/.+\/|groups\/.+\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/);
if (id[3].indexOf('youtu') > -1) {
type = 'youtube';
} else if (id[3].indexOf('vimeo') > -1) {
type = 'vimeo';
} else if (id[3].indexOf('vzaar') > -1) {
type = 'vzaar';
} else {
throw new Error('Video URL not supported.');
}
id = id[6];
} else {
throw new Error('Missing video URL.');
}
this._videos[url] = {
type: type,
id: id,
width: width,
height: height
};
item.attr('data-video', url);
this.thumbnail(target, this._videos[url]);
};
/**
* Creates video thumbnail.
* @protected
* @param {jQuery} target - The target containing the video data.
* @param {Object} info - The video info object.
* @see `fetch`
*/
Video.prototype.thumbnail = function(target, video) {
var tnLink,
icon,
path,
dimensions = video.width && video.height ? 'style="width:' + video.width + 'px;height:' + video.height + 'px;"' : '',
customTn = target.find('img'),
srcType = 'src',
lazyClass = '',
settings = this._core.settings,
create = function(path) {
icon = '';
if (settings.lazyLoad) {
tnLink = '';
} else {
tnLink = '';
}
target.after(tnLink);
target.after(icon);
};
// wrap video content into owl-video-wrapper div
target.wrap('');
if (this._core.settings.lazyLoad) {
srcType = 'data-src';
lazyClass = 'owl-lazy';
}
// custom thumbnail
if (customTn.length) {
create(customTn.attr(srcType));
customTn.remove();
return false;
}
if (video.type === 'youtube') {
path = "//img.youtube.com/vi/" + video.id + "/hqdefault.jpg";
create(path);
} else if (video.type === 'vimeo') {
$.ajax({
type: 'GET',
url: '//vimeo.com/api/v2/video/' + video.id + '.json',
jsonp: 'callback',
dataType: 'jsonp',
success: function(data) {
path = data[0].thumbnail_large;
create(path);
}
});
} else if (video.type === 'vzaar') {
$.ajax({
type: 'GET',
url: '//vzaar.com/api/videos/' + video.id + '.json',
jsonp: 'callback',
dataType: 'jsonp',
success: function(data) {
path = data.framegrab_url;
create(path);
}
});
}
};
/**
* Stops the current video.
* @public
*/
Video.prototype.stop = function() {
this._core.trigger('stop', null, 'video');
this._playing.find('.owl-video-frame').remove();
this._playing.removeClass('owl-video-playing');
this._playing = null;
this._core.leave('playing');
this._core.trigger('stopped', null, 'video');
};
/**
* Starts the current video.
* @public
* @param {Event} event - The event arguments.
*/
Video.prototype.play = function(event) {
var target = $(event.target),
item = target.closest('.' + this._core.settings.itemClass),
video = this._videos[item.attr('data-video')],
width = video.width || '100%',
height = video.height || this._core.$stage.height(),
html;
if (this._playing) {
return;
}
this._core.enter('playing');
this._core.trigger('play', null, 'video');
item = this._core.items(this._core.relative(item.index()));
this._core.reset(item.index());
if (video.type === 'youtube') {
html = '';
} else if (video.type === 'vimeo') {
html = '';
} else if (video.type === 'vzaar') {
html = '';
}
$('' + html + '
').insertAfter(item.find('.owl-video'));
this._playing = item.addClass('owl-video-playing');
};
/**
* Checks whether an video is currently in full screen mode or not.
* @todo Bad style because looks like a readonly method but changes members.
* @protected
* @returns {Boolean}
*/
Video.prototype.isInFullScreen = function() {
var element = document.fullscreenElement || document.mozFullScreenElement ||
document.webkitFullscreenElement;
return element && $(element).parent().hasClass('owl-video-frame');
};
/**
* Destroys the plugin.
*/
Video.prototype.destroy = function() {
var handler, property;
this._core.$element.off('click.owl.video');
for (handler in this._handlers) {
this._core.$element.off(handler, this._handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.Video = Video;
})(window.Zepto || window.jQuery, window, document);
/**
* Animate Plugin
* @version 2.1.0
* @author Bartosz Wojciechowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
/**
* Creates the animate plugin.
* @class The Navigation Plugin
* @param {Owl} scope - The Owl Carousel
*/
var Animate = function(scope) {
this.core = scope;
this.core.options = $.extend({}, Animate.Defaults, this.core.options);
this.swapping = true;
this.previous = undefined;
this.next = undefined;
this.handlers = {
'change.owl.carousel': $.proxy(function(e) {
if (e.namespace && e.property.name == 'position') {
this.previous = this.core.current();
this.next = e.property.value;
}
}, this),
'drag.owl.carousel dragged.owl.carousel translated.owl.carousel': $.proxy(function(e) {
if (e.namespace) {
this.swapping = e.type == 'translated';
}
}, this),
'translate.owl.carousel': $.proxy(function(e) {
if (e.namespace && this.swapping && (this.core.options.animateOut || this.core.options.animateIn)) {
this.swap();
}
}, this)
};
this.core.$element.on(this.handlers);
};
/**
* Default options.
* @public
*/
Animate.Defaults = {
animateOut: false,
animateIn: false
};
/**
* Toggles the animation classes whenever an translations starts.
* @protected
* @returns {Boolean|undefined}
*/
Animate.prototype.swap = function() {
if (this.core.settings.items !== 1) {
return;
}
if (!$.support.animation || !$.support.transition) {
return;
}
this.core.speed(0);
var left,
clear = $.proxy(this.clear, this),
previous = this.core.$stage.children().eq(this.previous),
next = this.core.$stage.children().eq(this.next),
incoming = this.core.settings.animateIn,
outgoing = this.core.settings.animateOut;
if (this.core.current() === this.previous) {
return;
}
if (outgoing) {
left = this.core.coordinates(this.previous) - this.core.coordinates(this.next);
previous.one($.support.animation.end, clear)
.css( { 'left': left + 'px' } )
.addClass('animated owl-animated-out')
.addClass(outgoing);
}
if (incoming) {
next.one($.support.animation.end, clear)
.addClass('animated owl-animated-in')
.addClass(incoming);
}
};
Animate.prototype.clear = function(e) {
$(e.target).css( { 'left': '' } )
.removeClass('animated owl-animated-out owl-animated-in')
.removeClass(this.core.settings.animateIn)
.removeClass(this.core.settings.animateOut);
this.core.onTransitionEnd();
};
/**
* Destroys the plugin.
* @public
*/
Animate.prototype.destroy = function() {
var handler, property;
for (handler in this.handlers) {
this.core.$element.off(handler, this.handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.Animate = Animate;
})(window.Zepto || window.jQuery, window, document);
/**
* Autoplay Plugin
* @version 2.1.0
* @author Bartosz Wojciechowski
* @author Artus Kolanowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
/**
* Creates the autoplay plugin.
* @class The Autoplay Plugin
* @param {Owl} scope - The Owl Carousel
*/
var Autoplay = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* The autoplay timeout.
* @type {Timeout}
*/
this._timeout = null;
/**
* Indicates whenever the autoplay is paused.
* @type {Boolean}
*/
this._paused = false;
/**
* All event handlers.
* @protected
* @type {Object}
*/
this._handlers = {
'changed.owl.carousel': $.proxy(function(e) {
if (e.namespace && e.property.name === 'settings') {
if (this._core.settings.autoplay) {
this.play();
} else {
this.stop();
}
} else if (e.namespace && e.property.name === 'position') {
//console.log('play?', e);
if (this._core.settings.autoplay) {
this._setAutoPlayInterval();
}
}
}, this),
'initialized.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.autoplay) {
this.play();
}
}, this),
'play.owl.autoplay': $.proxy(function(e, t, s) {
if (e.namespace) {
this.play(t, s);
}
}, this),
'stop.owl.autoplay': $.proxy(function(e) {
if (e.namespace) {
this.stop();
}
}, this),
'mouseover.owl.autoplay': $.proxy(function() {
if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) {
this.pause();
}
}, this),
'mouseleave.owl.autoplay': $.proxy(function() {
if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) {
this.play();
}
}, this),
'touchstart.owl.core': $.proxy(function() {
if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) {
this.pause();
}
}, this),
'touchend.owl.core': $.proxy(function() {
if (this._core.settings.autoplayHoverPause) {
this.play();
}
}, this)
};
// register event handlers
this._core.$element.on(this._handlers);
// set default options
this._core.options = $.extend({}, Autoplay.Defaults, this._core.options);
};
/**
* Default options.
* @public
*/
Autoplay.Defaults = {
autoplay: false,
autoplayTimeout: 5000,
autoplayHoverPause: false,
autoplaySpeed: false
};
/**
* Starts the autoplay.
* @public
* @param {Number} [timeout] - The interval before the next animation starts.
* @param {Number} [speed] - The animation speed for the animations.
*/
Autoplay.prototype.play = function(timeout, speed) {
this._paused = false;
if (this._core.is('rotating')) {
return;
}
this._core.enter('rotating');
this._setAutoPlayInterval();
};
/**
* Gets a new timeout
* @private
* @param {Number} [timeout] - The interval before the next animation starts.
* @param {Number} [speed] - The animation speed for the animations.
* @return {Timeout}
*/
Autoplay.prototype._getNextTimeout = function(timeout, speed) {
if ( this._timeout ) {
window.clearTimeout(this._timeout);
}
return window.setTimeout($.proxy(function() {
if (this._paused || this._core.is('busy') || this._core.is('interacting') || document.hidden) {
return;
}
this._core.next(speed || this._core.settings.autoplaySpeed);
}, this), timeout || this._core.settings.autoplayTimeout);
};
/**
* Sets autoplay in motion.
* @private
*/
Autoplay.prototype._setAutoPlayInterval = function() {
this._timeout = this._getNextTimeout();
};
/**
* Stops the autoplay.
* @public
*/
Autoplay.prototype.stop = function() {
if (!this._core.is('rotating')) {
return;
}
window.clearTimeout(this._timeout);
this._core.leave('rotating');
};
/**
* Stops the autoplay.
* @public
*/
Autoplay.prototype.pause = function() {
if (!this._core.is('rotating')) {
return;
}
this._paused = true;
};
/**
* Destroys the plugin.
*/
Autoplay.prototype.destroy = function() {
var handler, property;
this.stop();
for (handler in this._handlers) {
this._core.$element.off(handler, this._handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.autoplay = Autoplay;
})(window.Zepto || window.jQuery, window, document);
/**
* Navigation Plugin
* @version 2.1.0
* @author Artus Kolanowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
'use strict';
/**
* Creates the navigation plugin.
* @class The Navigation Plugin
* @param {Owl} carousel - The Owl Carousel.
*/
var Navigation = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* Indicates whether the plugin is initialized or not.
* @protected
* @type {Boolean}
*/
this._initialized = false;
/**
* The current paging indexes.
* @protected
* @type {Array}
*/
this._pages = [];
/**
* All DOM elements of the user interface.
* @protected
* @type {Object}
*/
this._controls = {};
/**
* Markup for an indicator.
* @protected
* @type {Array.}
*/
this._templates = [];
/**
* The carousel element.
* @type {jQuery}
*/
this.$element = this._core.$element;
/**
* Overridden methods of the carousel.
* @protected
* @type {Object}
*/
this._overrides = {
next: this._core.next,
prev: this._core.prev,
to: this._core.to
};
/**
* All event handlers.
* @protected
* @type {Object}
*/
this._handlers = {
'prepared.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.dotsData) {
this._templates.push('' +
$(e.content).find('[data-dot]').addBack('[data-dot]').attr('data-dot') + '
');
}
}, this),
'added.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.dotsData) {
this._templates.splice(e.position, 0, this._templates.pop());
}
}, this),
'remove.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.dotsData) {
this._templates.splice(e.position, 1);
}
}, this),
'changed.owl.carousel': $.proxy(function(e) {
if (e.namespace && e.property.name == 'position') {
this.draw();
}
}, this),
'initialized.owl.carousel': $.proxy(function(e) {
if (e.namespace && !this._initialized) {
this._core.trigger('initialize', null, 'navigation');
this.initialize();
this.update();
this.draw();
this._initialized = true;
this._core.trigger('initialized', null, 'navigation');
}
}, this),
'refreshed.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._initialized) {
this._core.trigger('refresh', null, 'navigation');
this.update();
this.draw();
this._core.trigger('refreshed', null, 'navigation');
}
}, this)
};
// set default options
this._core.options = $.extend({}, Navigation.Defaults, this._core.options);
// register event handlers
this.$element.on(this._handlers);
};
/**
* Default options.
* @public
* @todo Rename `slideBy` to `navBy`
*/
Navigation.Defaults = {
nav: false,
navText: [ 'prev', 'next' ],
navSpeed: false,
navElement: 'div',
navContainer: false,
navContainerClass: 'owl-nav',
navClass: [ 'owl-prev', 'owl-next' ],
slideBy: 1,
dotClass: 'owl-dot',
dotsClass: 'owl-dots',
dots: true,
dotsEach: false,
dotsData: false,
dotsSpeed: false,
dotsContainer: false
};
/**
* Initializes the layout of the plugin and extends the carousel.
* @protected
*/
Navigation.prototype.initialize = function() {
var override,
settings = this._core.settings;
// create DOM structure for relative navigation
this._controls.$relative = (settings.navContainer ? $(settings.navContainer)
: $('').addClass(settings.navContainerClass).appendTo(this.$element)).addClass('disabled');
this._controls.$previous = $('<' + settings.navElement + '>')
.addClass(settings.navClass[0])
.html(settings.navText[0])
.prependTo(this._controls.$relative)
.on('click', $.proxy(function(e) {
this.prev(settings.navSpeed);
}, this));
this._controls.$next = $('<' + settings.navElement + '>')
.addClass(settings.navClass[1])
.html(settings.navText[1])
.appendTo(this._controls.$relative)
.on('click', $.proxy(function(e) {
this.next(settings.navSpeed);
}, this));
// create DOM structure for absolute navigation
if (!settings.dotsData) {
this._templates = [ $('
')
.addClass(settings.dotClass)
.append($('
'))
.prop('outerHTML') ];
}
this._controls.$absolute = (settings.dotsContainer ? $(settings.dotsContainer)
: $('').addClass(settings.dotsClass).appendTo(this.$element)).addClass('disabled');
this._controls.$absolute.on('click', 'div', $.proxy(function(e) {
var index = $(e.target).parent().is(this._controls.$absolute)
? $(e.target).index() : $(e.target).parent().index();
e.preventDefault();
this.to(index, settings.dotsSpeed);
}, this));
// override public methods of the carousel
for (override in this._overrides) {
this._core[override] = $.proxy(this[override], this);
}
};
/**
* Destroys the plugin.
* @protected
*/
Navigation.prototype.destroy = function() {
var handler, control, property, override;
for (handler in this._handlers) {
this.$element.off(handler, this._handlers[handler]);
}
for (control in this._controls) {
this._controls[control].remove();
}
for (override in this.overides) {
this._core[override] = this._overrides[override];
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
/**
* Updates the internal state.
* @protected
*/
Navigation.prototype.update = function() {
var i, j, k,
lower = this._core.clones().length / 2,
upper = lower + this._core.items().length,
maximum = this._core.maximum(true),
settings = this._core.settings,
size = settings.center || settings.autoWidth || settings.dotsData
? 1 : settings.dotsEach || settings.items;
if (settings.slideBy !== 'page') {
settings.slideBy = Math.min(settings.slideBy, settings.items);
}
if (settings.dots || settings.slideBy == 'page') {
this._pages = [];
for (i = lower, j = 0, k = 0; i < upper; i++) {
if (j >= size || j === 0) {
this._pages.push({
start: Math.min(maximum, i - lower),
end: i - lower + size - 1
});
if (Math.min(maximum, i - lower) === maximum) {
break;
}
j = 0, ++k;
}
j += this._core.mergers(this._core.relative(i));
}
}
};
/**
* Draws the user interface.
* @todo The option `dotsData` wont work.
* @protected
*/
Navigation.prototype.draw = function() {
var difference,
settings = this._core.settings,
disabled = this._core.items().length <= settings.items,
index = this._core.relative(this._core.current()),
loop = settings.loop || settings.rewind;
this._controls.$relative.toggleClass('disabled', !settings.nav || disabled);
if (settings.nav) {
this._controls.$previous.toggleClass('disabled', !loop && index <= this._core.minimum(true));
this._controls.$next.toggleClass('disabled', !loop && index >= this._core.maximum(true));
}
this._controls.$absolute.toggleClass('disabled', !settings.dots || disabled);
if (settings.dots) {
difference = this._pages.length - this._controls.$absolute.children().length;
if (settings.dotsData && difference !== 0) {
this._controls.$absolute.html(this._templates.join(''));
} else if (difference > 0) {
this._controls.$absolute.append(new Array(difference + 1).join(this._templates[0]));
} else if (difference < 0) {
this._controls.$absolute.children().slice(difference).remove();
}
this._controls.$absolute.find('.active').removeClass('active');
this._controls.$absolute.children().eq($.inArray(this.current(), this._pages)).addClass('active');
}
};
/**
* Extends event data.
* @protected
* @param {Event} event - The event object which gets thrown.
*/
Navigation.prototype.onTrigger = function(event) {
var settings = this._core.settings;
event.page = {
index: $.inArray(this.current(), this._pages),
count: this._pages.length,
size: settings && (settings.center || settings.autoWidth || settings.dotsData
? 1 : settings.dotsEach || settings.items)
};
};
/**
* Gets the current page position of the carousel.
* @protected
* @returns {Number}
*/
Navigation.prototype.current = function() {
var current = this._core.relative(this._core.current());
return $.grep(this._pages, $.proxy(function(page, index) {
return page.start <= current && page.end >= current;
}, this)).pop();
};
/**
* Gets the current succesor/predecessor position.
* @protected
* @returns {Number}
*/
Navigation.prototype.getPosition = function(successor) {
var position, length,
settings = this._core.settings;
if (settings.slideBy == 'page') {
position = $.inArray(this.current(), this._pages);
length = this._pages.length;
successor ? ++position : --position;
position = this._pages[((position % length) + length) % length].start;
} else {
position = this._core.relative(this._core.current());
length = this._core.items().length;
successor ? position += settings.slideBy : position -= settings.slideBy;
}
return position;
};
/**
* Slides to the next item or page.
* @public
* @param {Number} [speed=false] - The time in milliseconds for the transition.
*/
Navigation.prototype.next = function(speed) {
$.proxy(this._overrides.to, this._core)(this.getPosition(true), speed);
};
/**
* Slides to the previous item or page.
* @public
* @param {Number} [speed=false] - The time in milliseconds for the transition.
*/
Navigation.prototype.prev = function(speed) {
$.proxy(this._overrides.to, this._core)(this.getPosition(false), speed);
};
/**
* Slides to the specified item or page.
* @public
* @param {Number} position - The position of the item or page.
* @param {Number} [speed] - The time in milliseconds for the transition.
* @param {Boolean} [standard=false] - Whether to use the standard behaviour or not.
*/
Navigation.prototype.to = function(position, speed, standard) {
var length;
if (!standard && this._pages.length) {
length = this._pages.length;
$.proxy(this._overrides.to, this._core)(this._pages[((position % length) + length) % length].start, speed);
} else {
$.proxy(this._overrides.to, this._core)(position, speed);
}
};
$.fn.owlCarousel.Constructor.Plugins.Navigation = Navigation;
})(window.Zepto || window.jQuery, window, document);
/**
* Hash Plugin
* @version 2.1.0
* @author Artus Kolanowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
'use strict';
/**
* Creates the hash plugin.
* @class The Hash Plugin
* @param {Owl} carousel - The Owl Carousel
*/
var Hash = function(carousel) {
/**
* Reference to the core.
* @protected
* @type {Owl}
*/
this._core = carousel;
/**
* Hash index for the items.
* @protected
* @type {Object}
*/
this._hashes = {};
/**
* The carousel element.
* @type {jQuery}
*/
this.$element = this._core.$element;
/**
* All event handlers.
* @protected
* @type {Object}
*/
this._handlers = {
'initialized.owl.carousel': $.proxy(function(e) {
if (e.namespace && this._core.settings.startPosition === 'URLHash') {
$(window).trigger('hashchange.owl.navigation');
}
}, this),
'prepared.owl.carousel': $.proxy(function(e) {
if (e.namespace) {
var hash = $(e.content).find('[data-hash]').addBack('[data-hash]').attr('data-hash');
if (!hash) {
return;
}
this._hashes[hash] = e.content;
}
}, this),
'changed.owl.carousel': $.proxy(function(e) {
if (e.namespace && e.property.name === 'position') {
var current = this._core.items(this._core.relative(this._core.current())),
hash = $.map(this._hashes, function(item, hash) {
return item === current ? hash : null;
}).join();
if (!hash || window.location.hash.slice(1) === hash) {
return;
}
window.location.hash = hash;
}
}, this)
};
// set default options
this._core.options = $.extend({}, Hash.Defaults, this._core.options);
// register the event handlers
this.$element.on(this._handlers);
// register event listener for hash navigation
$(window).on('hashchange.owl.navigation', $.proxy(function(e) {
var hash = window.location.hash.substring(1),
items = this._core.$stage.children(),
position = this._hashes[hash] && items.index(this._hashes[hash]);
if (position === undefined || position === this._core.current()) {
return;
}
this._core.to(this._core.relative(position), false, true);
}, this));
};
/**
* Default options.
* @public
*/
Hash.Defaults = {
URLhashListener: false
};
/**
* Destroys the plugin.
* @public
*/
Hash.prototype.destroy = function() {
var handler, property;
$(window).off('hashchange.owl.navigation');
for (handler in this._handlers) {
this._core.$element.off(handler, this._handlers[handler]);
}
for (property in Object.getOwnPropertyNames(this)) {
typeof this[property] != 'function' && (this[property] = null);
}
};
$.fn.owlCarousel.Constructor.Plugins.Hash = Hash;
})(window.Zepto || window.jQuery, window, document);
/**
* Support Plugin
*
* @version 2.1.0
* @author Vivid Planet Software GmbH
* @author Artus Kolanowski
* @author David Deutsch
* @license The MIT License (MIT)
*/
;(function($, window, document, undefined) {
var style = $('
').get(0).style,
prefixes = 'Webkit Moz O ms'.split(' '),
events = {
transition: {
end: {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd',
transition: 'transitionend'
}
},
animation: {
end: {
WebkitAnimation: 'webkitAnimationEnd',
MozAnimation: 'animationend',
OAnimation: 'oAnimationEnd',
animation: 'animationend'
}
}
},
tests = {
csstransforms: function() {
return !!test('transform');
},
csstransforms3d: function() {
return !!test('perspective');
},
csstransitions: function() {
return !!test('transition');
},
cssanimations: function() {
return !!test('animation');
}
};
function test(property, prefixed) {
var result = false,
upper = property.charAt(0).toUpperCase() + property.slice(1);
$.each((property + ' ' + prefixes.join(upper + ' ') + upper).split(' '), function(i, property) {
if (style[property] !== undefined) {
result = prefixed ? property : true;
return false;
}
});
return result;
}
function prefixed(property) {
return test(property, true);
}
if (tests.csstransitions()) {
/* jshint -W053 */
$.support.transition = new String(prefixed('transition'))
$.support.transition.end = events.transition.end[ $.support.transition ];
}
if (tests.cssanimations()) {
/* jshint -W053 */
$.support.animation = new String(prefixed('animation'))
$.support.animation.end = events.animation.end[ $.support.animation ];
}
if (tests.csstransforms()) {
/* jshint -W053 */
$.support.transform = new String(prefixed('transform'));
$.support.transform3d = tests.csstransforms3d();
}
})(window.Zepto || window.jQuery, window, document);
/*!
* GSAP 3.10.4
* https://greensock.com
*
* @license Copyright 2022, GreenSock. All rights reserved.
* Subject to the terms at https://greensock.com/standard-license or for Club GreenSock members, the agreement issued with that membership.
* @author: Jack Doyle, jack@greensock.com
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function o(t){return"string"==typeof t}function p(t){return"function"==typeof t}function q(t){return"number"==typeof t}function r(t){return void 0===t}function s(t){return"object"==typeof t}function t(t){return!1!==t}function u(){return"undefined"!=typeof window}function v(t){return p(t)||o(t)}function M(t){return(h=mt(t,ot))&&he}function N(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function O(t,e){return!e&&console.warn(t)}function P(t,e){return t&&(ot[t]=e)&&h&&(h[t]=e)||ot}function Q(){return 0}function $(t){var e,r,i=t[0];if(s(i)||p(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=pt.length;r--&&!pt[r].targetTest(i););e=pt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Lt(t[r],e)))||t.splice(r,1);return t}function _(t){return t._gsap||$(xt(t))[0]._gsap}function aa(t,e,i){return(i=t[e])&&p(i)?t[e]():r(i)&&t.getAttribute&&t.getAttribute(e)||i}function ba(t,e){return(t=t.split(",")).forEach(e)||t}function ca(t){return Math.round(1e5*t)/1e5||0}function da(t){return Math.round(1e7*t)/1e7||0}function ea(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function fa(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++ia;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function sa(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function ta(t,e){!t.parent||e&&!t.parent.autoRemoveChildren||t.parent.remove(t),t._act=0}function ua(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function xa(t){return t._repeat?gt(t._tTime,t=t.duration()+t._rDelay)*t:0}function za(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Aa(t){return t._end=da(t._start+(t._tDur/Math.abs(t._ts||t._rts||V)||0))}function Ba(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=da(r._time-(0V)&&e.render(r,!0)),ua(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur(n=Math.abs(n))&&(a=i,o=n);return a}function mb(t){return ta(t),t.scrollTrigger&&t.scrollTrigger.kill(!1),t.progress()<1&&Ot(t,"onInterrupt"),t}function rb(t,e,r){return(6*(t+=t<0?1:1>16,t>>8&Pt,t&Pt]:0:Mt.black;if(!_){if(","===t.substr(-1)&&(t=t.substr(0,t.length-1)),Mt[t])_=Mt[t];else if("#"===t.charAt(0)){if(t.length<6&&(t="#"+(i=t.charAt(1))+i+(n=t.charAt(2))+n+(a=t.charAt(3))+a+(5===t.length?t.charAt(4)+t.charAt(4):"")),9===t.length)return[(_=parseInt(t.substr(1,6),16))>>16,_>>8&Pt,_&Pt,parseInt(t.substr(7),16)/255];_=[(t=parseInt(t.substr(1),16))>>16,t>>8&Pt,t&Pt]}else if("hsl"===t.substr(0,3))if(_=d=t.match(tt),e){if(~t.indexOf("="))return _=t.match(et),r&&_.length<4&&(_[3]=1),_}else s=+_[0]%360/360,o=_[1]/100,i=2*(u=_[2]/100)-(n=u<=.5?u*(o+1):u+o-u*o),3<_.length&&(_[3]*=1),_[0]=rb(s+1/3,i,n),_[1]=rb(s,i,n),_[2]=rb(s-1/3,i,n);else _=t.match(tt)||Mt.transparent;_=_.map(Number)}return e&&!d&&(i=_[0]/Pt,n=_[1]/Pt,a=_[2]/Pt,u=((h=Math.max(i,n,a))+(l=Math.min(i,n,a)))/2,h===l?s=o=0:(f=h-l,o=.5=Y?u.endTime(!1):t._dur;return o(e)&&(isNaN(e)||e in s)?(n=e.charAt(0),a="%"===e.substr(-1),i=e.indexOf("="),"<"===n||">"===n?(0<=i&&(e=e.replace(/=/,"")),("<"===n?u._start:u.endTime(0<=u._repeat))+(parseFloat(e.substr(1))||0)*(a?(i<0?u:r).totalDuration()/100:1)):i<0?(e in s||(s[e]=h),s[e]):(n=parseFloat(e.charAt(i-1)+e.substr(i+1)),a&&r&&(n=n/100*(J(r)?r[0]:r).totalDuration()),1=r&&te)return i;i=i._next}else for(i=t._last;i&&i._start>=r;){if("isPause"===i.data&&i._start=n._start)&&n._ts&&h!==n){if(n.parent!==this)return this.render(t,e,r);if(n.render(0=this.totalDuration()||!v&&c)&&(f!==this._start&&Math.abs(l)===Math.abs(this._ts)||this._lock||(!t&&g||!(v===m&&0=i&&(a instanceof $t?e&&n.push(a):(r&&n.push(a),t&&n.push.apply(n,a.getChildren(!0,e,r)))),a=a._next;return n},e.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},e.remove=function remove(t){return o(t)?this.removeLabel(t):p(t)?this.killTweensOf(t):(sa(this,t),t===this._recent&&(this._recent=this._last),ua(this))},e.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,!this._dp&&this._ts&&(this._start=da(Dt.time-(0e:!e||a.isActive())&&i.push(a):(r=a.getTweensOf(n,e)).length&&i.push.apply(i,r),a=a._next;return i},e.tweenTo=function tweenTo(t,e){e=e||{};var r,i=this,n=Tt(i,t),a=e.startAt,s=e.onStart,o=e.onStartParams,u=e.immediateRender,h=$t.to(i,ka({ease:e.ease||"none",lazy:!1,immediateRender:!1,time:n,overwrite:"auto",duration:e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale())||V,onStart:function onStart(){if(i.pause(),!r){var t=e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale());h._dur!==t&&Ka(h,t,0,1).render(h._time,!0,!0),r=1}s&&s.apply(h,o||[])}},e));return u?h.render(0):h},e.tweenFromTo=function tweenFromTo(t,e,r){return this.tweenTo(e,ka({startAt:{time:Tt(this,t)}},r))},e.recent=function recent(){return this._recent},e.nextLabel=function nextLabel(t){return void 0===t&&(t=this._time),kb(this,Tt(this,t))},e.previousLabel=function previousLabel(t){return void 0===t&&(t=this._time),kb(this,Tt(this,t),1)},e.currentLabel=function currentLabel(t){return arguments.length?this.seek(t,!0):this.previousLabel(this._time+V)},e.shiftChildren=function shiftChildren(t,e,r){void 0===r&&(r=0);for(var i,n=this._first,a=this.labels;n;)n._start>=r&&(n._start+=t,n._end+=t),n=n._next;if(e)for(i in a)a[i]>=r&&(a[i]+=t);return ua(this)},e.invalidate=function invalidate(){var t=this._first;for(this._lock=0;t;)t.invalidate(),t=t._next;return n.prototype.invalidate.call(this)},e.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._dp&&(this._time=this._tTime=this._pTime=0),t&&(this.labels={}),ua(this)},e.totalDuration=function totalDuration(t){var e,r,i,n=0,a=this,s=a._last,o=Y;if(arguments.length)return a.timeScale((a._repeat<0?a.duration():a.totalDuration())/(a.reversed()?-t:t));if(a._dirty){for(i=a.parent;s;)e=s._prev,s._dirty&&s.totalDuration(),o<(r=s._start)&&a._sort&&s._ts&&!a._lock?(a._lock=1,Da(a,s,r-s._delay,1)._lock=0):o=r,r<0&&s._ts&&(n-=r,(!i&&!a._dp||i&&i.smoothChildTiming)&&(a._start+=r/a._ts,a._time-=r,a._tTime-=r),a.shiftChildren(-r,!1,-Infinity),o=0),s._end>n&&s._ts&&(n=s._end),s=e;Ka(a,a===I&&a._time>n?a._time:n,1,1),a._dirty=0}return a._tDur},Timeline.updateRoot=function updateRoot(t){if(I._ts&&(ha(I,za(t,I)),f=Dt.frame),Dt.frame>=_t){_t+=U.autoSleep||120;var e=I._first;if((!e||!e._ts)&&U.autoSleep&&Dt._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||Dt.sleep()}}},Timeline}(Nt);ka(Ut.prototype,{_lock:0,_hasPause:0,_forcing:0});function Ub(t,e,r,i,n,a){var u,h,l,f;if(ft[t]&&!1!==(u=new ft[t]).init(n,u.rawVars?e[t]:function _processVars(t,e,r,i,n){if(p(t)&&(t=Qt(t,n,e,r,i)),!s(t)||t.style&&t.nodeType||J(t)||H(t))return o(t)?Qt(t,n,e,r,i):t;var a,u={};for(a in t)u[a]=Qt(t[a],n,e,r,i);return u}(e[t],i,n,a,r),r,i,a)&&(r._pt=h=new oe(r._pt,n,t,0,1,u.render,u,0,u.priority),r!==d))for(l=r._ptLookup[r._targets.indexOf(n)],f=u._props.length;f--;)l[u._props[f]]=h;return u}function $b(t,r,e,i){var n,a,s=r.ease||i||"power1.inOut";if(J(r))a=e[t]||(e[t]=[]),r.forEach(function(t,e){return a.push({t:e/(r.length-1)*100,v:t,e:s})});else for(n in r)a=e[n]||(e[n]=[]),"ease"===n||a.push({t:parseFloat(t),v:r[n],e:s})}var Yt,qt,Vt=function _addPropTween(t,e,r,i,n,a,s,u,h){p(i)&&(i=i(n||0,t,a));var l,f=t[e],d="get"!==r?r:p(f)?h?t[e.indexOf("set")||!p(t["get"+e.substr(3)])?e:"get"+e.substr(3)](h):t[e]():f,_=p(f)?h?Zt:Wt:Gt;if(o(i)&&(~i.indexOf("random(")&&(i=hb(i)),"="===i.charAt(1)&&(!(l=ea(d,i)+(Ra(d)||0))&&0!==l||(i=l))),d!==i||qt)return isNaN(d*i)||""===i?(f||e in t||N(e,i),function _addComplexStringPropTween(t,e,r,i,n,a,s){var o,u,h,l,f,d,_,p,c=new oe(this._pt,t,e,0,1,ee,null,n),m=0,g=0;for(c.b=r,c.e=i,r+="",(_=~(i+="").indexOf("random("))&&(i=hb(i)),a&&(a(p=[r,i],t,e),r=p[0],i=p[1]),u=r.match(it)||[];o=it.exec(i);)l=o[0],f=i.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(d=parseFloat(u[g-1])||0,c._pt={_next:c._pt,p:f||1===g?f:",",s:d,c:"="===l.charAt(1)?ea(d,l)-d:parseFloat(l)-d,m:h&&h<4?Math.round:0},m=it.lastIndex);return c.c=m")});else{for(f in h={},w)"ease"===f||"easeEach"===f||$b(f,w[f],h,w.easeEach);for(f in h)for(A=h[f].sort(function(t,e){return t.t-e.t}),u=z=0;u=t._tDur||e<0)&&t.ratio===u&&(u&&ta(t,1),r||(Ot(t,u?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}else t._zTime||(t._zTime=e)}(this,t,e,r);return this},e.targets=function targets(){return this._targets},e.invalidate=function invalidate(){return this._pt=this._op=this._startAt=this._onUpdate=this._lazy=this.ratio=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(),R.prototype.invalidate.call(this)},e.resetTo=function resetTo(t,e,r,i){c||Dt.wake(),this._ts||this.play();var n,a=Math.min(this._dur,(this._dp._time-this._start)*this._ts);return this._initted||Xt(this,a),n=this._ease(a/this._dur),function _updatePropTweens(t,e,r,i,n,a,s){var o,u,h,l=(t._pt&&t._ptCache||(t._ptCache={}))[e];if(!l)for(l=t._ptCache[e]=[],u=t._ptLookup,h=t._targets.length;h--;){if((o=u[h][e])&&o.d&&o.d._pt)for(o=o.d._pt;o&&o.p!==e;)o=o._next;if(!o)return qt=1,t.vars[e]="+=0",Xt(t,s),qt=0,1;l.push(o)}for(h=l.length;h--;)(o=l[h]).s=!i&&0!==i||n?o.s+(i||0)+a*o.c:i,o.c=r-o.s,o.e&&(o.e=ca(r)+Ra(o.e)),o.b&&(o.b=o.s+Ra(o.b))}(this,t,e,r,i,n,a)?this.resetTo(t,e,r,i):(Ba(this,0),this.parent||ra(this._dp,this,"_first","_last",this._dp._sort?"_start":0),this.render(0))},e.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e))return this._lazy=this._pt=0,this.parent?mb(this):this;if(this.timeline){var r=this.timeline.totalDuration();return this.timeline.killTweensOf(t,e,Yt&&!0!==Yt.vars.overwrite)._first||mb(this),this.parent&&r!==this.timeline.totalDuration()&&Ka(this,this._dur*this.timeline._tDur/r,0,1),this}var i,n,a,s,u,h,l,f=this._targets,d=t?xt(t):f,p=this._ptLookup,c=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,i=r===e.length;i&&r--&&t[r]===e[r];);return r<0}(f,d))return"all"===e&&(this._pt=0),mb(this);for(i=this._op=this._op||[],"all"!==e&&(o(e)&&(u={},ba(e,function(t){return u[t]=1}),e=u),e=function _addAliasesToVars(t,e){var r,i,n,a,s=t[0]?_(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(i in r=mt({},e),o)if(i in r)for(n=(a=o[i].split(",")).length;n--;)r[a[n]]=r[i];return r}(f,e)),l=f.length;l--;)if(~d.indexOf(f[l]))for(u in n=p[l],"all"===e?(i[l]=e,s=n,a={}):(a=i[l]=i[l]||{},s=e),s)(h=n&&n[u])&&("kill"in h.d&&!0!==h.d.kill(u)||sa(this,h,"_pt"),delete n[u]),"all"!==a&&(a[u]=1);return this._initted&&!this._pt&&c&&mb(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return Oa(1,arguments)},Tween.delayedCall=function delayedCall(t,e,r,i){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:i})},Tween.fromTo=function fromTo(t,e,r){return Oa(2,arguments)},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return I.killTweensOf(t,e,r)},Tween}(Nt);ka($t.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),ba("staggerTo,staggerFrom,staggerFromTo",function(r){$t[r]=function(){var t=new Ut,e=wt.call(arguments,0);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function gc(t,e,r){return t.setAttribute(e,r)}function oc(t,e,r,i){i.mSet(t,e,i.m.call(i.tween,r,i.mt),i)}var Gt=function _setterPlain(t,e,r){return t[e]=r},Wt=function _setterFunc(t,e,r){return t[e](r)},Zt=function _setterFuncWithParam(t,e,r,i){return t[e](i.fp,r)},Ht=function _getSetter(t,e){return p(t[e])?Wt:r(t[e])&&t.setAttribute?gc:Gt},Jt=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e6*(e.s+e.c*t))/1e6,e)},te=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},ee=function _renderComplexString(t,e){var r=e._pt,i="";if(!t&&e.b)i=e.b;else if(1===t&&e.e)i=e.e;else{for(;r;)i=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+i,r=r._next;i+=e.c}e.set(e.t,e.p,i,e)},re=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},ie=function _addPluginModifier(t,e,r,i){for(var n,a=this._pt;a;)n=a._next,a.p===i&&a.modifier(t,e,r),a=n},ne=function _killPropTweensOf(t){for(var e,r,i=this._pt;i;)r=i._next,i.p===t&&!i.op||i.op===t?sa(this,i,"_pt"):i.dep||(e=1),i=r;return!e},se=function _sortPropTweensByPriority(t){for(var e,r,i,n,a=t._pt;a;){for(e=a._next,r=i;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:n)?a._prev._next=a:i=a,(a._next=r)?r._prev=a:n=a,a=e}t._pt=i},oe=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=oc,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,i,n,a,s,o,u){this.t=e,this.s=i,this.c=n,this.p=r,this.r=a||Jt,this.d=s||this,this.set=o||Gt,this.pr=u||0,(this._next=t)&&(t._prev=this)}ba(ct+"parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert,scrollTrigger",function(t){return ut[t]=1}),ot.TweenMax=ot.TweenLite=$t,ot.TimelineLite=ot.TimelineMax=Ut,I=new Ut({sortChildren:!1,defaults:L,autoRemoveChildren:!0,id:"root",smoothChildTiming:!0}),U.stringFilter=xb;var ue={registerPlugin:function registerPlugin(){for(var t=arguments.length,e=new Array(t),r=0;r= 0) {
var data = classes[index].split('_');
datas[0][data[2]] = data[3]
}
}
}
var iData;
for(iData = 2; ; iData++) {
if($el.data("parallax"+iData)) {
datas.push($el.data("parallax-"+iData));
}
else {
break;
}
}
var datasLength = datas.length;
for(iData = 0; iData < datasLength; iData ++) {
var data = datas[iData];
var scrollFrom = data["from-scroll"];
if (scrollFrom == undefined) scrollFrom = Math.max(0, $(el).offset().top - windowHeight);
scrollFrom = scrollFrom | 0;
var scrollDistance = data["distance"];
var scrollTo = data["to-scroll"];
if (scrollDistance == undefined && scrollTo == undefined) scrollDistance = windowHeight;
scrollDistance = Math.max(scrollDistance | 0, 1);
var easing = data["easing"];
var easingReturn = data["easing-return"];
if (easing == undefined || !$.easing|| !$.easing[easing]) easing = null;
if (easingReturn == undefined || !$.easing|| !$.easing[easingReturn]) easingReturn = easing;
if (easing) {
var totalTime = data["duration"];
if (totalTime == undefined) totalTime = scrollDistance;
totalTime = Math.max(totalTime | 0, 1);
var totalTimeReturn = data["duration-return"];
if (totalTimeReturn == undefined) totalTimeReturn = totalTime;
scrollDistance = 1;
var currentTime = $el.data("current-time");
if(currentTime == undefined) currentTime = 0;
}
if (scrollTo == undefined) scrollTo = scrollFrom + scrollDistance;
scrollTo = scrollTo | 0;
var smoothness = data["smoothness"];
if (smoothness == undefined) smoothness = 30;
smoothness = smoothness | 0;
if (noSmooth || smoothness == 0) smoothness = 1;
smoothness = smoothness | 0;
var scrollCurrent = scroll;
scrollCurrent = Math.max(scrollCurrent, scrollFrom);
scrollCurrent = Math.min(scrollCurrent, scrollTo);
if(easing) {
if($el.data("sens") == undefined) $el.data("sens", "back");
if(scrollCurrent>scrollFrom) {
if($el.data("sens") == "back") {
currentTime = 1;
$el.data("sens", "go");
}
else {
currentTime++;
}
}
if(scrollCurrent0 && currentTime<=totalTime) {
var from = defaultProp;
if($el.data("sens") == "back") {
from = to;
to = -to;
easing = easingReturn;
totalTime = totalTimeReturn;
}
val = $.easing[easing](null, currentTime, from, to, totalTime);
}
val = Math.ceil(val * this.round) / this.round;
if(val==prev&&next==to) val = to;
if(!properties[prop]) properties[prop] = 0;
properties[prop] += val;
if (prev != properties[prop]) {
$el.data("_" + prop, properties[prop]);
applyProperties = true;
}
}, this));
}
if (applyProperties) {
if (properties["z"] != undefined) {
var perspective = data["perspective"];
if (perspective == undefined) perspective = 800;
var $parent = $el.parent();
if(!$parent.data("style")) $parent.data("style", $parent.attr("style") || "");
$parent.attr("style", "perspective:" + perspective + "px; -webkit-perspective:" + perspective + "px; "+ $parent.data("style"));
}
if(properties["scaleX"] == undefined) properties["scaleX"] = 1;
if(properties["scaleY"] == undefined) properties["scaleY"] = 1;
if(properties["scaleZ"] == undefined) properties["scaleZ"] = 1;
if (properties["scale"] != undefined) {
properties["scaleX"] *= properties["scale"];
properties["scaleY"] *= properties["scale"];
properties["scaleZ"] *= properties["scale"];
}
var translate3d = "translate3d(" + (properties["x"] ? properties["x"] : 0) + "px, " + (properties["y"] ? properties["y"] : 0) + "px, " + (properties["z"] ? properties["z"] : 0) + "px)";
var rotate3d = "rotateX(" + (properties["rotateX"] ? properties["rotateX"] : 0) + "deg) rotateY(" + (properties["rotateY"] ? properties["rotateY"] : 0) + "deg) rotateZ(" + (properties["rotateZ"] ? properties["rotateZ"] : 0) + "deg)";
var scale3d = "scaleX(" + properties["scaleX"] + ") scaleY(" + properties["scaleY"] + ") scaleZ(" + properties["scaleZ"] + ")";
var cssTransform = translate3d + " " + rotate3d + " " + scale3d + ";";
this._log(cssTransform);
$el.attr("style", "transform:" + cssTransform + " -webkit-transform:" + cssTransform + " " + style);
}
}, this));
if(window.requestAnimationFrame) {
window.requestAnimationFrame($.proxy(this._onScroll, this, false));
}
else {
this._requestAnimationFrame($.proxy(this._onScroll, this, false));
}
}
};
})(jQuery);
/*
* jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
*
* Uses the built in easing capabilities added In jQuery 1.1
* to offer multiple easing options
*
* TERMS OF USE - jQuery Easing
*
* Open source under the BSD License.
*
* Copyright © 2008 George McGinley Smith
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
// t: current time, b: begInnIng value, c: change In value, d: duration
jQuery.easing['jswing'] = jQuery.easing['swing'];
jQuery.extend(jQuery.easing,
{
def: 'easeOutQuad',
swing: function (x, t, b, c, d) {
//alert(jQuery.easing.default);
return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
},
easeInQuad: function (x, t, b, c, d) {
return c * (t /= d) * t + b;
},
easeOutQuad: function (x, t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
easeInOutQuad: function (x, t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
},
easeInCubic: function (x, t, b, c, d) {
return c * (t /= d) * t * t + b;
},
easeOutCubic: function (x, t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
},
easeInOutCubic: function (x, t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
return c / 2 * ((t -= 2) * t * t + 2) + b;
},
easeInQuart: function (x, t, b, c, d) {
return c * (t /= d) * t * t * t + b;
},
easeOutQuart: function (x, t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
},
easeInOutQuart: function (x, t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
},
easeInQuint: function (x, t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
},
easeOutQuint: function (x, t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
easeInOutQuint: function (x, t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
},
easeInSine: function (x, t, b, c, d) {
return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
},
easeOutSine: function (x, t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b;
},
easeInOutSine: function (x, t, b, c, d) {
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
},
easeInExpo: function (x, t, b, c, d) {
return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
},
easeOutExpo: function (x, t, b, c, d) {
return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
},
easeInOutExpo: function (x, t, b, c, d) {
if (t == 0) return b;
if (t == d) return b + c;
if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
},
easeInCirc: function (x, t, b, c, d) {
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
},
easeOutCirc: function (x, t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
},
easeInOutCirc: function (x, t, b, c, d) {
if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
},
easeInElastic: function (x, t, b, c, d) {
var s = 1.70158; var p = 0; var a = c;
if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3;
if (a < Math.abs(c)) { a = c; var s = p / 4; }
else var s = p / (2 * Math.PI) * Math.asin(c / a);
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
},
easeOutElastic: function (x, t, b, c, d) {
var s = 1.70158; var p = 0; var a = c;
if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3;
if (a < Math.abs(c)) { a = c; var s = p / 4; }
else var s = p / (2 * Math.PI) * Math.asin(c / a);
return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
},
easeInOutElastic: function (x, t, b, c, d) {
var s = 1.70158; var p = 0; var a = c;
if (t == 0) return b; if ((t /= d / 2) == 2) return b + c; if (!p) p = d * (.3 * 1.5);
if (a < Math.abs(c)) { a = c; var s = p / 4; }
else var s = p / (2 * Math.PI) * Math.asin(c / a);
if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
},
easeInBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c * (t /= d) * t * ((s + 1) * t - s) + b;
},
easeOutBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
},
easeInOutBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
},
easeInBounce: function (x, t, b, c, d) {
return c - jQuery.easing.easeOutBounce(x, d - t, 0, c, d) + b;
},
easeOutBounce: function (x, t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
}
},
easeInOutBounce: function (x, t, b, c, d) {
if (t < d / 2) return jQuery.easing.easeInBounce(x, t * 2, 0, c, d) * .5 + b;
return jQuery.easing.easeOutBounce(x, t * 2 - d, 0, c, d) * .5 + c * .5 + b;
}
});
/*
Plugin: jQuery Parallax
Version 1.1.3
Author: Ian Lunn
Twitter: @IanLunn
Author URL: http://www.ianlunn.co.uk/
Plugin URL: http://www.ianlunn.co.uk/plugins/jquery-parallax/
Dual licensed under the MIT and GPL licenses:
http://www.opensource.org/licenses/mit-license.php
http://www.gnu.org/licenses/gpl.html
*/
(function( $ ){
var $window = $(window);
var windowHeight = $window.height();
$window.on('resize', function () {
windowHeight = $window.height();
});
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
var $this = $(this);
var getHeight;
var firstTop;
var paddingTop = 0;
//get the starting position of each element to have parallax applied to it
$this.each(function(){
firstTop = $this.offset().top;
});
if (outerHeight) {
getHeight = function(jqo) {
return jqo.outerHeight(true);
};
} else {
getHeight = function(jqo) {
return jqo.height();
};
}
// setup defaults if arguments aren't specified
if (arguments.length < 1 || xpos === null) xpos = "50%";
if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
if (arguments.length < 3 || outerHeight === null) outerHeight = true;
// function to be called whenever the window is scrolled or resized
function update(){
var pos = $window.scrollTop();
if (!$this.hasClass('wd-parallax')){
return;
}
$this.each(function(){
var $element = $(this);
var top = $element.offset().top;
var height = getHeight($element);
// Check if totally above or totally below viewport
if (top + height < pos || top > pos + windowHeight) {
return;
}
$this.attr('style', 'background-position: ' + xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px !important");
});
}
$window.bind('scroll', update).on('resize', update);
update();
};
})(jQuery);
/*! PhotoSwipe - v4.1.0 - 2015-07-23
* http://photoswipe.com
* Copyright (c) 2015 Dmitry Semenov; */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.PhotoSwipe = factory();
}
})(this, function () {
'use strict';
var PhotoSwipe = function (template, UiClass, items, options) {
/*>>framework-bridge*/
/**
*
* Set of generic functions used by gallery.
*
* You're free to modify anything here as long as functionality is kept.
*
*/
var framework = {
features: null,
bind: function (target, type, listener, unbind) {
var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
type = type.split(' ');
for (var i = 0; i < type.length; i++) {
if (type[i]) {
target[methodName](type[i], listener, false);
}
}
},
isArray: function (obj) {
return (obj instanceof Array);
},
createEl: function (classes, tag) {
var el = document.createElement(tag || 'div');
if (classes) {
el.className = classes;
}
return el;
},
getScrollY: function () {
var yOffset = window.pageYOffset;
return yOffset !== undefined ? yOffset : document.documentElement.scrollTop;
},
unbind: function (target, type, listener) {
framework.bind(target, type, listener, true);
},
removeClass: function (el, className) {
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
},
addClass: function (el, className) {
if (!framework.hasClass(el, className)) {
el.className += (el.className ? ' ' : '') + className;
}
},
hasClass: function (el, className) {
return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
},
getChildByClass: function (parentEl, childClassName) {
var node = parentEl.firstChild;
while (node) {
if (framework.hasClass(node, childClassName)) {
return node;
}
node = node.nextSibling;
}
},
arraySearch: function (array, value, key) {
var i = array.length;
while (i--) {
if (array[i][key] === value) {
return i;
}
}
return -1;
},
extend: function (o1, o2, preventOverwrite) {
for (var prop in o2) {
if (o2.hasOwnProperty(prop)) {
if (preventOverwrite && o1.hasOwnProperty(prop)) {
continue;
}
o1[prop] = o2[prop];
}
}
},
easing: {
sine: {
out: function (k) {
return Math.sin(k * (Math.PI / 2));
},
inOut: function (k) {
return - (Math.cos(Math.PI * k) - 1) / 2;
}
},
cubic: {
out: function (k) {
return --k * k * k + 1;
}
}
/*
elastic: {
out: function ( k ) {
var s, a = 0.1, p = 0.4;
if ( k === 0 ) return 0;
if ( k === 1 ) return 1;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
},
},
back: {
out: function ( k ) {
var s = 1.70158;
return --k * k * ( ( s + 1 ) * k + s ) + 1;
}
}
*/
},
/**
*
* @return {object}
*
* {
* raf : request animation frame function
* caf : cancel animation frame function
* transfrom : transform property key (with vendor), or null if not supported
* oldIE : IE8 or below
* }
*
*/
detectFeatures: function () {
if (framework.features) {
return framework.features;
}
var helperEl = framework.createEl(),
helperStyle = helperEl.style,
vendor = '',
features = {};
// IE8 and below
features.oldIE = document.all && !document.addEventListener;
features.touch = 'ontouchstart' in window;
if (window.requestAnimationFrame) {
features.raf = window.requestAnimationFrame;
features.caf = window.cancelAnimationFrame;
}
features.pointerEvent = navigator.pointerEnabled || navigator.msPointerEnabled;
// fix false-positive detection of old Android in new IE
// (IE11 ua string contains "Android 4.0")
if (!features.pointerEvent) {
var ua = navigator.userAgent;
// Detect if device is iPhone or iPod and if it's older than iOS 8
// http://stackoverflow.com/a/14223920
//
// This detection is made because of buggy top/bottom toolbars
// that don't trigger window.resize event.
// For more info refer to _isFixedPosition variable in core.js
if (/iP(hone|od)/.test(navigator.platform)) {
var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
if (v && v.length > 0) {
v = parseInt(v[1], 10);
if (v >= 1 && v < 8) {
features.isOldIOSPhone = true;
}
}
}
// Detect old Android (before KitKat)
// due to bugs related to position:fixed
// http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript
var match = ua.match(/Android\s([0-9\.]*)/);
var androidversion = match ? match[1] : 0;
androidversion = parseFloat(androidversion);
if (androidversion >= 1) {
if (androidversion < 4.4) {
features.isOldAndroid = true; // for fixed position bug & performance
}
features.androidVersion = androidversion; // for touchend bug
}
features.isMobileOpera = /opera mini|opera mobi/i.test(ua);
// p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs.
}
var styleChecks = ['transform', 'perspective', 'animationName'],
vendors = ['', 'webkit', 'Moz', 'ms', 'O'],
styleCheckItem,
styleName;
for (var i = 0; i < 4; i++) {
vendor = vendors[i];
for (var a = 0; a < 3; a++) {
styleCheckItem = styleChecks[a];
// uppercase first letter of property name, if vendor is present
styleName = vendor + (vendor ?
styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) :
styleCheckItem);
if (!features[styleCheckItem] && styleName in helperStyle) {
features[styleCheckItem] = styleName;
}
}
if (vendor && !features.raf) {
vendor = vendor.toLowerCase();
features.raf = window[vendor + 'RequestAnimationFrame'];
if (features.raf) {
features.caf = window[vendor + 'CancelAnimationFrame'] ||
window[vendor + 'CancelRequestAnimationFrame'];
}
}
}
if (!features.raf) {
var lastTime = 0;
features.raf = function (fn) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () { fn(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
features.caf = function (id) { clearTimeout(id); };
}
// Detect SVG support
features.svg = !!document.createElementNS &&
!!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect;
framework.features = features;
return features;
}
};
framework.detectFeatures();
// Override addEventListener for old versions of IE
if (framework.features.oldIE) {
framework.bind = function (target, type, listener, unbind) {
type = type.split(' ');
var methodName = (unbind ? 'detach' : 'attach') + 'Event',
evName,
_handleEv = function () {
listener.handleEvent.call(listener);
};
for (var i = 0; i < type.length; i++) {
evName = type[i];
if (evName) {
if (typeof listener === 'object' && listener.handleEvent) {
if (!unbind) {
listener['oldIE' + evName] = _handleEv;
} else {
if (!listener['oldIE' + evName]) {
return false;
}
}
target[methodName]('on' + evName, listener['oldIE' + evName]);
} else {
target[methodName]('on' + evName, listener);
}
}
}
};
}
/*>>framework-bridge*/
/*>>core*/
//function(template, UiClass, items, options)
var self = this;
/**
* Static vars, don't change unless you know what you're doing.
*/
var DOUBLE_TAP_RADIUS = 25,
NUM_HOLDERS = 3;
/**
* Options
*/
var _options = {
allowPanToNext: true,
spacing: 0.12,
bgOpacity: 1,
mouseUsed: false,
loop: true,
pinchToClose: true,
closeOnScroll: true,
closeOnVerticalDrag: true,
verticalDragRange: 0.75,
hideAnimationDuration: 333,
showAnimationDuration: 333,
showHideOpacity: false,
focus: true,
escKey: true,
arrowKeys: true,
mainScrollEndFriction: 0.35,
panEndFriction: 0.35,
isClickableElement: function (el) {
return el.tagName === 'A';
},
getDoubleTapZoom: function (isMouseClick, item) {
if (isMouseClick) {
return 1;
} else {
return item.initialZoomLevel < 0.7 ? 1 : 1.33;
}
},
maxSpreadZoom: 1.33,
modal: true,
// not fully implemented yet
scaleMode: 'fit' // TODO
};
framework.extend(_options, options);
/**
* Private helper variables & functions
*/
var _getEmptyPoint = function () {
return { x: 0, y: 0 };
};
var _isOpen,
_isDestroying,
_closedByScroll,
_currentItemIndex,
_containerStyle,
_containerShiftIndex,
_currPanDist = _getEmptyPoint(),
_startPanOffset = _getEmptyPoint(),
_panOffset = _getEmptyPoint(),
_upMoveEvents, // drag move, drag end & drag cancel events array
_downEvents, // drag start events array
_globalEventHandlers,
_viewportSize = {},
_currZoomLevel,
_startZoomLevel,
_translatePrefix,
_translateSufix,
_updateSizeInterval,
_itemsNeedUpdate,
_currPositionIndex = 0,
_offset = {},
_slideSize = _getEmptyPoint(), // size of slide area, including spacing
_itemHolders,
_prevItemIndex,
_indexDiff = 0, // difference of indexes since last content update
_dragStartEvent,
_dragMoveEvent,
_dragEndEvent,
_dragCancelEvent,
_transformKey,
_pointerEventEnabled,
_isFixedPosition = true,
_likelyTouchDevice,
_modules = [],
_requestAF,
_cancelAF,
_initalClassName,
_initalWindowScrollY,
_oldIE,
_currentWindowScrollY,
_features,
_windowVisibleSize = {},
_renderMaxResolution = false,
// Registers PhotoSWipe module (History, Controller ...)
_registerModule = function (name, module) {
framework.extend(self, module.publicMethods);
_modules.push(name);
},
_getLoopedId = function (index) {
var numSlides = _getNumItems();
if (index > numSlides - 1) {
return index - numSlides;
} else if (index < 0) {
return numSlides + index;
}
return index;
},
// Micro bind/trigger
_listeners = {},
_listen = function (name, fn) {
if (!_listeners[name]) {
_listeners[name] = [];
}
return _listeners[name].push(fn);
},
_shout = function (name) {
var listeners = _listeners[name];
if (listeners) {
var args = Array.prototype.slice.call(arguments);
args.shift();
for (var i = 0; i < listeners.length; i++) {
listeners[i].apply(self, args);
}
}
},
_getCurrentTime = function () {
return new Date().getTime();
},
_applyBgOpacity = function (opacity) {
_bgOpacity = opacity;
self.bg.style.opacity = opacity * _options.bgOpacity;
},
_applyZoomTransform = function (styleObj, x, y, zoom, item) {
if (!_renderMaxResolution || (item && item !== self.currItem)) {
zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio);
}
styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')';
},
_applyCurrentZoomPan = function (allowRenderResolution) {
if (_currZoomElementStyle) {
if (allowRenderResolution) {
if (_currZoomLevel > self.currItem.fitRatio) {
if (!_renderMaxResolution) {
_setImageSize(self.currItem, false, true);
_renderMaxResolution = true;
}
} else {
if (_renderMaxResolution) {
_setImageSize(self.currItem);
_renderMaxResolution = false;
}
}
}
_applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.y, _currZoomLevel);
}
},
_applyZoomPanToItem = function (item) {
if (item.container) {
_applyZoomTransform(item.container.style,
item.initialPosition.x,
item.initialPosition.y,
item.initialZoomLevel,
item);
}
},
_setTranslateX = function (x, elStyle) {
elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix;
},
_moveMainScroll = function (x, dragging) {
if (!_options.loop && dragging) {
var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x,
delta = Math.round(x - _mainScrollPos.x);
if ((newSlideIndexOffset < 0 && delta > 0) ||
(newSlideIndexOffset >= _getNumItems() - 1 && delta < 0)) {
x = _mainScrollPos.x + delta * _options.mainScrollEndFriction;
}
}
_mainScrollPos.x = x;
_setTranslateX(x, _containerStyle);
},
_calculatePanOffset = function (axis, zoomLevel) {
var m = _midZoomPoint[axis] - _offset[axis];
return _startPanOffset[axis] + _currPanDist[axis] + m - m * (zoomLevel / _startZoomLevel);
},
_equalizePoints = function (p1, p2) {
p1.x = p2.x;
p1.y = p2.y;
if (p2.id) {
p1.id = p2.id;
}
},
_roundPoint = function (p) {
p.x = Math.round(p.x);
p.y = Math.round(p.y);
},
_mouseMoveTimeout = null,
_onFirstMouseMove = function () {
// Wait until mouse move event is fired at least twice during 100ms
// We do this, because some mobile browsers trigger it on touchstart
if (_mouseMoveTimeout) {
framework.unbind(document, 'mousemove', _onFirstMouseMove);
framework.addClass(template, 'pswp--has_mouse');
_options.mouseUsed = true;
_shout('mouseUsed');
}
_mouseMoveTimeout = setTimeout(function () {
_mouseMoveTimeout = null;
}, 100);
},
_bindEvents = function () {
framework.bind(document, 'keydown', self);
if (_features.transform) {
// don't bind click event in browsers that don't support transform (mostly IE8)
framework.bind(self.scrollWrap, 'click', self);
}
if (!_options.mouseUsed) {
framework.bind(document, 'mousemove', _onFirstMouseMove);
}
framework.bind(window, 'resize scroll', self);
_shout('bindEvents');
},
_unbindEvents = function () {
framework.unbind(window, 'resize', self);
framework.unbind(window, 'scroll', _globalEventHandlers.scroll);
framework.unbind(document, 'keydown', self);
framework.unbind(document, 'mousemove', _onFirstMouseMove);
if (_features.transform) {
framework.unbind(self.scrollWrap, 'click', self);
}
if (_isDragging) {
framework.unbind(window, _upMoveEvents, self);
}
_shout('unbindEvents');
},
_calculatePanBounds = function (zoomLevel, update) {
var bounds = _calculateItemSize(self.currItem, _viewportSize, zoomLevel);
if (update) {
_currPanBounds = bounds;
}
return bounds;
},
_getMinZoomLevel = function (item) {
if (!item) {
item = self.currItem;
}
return item.initialZoomLevel;
},
_getMaxZoomLevel = function (item) {
if (!item) {
item = self.currItem;
}
return item.w > 0 ? _options.maxSpreadZoom : 1;
},
// Return true if offset is out of the bounds
_modifyDestPanOffset = function (axis, destPanBounds, destPanOffset, destZoomLevel) {
if (destZoomLevel === self.currItem.initialZoomLevel) {
destPanOffset[axis] = self.currItem.initialPosition[axis];
return true;
} else {
destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel);
if (destPanOffset[axis] > destPanBounds.min[axis]) {
destPanOffset[axis] = destPanBounds.min[axis];
return true;
} else if (destPanOffset[axis] < destPanBounds.max[axis]) {
destPanOffset[axis] = destPanBounds.max[axis];
return true;
}
}
return false;
},
_setupTransforms = function () {
if (_transformKey) {
// setup 3d transforms
var allow3dTransform = _features.perspective && !_likelyTouchDevice;
_translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '(');
_translateSufix = _features.perspective ? ', 0px)' : ')';
return;
}
// Override zoom/pan/move functions in case old browser is used (most likely IE)
// (so they use left/top/width/height, instead of CSS transform)
_transformKey = 'left';
framework.addClass(template, 'pswp--ie');
_setTranslateX = function (x, elStyle) {
elStyle.left = x + 'px';
};
_applyZoomPanToItem = function (item) {
var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
s = item.container.style,
w = zoomRatio * item.w,
h = zoomRatio * item.h;
s.width = w + 'px';
s.height = h + 'px';
s.left = item.initialPosition.x + 'px';
s.top = item.initialPosition.y + 'px';
};
_applyCurrentZoomPan = function () {
if (_currZoomElementStyle) {
var s = _currZoomElementStyle,
item = self.currItem,
zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
w = zoomRatio * item.w,
h = zoomRatio * item.h;
s.width = w + 'px';
s.height = h + 'px';
s.left = _panOffset.x + 'px';
s.top = _panOffset.y + 'px';
}
};
},
_onKeyDown = function (e) {
var keydownAction = '';
if (_options.escKey && e.keyCode === 27) {
keydownAction = 'close';
} else if (_options.arrowKeys) {
if (e.keyCode === 37) {
keydownAction = 'prev';
} else if (e.keyCode === 39) {
keydownAction = 'next';
}
}
if (keydownAction) {
// don't do anything if special key pressed to prevent from overriding default browser actions
// e.g. in Chrome on Mac cmd+arrow-left returns to previous page
if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
self[keydownAction]();
}
}
},
_onGlobalClick = function (e) {
if (!e) {
return;
}
// don't allow click event to pass through when triggering after drag or some other gesture
if (_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) {
e.preventDefault();
e.stopPropagation();
}
},
_updatePageScrollOffset = function () {
self.setScrollOffset(0, framework.getScrollY());
};
// Micro animation engine
var _animations = {},
_numAnimations = 0,
_stopAnimation = function (name) {
if (_animations[name]) {
if (_animations[name].raf) {
_cancelAF(_animations[name].raf);
}
_numAnimations--;
delete _animations[name];
}
},
_registerStartAnimation = function (name) {
if (_animations[name]) {
_stopAnimation(name);
}
if (!_animations[name]) {
_numAnimations++;
_animations[name] = {};
}
},
_stopAllAnimations = function () {
for (var prop in _animations) {
if (_animations.hasOwnProperty(prop)) {
_stopAnimation(prop);
}
}
},
_animateProp = function (name, b, endProp, d, easingFn, onUpdate, onComplete) {
var startAnimTime = _getCurrentTime(), t;
_registerStartAnimation(name);
var animloop = function () {
if (_animations[name]) {
t = _getCurrentTime() - startAnimTime; // time diff
//b - beginning (start prop)
//d - anim duration
if (t >= d) {
_stopAnimation(name);
onUpdate(endProp);
if (onComplete) {
onComplete();
}
return;
}
onUpdate((endProp - b) * easingFn(t / d) + b);
_animations[name].raf = _requestAF(animloop);
}
};
animloop();
};
var publicMethods = {
// make a few local variables and functions public
shout: _shout,
listen: _listen,
viewportSize: _viewportSize,
options: _options,
isMainScrollAnimating: function () {
return _mainScrollAnimating;
},
getZoomLevel: function () {
return _currZoomLevel;
},
getCurrentIndex: function () {
return _currentItemIndex;
},
isDragging: function () {
return _isDragging;
},
isZooming: function () {
return _isZooming;
},
setScrollOffset: function (x, y) {
_offset.x = x;
_currentWindowScrollY = _offset.y = y;
_shout('updateScrollOffset', _offset);
},
applyZoomPan: function (zoomLevel, panX, panY, allowRenderResolution) {
_panOffset.x = panX;
_panOffset.y = panY;
_currZoomLevel = zoomLevel;
_applyCurrentZoomPan(allowRenderResolution);
},
init: function () {
if (_isOpen || _isDestroying) {
return;
}
var i;
self.framework = framework; // basic functionality
self.template = template; // root DOM element of PhotoSwipe
self.bg = framework.getChildByClass(template, 'pswp__bg');
_initalClassName = template.className;
_isOpen = true;
_features = framework.detectFeatures();
_requestAF = _features.raf;
_cancelAF = _features.caf;
_transformKey = _features.transform;
_oldIE = _features.oldIE;
self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap');
self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container');
_containerStyle = self.container.style; // for fast access
// Objects that hold slides (there are only 3 in DOM)
self.itemHolders = _itemHolders = [
{ el: self.container.children[0], wrap: 0, index: -1 },
{ el: self.container.children[1], wrap: 0, index: -1 },
{ el: self.container.children[2], wrap: 0, index: -1 }
];
// hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
_itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none';
_setupTransforms();
// Setup global events
_globalEventHandlers = {
resize: self.updateSize,
scroll: _updatePageScrollOffset,
keydown: _onKeyDown,
click: _onGlobalClick
};
// disable show/hide effects on old browsers that don't support CSS animations or transforms,
// old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models.
var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera;
if (!_features.animationName || !_features.transform || oldPhone) {
_options.showAnimationDuration = _options.hideAnimationDuration = 0;
}
// init modules
for (i = 0; i < _modules.length; i++) {
self['init' + _modules[i]]();
}
// init
if (UiClass) {
var ui = self.ui = new UiClass(self, framework);
ui.init();
}
_shout('firstUpdate');
_currentItemIndex = _currentItemIndex || _options.index || 0;
// validate index
if (isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems()) {
_currentItemIndex = 0;
}
self.currItem = _getItemAt(_currentItemIndex);
if (_features.isOldIOSPhone || _features.isOldAndroid) {
_isFixedPosition = false;
}
template.setAttribute('aria-hidden', 'false');
if (_options.modal) {
if (!_isFixedPosition) {
template.style.position = 'absolute';
template.style.top = framework.getScrollY() + 'px';
} else {
template.style.position = 'fixed';
}
}
if (_currentWindowScrollY === undefined) {
_shout('initialLayout');
_currentWindowScrollY = _initalWindowScrollY = framework.getScrollY();
}
// add classes to root element of PhotoSwipe
var rootClasses = 'pswp--open ';
if (_options.mainClass) {
rootClasses += _options.mainClass + ' ';
}
if (_options.showHideOpacity) {
rootClasses += 'pswp--animate_opacity ';
}
rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch';
rootClasses += _features.animationName ? ' pswp--css_animation' : '';
rootClasses += _features.svg ? ' pswp--svg' : '';
framework.addClass(template, rootClasses);
self.updateSize();
// initial update
_containerShiftIndex = -1;
_indexDiff = null;
for (i = 0; i < NUM_HOLDERS; i++) {
_setTranslateX((i + _containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style);
}
if (!_oldIE) {
framework.bind(self.scrollWrap, _downEvents, self); // no dragging for old IE
}
_listen('initialZoomInEnd', function () {
self.setContent(_itemHolders[0], _currentItemIndex - 1);
self.setContent(_itemHolders[2], _currentItemIndex + 1);
_itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block';
if (_options.focus) {
// focus causes layout,
// which causes lag during the animation,
// that's why we delay it untill the initial zoom transition ends
template.focus();
}
_bindEvents();
});
// set content for center slide (first time)
self.setContent(_itemHolders[1], _currentItemIndex);
self.updateCurrItem();
_shout('afterInit');
if (!_isFixedPosition) {
// On all versions of iOS lower than 8.0, we check size of viewport every second.
//
// This is done to detect when Safari top & bottom bars appear,
// as this action doesn't trigger any events (like resize).
//
// On iOS8 they fixed this.
//
// 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%.
_updateSizeInterval = setInterval(function () {
if (!_numAnimations && !_isDragging && !_isZooming && (_currZoomLevel === self.currItem.initialZoomLevel)) {
self.updateSize();
}
}, 1000);
}
framework.addClass(template, 'pswp--visible');
},
// Close the gallery, then destroy it
close: function () {
if (!_isOpen) {
return;
}
_isOpen = false;
_isDestroying = true;
_shout('close');
_unbindEvents();
_showOrHide(self.currItem, null, true, self.destroy);
},
// destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks)
destroy: function () {
_shout('destroy');
if (_showOrHideTimeout) {
clearTimeout(_showOrHideTimeout);
}
template.setAttribute('aria-hidden', 'true');
template.className = _initalClassName;
if (_updateSizeInterval) {
clearInterval(_updateSizeInterval);
}
framework.unbind(self.scrollWrap, _downEvents, self);
// we unbind scroll event at the end, as closing animation may depend on it
framework.unbind(window, 'scroll', self);
_stopDragUpdateLoop();
_stopAllAnimations();
_listeners = null;
},
/**
* Pan image to position
* @param {Number} x
* @param {Number} y
* @param {Boolean} force Will ignore bounds if set to true.
*/
panTo: function (x, y, force) {
if (!force) {
if (x > _currPanBounds.min.x) {
x = _currPanBounds.min.x;
} else if (x < _currPanBounds.max.x) {
x = _currPanBounds.max.x;
}
if (y > _currPanBounds.min.y) {
y = _currPanBounds.min.y;
} else if (y < _currPanBounds.max.y) {
y = _currPanBounds.max.y;
}
}
_panOffset.x = x;
_panOffset.y = y;
_applyCurrentZoomPan();
},
handleEvent: function (e) {
e = e || window.event;
if (_globalEventHandlers[e.type]) {
_globalEventHandlers[e.type](e);
}
},
goTo: function (index) {
index = _getLoopedId(index);
var diff = index - _currentItemIndex;
_indexDiff = diff;
_currentItemIndex = index;
self.currItem = _getItemAt(_currentItemIndex);
_currPositionIndex -= diff;
_moveMainScroll(_slideSize.x * _currPositionIndex);
_stopAllAnimations();
_mainScrollAnimating = false;
self.updateCurrItem();
},
next: function () {
self.goTo(_currentItemIndex + 1);
},
prev: function () {
self.goTo(_currentItemIndex - 1);
},
// update current zoom/pan objects
updateCurrZoomItem: function (emulateSetContent) {
if (emulateSetContent) {
_shout('beforeChange', 0);
}
// itemHolder[1] is middle (current) item
if (_itemHolders[1].el.children.length) {
var zoomElement = _itemHolders[1].el.children[0];
if (framework.hasClass(zoomElement, 'pswp__zoom-wrap')) {
_currZoomElementStyle = zoomElement.style;
} else {
_currZoomElementStyle = null;
}
} else {
_currZoomElementStyle = null;
}
_currPanBounds = self.currItem.bounds;
_startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
_panOffset.x = _currPanBounds.center.x;
_panOffset.y = _currPanBounds.center.y;
if (emulateSetContent) {
_shout('afterChange');
}
},
invalidateCurrItems: function () {
_itemsNeedUpdate = true;
for (var i = 0; i < NUM_HOLDERS; i++) {
if (_itemHolders[i].item) {
_itemHolders[i].item.needsUpdate = true;
}
}
},
updateCurrItem: function (beforeAnimation) {
if (_indexDiff === 0) {
return;
}
var diffAbs = Math.abs(_indexDiff),
tempHolder;
if (beforeAnimation && diffAbs < 2) {
return;
}
self.currItem = _getItemAt(_currentItemIndex);
_renderMaxResolution = false;
_shout('beforeChange', _indexDiff);
if (diffAbs >= NUM_HOLDERS) {
_containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS);
diffAbs = NUM_HOLDERS;
}
for (var i = 0; i < diffAbs; i++) {
if (_indexDiff > 0) {
tempHolder = _itemHolders.shift();
_itemHolders[NUM_HOLDERS - 1] = tempHolder; // move first to last
_containerShiftIndex++;
_setTranslateX((_containerShiftIndex + 2) * _slideSize.x, tempHolder.el.style);
self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1);
} else {
tempHolder = _itemHolders.pop();
_itemHolders.unshift(tempHolder); // move last to first
_containerShiftIndex--;
_setTranslateX(_containerShiftIndex * _slideSize.x, tempHolder.el.style);
self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1);
}
}
// reset zoom/pan on previous item
if (_currZoomElementStyle && Math.abs(_indexDiff) === 1) {
var prevItem = _getItemAt(_prevItemIndex);
if (prevItem.initialZoomLevel !== _currZoomLevel) {
_calculateItemSize(prevItem, _viewportSize);
_setImageSize(prevItem);
_applyZoomPanToItem(prevItem);
}
}
// reset diff after update
_indexDiff = 0;
self.updateCurrZoomItem();
_prevItemIndex = _currentItemIndex;
_shout('afterChange');
},
updateSize: function (force) {
if (!_isFixedPosition && _options.modal) {
var windowScrollY = framework.getScrollY();
if (_currentWindowScrollY !== windowScrollY) {
template.style.top = windowScrollY + 'px';
_currentWindowScrollY = windowScrollY;
}
if (!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) {
return;
}
_windowVisibleSize.x = window.innerWidth;
_windowVisibleSize.y = window.innerHeight;
//template.style.width = _windowVisibleSize.x + 'px';
template.style.height = _windowVisibleSize.y + 'px';
}
_viewportSize.x = self.scrollWrap.clientWidth;
_viewportSize.y = self.scrollWrap.clientHeight;
_updatePageScrollOffset();
_slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing);
_slideSize.y = _viewportSize.y;
_moveMainScroll(_slideSize.x * _currPositionIndex);
_shout('beforeResize'); // even may be used for example to switch image sources
// don't re-calculate size on inital size update
if (_containerShiftIndex !== undefined) {
var holder,
item,
hIndex;
for (var i = 0; i < NUM_HOLDERS; i++) {
holder = _itemHolders[i];
_setTranslateX((i + _containerShiftIndex) * _slideSize.x, holder.el.style);
hIndex = _currentItemIndex + i - 1;
if (_options.loop && _getNumItems() > 2) {
hIndex = _getLoopedId(hIndex);
}
// update zoom level on items and refresh source (if needsUpdate)
item = _getItemAt(hIndex);
// re-render gallery item if `needsUpdate`,
// or doesn't have `bounds` (entirely new slide object)
if (item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds)) {
self.cleanSlide(item);
self.setContent(holder, hIndex);
// if "center" slide
if (i === 1) {
self.currItem = item;
self.updateCurrZoomItem(true);
}
item.needsUpdate = false;
} else if (holder.index === -1 && hIndex >= 0) {
// add content first time
self.setContent(holder, hIndex);
}
if (item && item.container) {
_calculateItemSize(item, _viewportSize);
_setImageSize(item);
_applyZoomPanToItem(item);
}
}
_itemsNeedUpdate = false;
}
_startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
_currPanBounds = self.currItem.bounds;
if (_currPanBounds) {
_panOffset.x = _currPanBounds.center.x;
_panOffset.y = _currPanBounds.center.y;
_applyCurrentZoomPan(true);
}
_shout('resize');
},
// Zoom current item to
zoomTo: function (destZoomLevel, centerPoint, speed, easingFn, updateFn) {
/*
if(destZoomLevel === 'fit') {
destZoomLevel = self.currItem.fitRatio;
} else if(destZoomLevel === 'fill') {
destZoomLevel = self.currItem.fillRatio;
}
*/
if (centerPoint) {
_startZoomLevel = _currZoomLevel;
_midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x;
_midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y;
_equalizePoints(_startPanOffset, _panOffset);
}
var destPanBounds = _calculatePanBounds(destZoomLevel, false),
destPanOffset = {};
_modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel);
_modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel);
var initialZoomLevel = _currZoomLevel;
var initialPanOffset = {
x: _panOffset.x,
y: _panOffset.y
};
_roundPoint(destPanOffset);
var onUpdate = function (now) {
if (now === 1) {
_currZoomLevel = destZoomLevel;
_panOffset.x = destPanOffset.x;
_panOffset.y = destPanOffset.y;
} else {
_currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
_panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x;
_panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y;
}
if (updateFn) {
updateFn(now);
}
_applyCurrentZoomPan(now === 1);
};
if (speed) {
_animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate);
} else {
onUpdate(1);
}
}
};
/*>>core*/
/*>>gestures*/
/**
* Mouse/touch/pointer event handlers.
*
* separated from @core.js for readability
*/
var MIN_SWIPE_DISTANCE = 30,
DIRECTION_CHECK_OFFSET = 10; // amount of pixels to drag to determine direction of swipe
var _gestureStartTime,
_gestureCheckSpeedTime,
// pool of objects that are used during dragging of zooming
p = {}, // first point
p2 = {}, // second point (for zoom gesture)
delta = {},
_currPoint = {},
_startPoint = {},
_currPointers = [],
_startMainScrollPos = {},
_releaseAnimData,
_posPoints = [], // array of points during dragging, used to determine type of gesture
_tempPoint = {},
_isZoomingIn,
_verticalDragInitiated,
_oldAndroidTouchEndTimeout,
_currZoomedItemIndex = 0,
_centerPoint = _getEmptyPoint(),
_lastReleaseTime = 0,
_isDragging, // at least one pointer is down
_isMultitouch, // at least two _pointers are down
_zoomStarted, // zoom level changed during zoom gesture
_moved,
_dragAnimFrame,
_mainScrollShifted,
_currentPoints, // array of current touch points
_isZooming,
_currPointsDistance,
_startPointsDistance,
_currPanBounds,
_mainScrollPos = _getEmptyPoint(),
_currZoomElementStyle,
_mainScrollAnimating, // true, if animation after swipe gesture is running
_midZoomPoint = _getEmptyPoint(),
_currCenterPoint = _getEmptyPoint(),
_direction,
_isFirstMove,
_opacityChanged,
_bgOpacity,
_wasOverInitialZoom,
_isEqualPoints = function (p1, p2) {
return p1.x === p2.x && p1.y === p2.y;
},
_isNearbyPoints = function (touch0, touch1) {
return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS;
},
_calculatePointsDistance = function (p1, p2) {
_tempPoint.x = Math.abs(p1.x - p2.x);
_tempPoint.y = Math.abs(p1.y - p2.y);
return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y);
},
_stopDragUpdateLoop = function () {
if (_dragAnimFrame) {
_cancelAF(_dragAnimFrame);
_dragAnimFrame = null;
}
},
_dragUpdateLoop = function () {
if (_isDragging) {
_dragAnimFrame = _requestAF(_dragUpdateLoop);
_renderMovement();
}
},
_canPan = function () {
return !(_options.scaleMode === 'fit' && _currZoomLevel === self.currItem.initialZoomLevel);
},
// find the closest parent DOM element
_closestElement = function (el, fn) {
if (!el) {
return false;
}
// don't search elements above pswp__scroll-wrap
if (el.className && el.className.indexOf('pswp__scroll-wrap') > -1) {
return false;
}
if (fn(el)) {
return el;
}
return _closestElement(el.parentNode, fn);
},
_preventObj = {},
_preventDefaultEventBehaviour = function (e, isDown) {
_preventObj.prevent = !_closestElement(e.target, _options.isClickableElement);
_shout('preventDragEvent', e, isDown, _preventObj);
return _preventObj.prevent;
},
_convertTouchToPoint = function (touch, p) {
p.x = touch.pageX;
p.y = touch.pageY;
p.id = touch.identifier;
return p;
},
_findCenterOfPoints = function (p1, p2, pCenter) {
pCenter.x = (p1.x + p2.x) * 0.5;
pCenter.y = (p1.y + p2.y) * 0.5;
},
_pushPosPoint = function (time, x, y) {
if (time - _gestureCheckSpeedTime > 50) {
var o = _posPoints.length > 2 ? _posPoints.shift() : {};
o.x = x;
o.y = y;
_posPoints.push(o);
_gestureCheckSpeedTime = time;
}
},
_calculateVerticalDragOpacityRatio = function () {
var yOffset = _panOffset.y - self.currItem.initialPosition.y; // difference between initial and current position
return 1 - Math.abs(yOffset / (_viewportSize.y / 2));
},
// points pool, reused during touch events
_ePoint1 = {},
_ePoint2 = {},
_tempPointsArr = [],
_tempCounter,
_getTouchPoints = function (e) {
// clean up previous points, without recreating array
while (_tempPointsArr.length > 0) {
_tempPointsArr.pop();
}
if (!_pointerEventEnabled) {
if (e.type.indexOf('touch') > -1) {
if (e.touches && e.touches.length > 0) {
_tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1);
if (e.touches.length > 1) {
_tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2);
}
}
} else {
_ePoint1.x = e.pageX;
_ePoint1.y = e.pageY;
_ePoint1.id = '';
_tempPointsArr[0] = _ePoint1;//_ePoint1;
}
} else {
_tempCounter = 0;
// we can use forEach, as pointer events are supported only in modern browsers
_currPointers.forEach(function (p) {
if (_tempCounter === 0) {
_tempPointsArr[0] = p;
} else if (_tempCounter === 1) {
_tempPointsArr[1] = p;
}
_tempCounter++;
});
}
return _tempPointsArr;
},
_panOrMoveMainScroll = function (axis, delta) {
var panFriction,
overDiff = 0,
newOffset = _panOffset[axis] + delta[axis],
startOverDiff,
dir = delta[axis] > 0,
newMainScrollPosition = _mainScrollPos.x + delta.x,
mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x,
newPanPos,
newMainScrollPos;
// calculate fdistance over the bounds and friction
if (newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) {
panFriction = _options.panEndFriction;
// Linear increasing of friction, so at 1/4 of viewport it's at max value.
// Looks not as nice as was expected. Left for history.
// panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) );
} else {
panFriction = 1;
}
newOffset = _panOffset[axis] + delta[axis] * panFriction;
// move main scroll or start panning
if (_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) {
if (!_currZoomElementStyle) {
newMainScrollPos = newMainScrollPosition;
} else if (_direction === 'h' && axis === 'x' && !_zoomStarted) {
if (dir) {
if (newOffset > _currPanBounds.min[axis]) {
panFriction = _options.panEndFriction;
overDiff = _currPanBounds.min[axis] - newOffset;
startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis];
}
// drag right
if ((startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1) {
newMainScrollPos = newMainScrollPosition;
if (mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) {
newMainScrollPos = _startMainScrollPos.x;
}
} else {
if (_currPanBounds.min.x !== _currPanBounds.max.x) {
newPanPos = newOffset;
}
}
} else {
if (newOffset < _currPanBounds.max[axis]) {
panFriction = _options.panEndFriction;
overDiff = newOffset - _currPanBounds.max[axis];
startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis];
}
if ((startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1) {
newMainScrollPos = newMainScrollPosition;
if (mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) {
newMainScrollPos = _startMainScrollPos.x;
}
} else {
if (_currPanBounds.min.x !== _currPanBounds.max.x) {
newPanPos = newOffset;
}
}
}
//
}
if (axis === 'x') {
if (newMainScrollPos !== undefined) {
_moveMainScroll(newMainScrollPos, true);
if (newMainScrollPos === _startMainScrollPos.x) {
_mainScrollShifted = false;
} else {
_mainScrollShifted = true;
}
}
if (_currPanBounds.min.x !== _currPanBounds.max.x) {
if (newPanPos !== undefined) {
_panOffset.x = newPanPos;
} else if (!_mainScrollShifted) {
_panOffset.x += delta.x * panFriction;
}
}
return newMainScrollPos !== undefined;
}
}
if (!_mainScrollAnimating) {
if (!_mainScrollShifted) {
if (_currZoomLevel > self.currItem.fitRatio) {
_panOffset[axis] += delta[axis] * panFriction;
}
}
}
},
// Pointerdown/touchstart/mousedown handler
_onDragStart = function (e) {
// Allow dragging only via left mouse button.
// As this handler is not added in IE8 - we ignore e.which
//
// http://www.quirksmode.org/js/events_properties.html
// https://developer.mozilla.org/en-US/docs/Web/API/event.button
if (e.type === 'mousedown' && e.button > 0) {
return;
}
if (_initialZoomRunning) {
e.preventDefault();
return;
}
if (_oldAndroidTouchEndTimeout && e.type === 'mousedown') {
return;
}
if (_preventDefaultEventBehaviour(e, true)) {
e.preventDefault();
}
_shout('pointerDown');
if (_pointerEventEnabled) {
var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
if (pointerIndex < 0) {
pointerIndex = _currPointers.length;
}
_currPointers[pointerIndex] = { x: e.pageX, y: e.pageY, id: e.pointerId };
}
var startPointsList = _getTouchPoints(e),
numPoints = startPointsList.length;
_currentPoints = null;
_stopAllAnimations();
// init drag
if (!_isDragging || numPoints === 1) {
_isDragging = _isFirstMove = true;
framework.bind(window, _upMoveEvents, self);
_isZoomingIn =
_wasOverInitialZoom =
_opacityChanged =
_verticalDragInitiated =
_mainScrollShifted =
_moved =
_isMultitouch =
_zoomStarted = false;
_direction = null;
_shout('firstTouchStart', startPointsList);
_equalizePoints(_startPanOffset, _panOffset);
_currPanDist.x = _currPanDist.y = 0;
_equalizePoints(_currPoint, startPointsList[0]);
_equalizePoints(_startPoint, _currPoint);
//_equalizePoints(_startMainScrollPos, _mainScrollPos);
_startMainScrollPos.x = _slideSize.x * _currPositionIndex;
_posPoints = [{
x: _currPoint.x,
y: _currPoint.y
}];
_gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime();
//_mainScrollAnimationEnd(true);
_calculatePanBounds(_currZoomLevel, true);
// Start rendering
_stopDragUpdateLoop();
_dragUpdateLoop();
}
// init zoom
if (!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) {
_startZoomLevel = _currZoomLevel;
_zoomStarted = false; // true if zoom changed at least once
_isZooming = _isMultitouch = true;
_currPanDist.y = _currPanDist.x = 0;
_equalizePoints(_startPanOffset, _panOffset);
_equalizePoints(p, startPointsList[0]);
_equalizePoints(p2, startPointsList[1]);
_findCenterOfPoints(p, p2, _currCenterPoint);
_midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x;
_midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y;
_currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2);
}
},
// Pointermove/touchmove/mousemove handler
_onDragMove = function (e) {
e.preventDefault();
if (_pointerEventEnabled) {
var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
if (pointerIndex > -1) {
var p = _currPointers[pointerIndex];
p.x = e.pageX;
p.y = e.pageY;
}
}
if (_isDragging) {
var touchesList = _getTouchPoints(e);
if (!_direction && !_moved && !_isZooming) {
if (_mainScrollPos.x !== _slideSize.x * _currPositionIndex) {
// if main scroll position is shifted – direction is always horizontal
_direction = 'h';
} else {
var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y);
// check the direction of movement
if (Math.abs(diff) >= DIRECTION_CHECK_OFFSET) {
_direction = diff > 0 ? 'h' : 'v';
_currentPoints = touchesList;
}
}
} else {
_currentPoints = touchesList;
}
}
},
//
_renderMovement = function () {
if (!_currentPoints) {
return;
}
var numPoints = _currentPoints.length;
if (numPoints === 0) {
return;
}
_equalizePoints(p, _currentPoints[0]);
delta.x = p.x - _currPoint.x;
delta.y = p.y - _currPoint.y;
if (_isZooming && numPoints > 1) {
// Handle behaviour for more than 1 point
_currPoint.x = p.x;
_currPoint.y = p.y;
// check if one of two points changed
if (!delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2)) {
return;
}
_equalizePoints(p2, _currentPoints[1]);
if (!_zoomStarted) {
_zoomStarted = true;
_shout('zoomGestureStarted');
}
// Distance between two points
var pointsDistance = _calculatePointsDistance(p, p2);
var zoomLevel = _calculateZoomLevel(pointsDistance);
// slightly over the of initial zoom level
if (zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) {
_wasOverInitialZoom = true;
}
// Apply the friction if zoom level is out of the bounds
var zoomFriction = 1,
minZoomLevel = _getMinZoomLevel(),
maxZoomLevel = _getMaxZoomLevel();
if (zoomLevel < minZoomLevel) {
if (_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) {
// fade out background if zooming out
var minusDiff = minZoomLevel - zoomLevel;
var percent = 1 - minusDiff / (minZoomLevel / 1.2);
_applyBgOpacity(percent);
_shout('onPinchClose', percent);
_opacityChanged = true;
} else {
zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel;
if (zoomFriction > 1) {
zoomFriction = 1;
}
zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3);
}
} else if (zoomLevel > maxZoomLevel) {
// 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5
zoomFriction = (zoomLevel - maxZoomLevel) / (minZoomLevel * 6);
if (zoomFriction > 1) {
zoomFriction = 1;
}
zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel;
}
if (zoomFriction < 0) {
zoomFriction = 0;
}
// distance between touch points after friction is applied
_currPointsDistance = pointsDistance;
// _centerPoint - The point in the middle of two pointers
_findCenterOfPoints(p, p2, _centerPoint);
// paning with two pointers pressed
_currPanDist.x += _centerPoint.x - _currCenterPoint.x;
_currPanDist.y += _centerPoint.y - _currCenterPoint.y;
_equalizePoints(_currCenterPoint, _centerPoint);
_panOffset.x = _calculatePanOffset('x', zoomLevel);
_panOffset.y = _calculatePanOffset('y', zoomLevel);
_isZoomingIn = zoomLevel > _currZoomLevel;
_currZoomLevel = zoomLevel;
_applyCurrentZoomPan();
} else {
// handle behaviour for one point (dragging or panning)
if (!_direction) {
return;
}
if (_isFirstMove) {
_isFirstMove = false;
// subtract drag distance that was used during the detection direction
if (Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) {
delta.x -= _currentPoints[0].x - _startPoint.x;
}
if (Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) {
delta.y -= _currentPoints[0].y - _startPoint.y;
}
}
_currPoint.x = p.x;
_currPoint.y = p.y;
// do nothing if pointers position hasn't changed
if (delta.x === 0 && delta.y === 0) {
return;
}
if (_direction === 'v' && _options.closeOnVerticalDrag) {
if (!_canPan()) {
_currPanDist.y += delta.y;
_panOffset.y += delta.y;
var opacityRatio = _calculateVerticalDragOpacityRatio();
_verticalDragInitiated = true;
_shout('onVerticalDrag', opacityRatio);
_applyBgOpacity(opacityRatio);
_applyCurrentZoomPan();
return;
}
}
_pushPosPoint(_getCurrentTime(), p.x, p.y);
_moved = true;
_currPanBounds = self.currItem.bounds;
var mainScrollChanged = _panOrMoveMainScroll('x', delta);
if (!mainScrollChanged) {
_panOrMoveMainScroll('y', delta);
_roundPoint(_panOffset);
_applyCurrentZoomPan();
}
}
},
// Pointerup/pointercancel/touchend/touchcancel/mouseup event handler
_onDragRelease = function (e) {
if (_features.isOldAndroid) {
if (_oldAndroidTouchEndTimeout && e.type === 'mouseup') {
return;
}
// on Android (v4.1, 4.2, 4.3 & possibly older)
// ghost mousedown/up event isn't preventable via e.preventDefault,
// which causes fake mousedown event
// so we block mousedown/up for 600ms
if (e.type.indexOf('touch') > -1) {
clearTimeout(_oldAndroidTouchEndTimeout);
_oldAndroidTouchEndTimeout = setTimeout(function () {
_oldAndroidTouchEndTimeout = 0;
}, 600);
}
}
_shout('pointerUp');
if (_preventDefaultEventBehaviour(e, false)) {
e.preventDefault();
}
var releasePoint;
if (_pointerEventEnabled) {
var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
if (pointerIndex > -1) {
releasePoint = _currPointers.splice(pointerIndex, 1)[0];
if (navigator.pointerEnabled) {
releasePoint.type = e.pointerType || 'mouse';
} else {
var MSPOINTER_TYPES = {
4: 'mouse', // event.MSPOINTER_TYPE_MOUSE
2: 'touch', // event.MSPOINTER_TYPE_TOUCH
3: 'pen' // event.MSPOINTER_TYPE_PEN
};
releasePoint.type = MSPOINTER_TYPES[e.pointerType];
if (!releasePoint.type) {
releasePoint.type = e.pointerType || 'mouse';
}
}
}
}
var touchList = _getTouchPoints(e),
gestureType,
numPoints = touchList.length;
if (e.type === 'mouseup') {
numPoints = 0;
}
// Do nothing if there were 3 touch points or more
if (numPoints === 2) {
_currentPoints = null;
return true;
}
// if second pointer released
if (numPoints === 1) {
_equalizePoints(_startPoint, touchList[0]);
}
// pointer hasn't moved, send "tap release" point
if (numPoints === 0 && !_direction && !_mainScrollAnimating) {
if (!releasePoint) {
if (e.type === 'mouseup') {
releasePoint = { x: e.pageX, y: e.pageY, type: 'mouse' };
} else if (e.changedTouches && e.changedTouches[0]) {
releasePoint = { x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type: 'touch' };
}
}
_shout('touchRelease', e, releasePoint);
}
// Difference in time between releasing of two last touch points (zoom gesture)
var releaseTimeDiff = -1;
// Gesture completed, no pointers left
if (numPoints === 0) {
_isDragging = false;
framework.unbind(window, _upMoveEvents, self);
_stopDragUpdateLoop();
if (_isZooming) {
// Two points released at the same time
releaseTimeDiff = 0;
} else if (_lastReleaseTime !== -1) {
releaseTimeDiff = _getCurrentTime() - _lastReleaseTime;
}
}
_lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1;
if (releaseTimeDiff !== -1 && releaseTimeDiff < 150) {
gestureType = 'zoom';
} else {
gestureType = 'swipe';
}
if (_isZooming && numPoints < 2) {
_isZooming = false;
// Only second point released
if (numPoints === 1) {
gestureType = 'zoomPointerUp';
}
_shout('zoomGestureEnded');
}
_currentPoints = null;
if (!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) {
// nothing to animate
return;
}
_stopAllAnimations();
if (!_releaseAnimData) {
_releaseAnimData = _initDragReleaseAnimationData();
}
_releaseAnimData.calculateSwipeSpeed('x');
if (_verticalDragInitiated) {
var opacityRatio = _calculateVerticalDragOpacityRatio();
if (opacityRatio < _options.verticalDragRange) {
self.close();
} else {
var initalPanY = _panOffset.y,
initialBgOpacity = _bgOpacity;
_animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function (now) {
_panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY;
_applyBgOpacity((1 - initialBgOpacity) * now + initialBgOpacity);
_applyCurrentZoomPan();
});
_shout('onVerticalDrag', 1);
}
return;
}
// main scroll
if ((_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) {
var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData);
if (itemChanged) {
return;
}
gestureType = 'zoomPointerUp';
}
// prevent zoom/pan animation when main scroll animation runs
if (_mainScrollAnimating) {
return;
}
// Complete simple zoom gesture (reset zoom level if it's out of the bounds)
if (gestureType !== 'swipe') {
_completeZoomGesture();
return;
}
// Complete pan gesture if main scroll is not shifted, and it's possible to pan current image
if (!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) {
_completePanGesture(_releaseAnimData);
}
},
// Returns object with data about gesture
// It's created only once and then reused
_initDragReleaseAnimationData = function () {
// temp local vars
var lastFlickDuration,
tempReleasePos;
// s = this
var s = {
lastFlickOffset: {},
lastFlickDist: {},
lastFlickSpeed: {},
slowDownRatio: {},
slowDownRatioReverse: {},
speedDecelerationRatio: {},
speedDecelerationRatioAbs: {},
distanceOffset: {},
backAnimDestination: {},
backAnimStarted: {},
calculateSwipeSpeed: function (axis) {
if (_posPoints.length > 1) {
lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50;
tempReleasePos = _posPoints[_posPoints.length - 2][axis];
} else {
lastFlickDuration = _getCurrentTime() - _gestureStartTime; // total gesture duration
tempReleasePos = _startPoint[axis];
}
s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos;
s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis]);
if (s.lastFlickDist[axis] > 20) {
s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration;
} else {
s.lastFlickSpeed[axis] = 0;
}
if (Math.abs(s.lastFlickSpeed[axis]) < 0.1) {
s.lastFlickSpeed[axis] = 0;
}
s.slowDownRatio[axis] = 0.95;
s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
s.speedDecelerationRatio[axis] = 1;
},
calculateOverBoundsAnimOffset: function (axis, speed) {
if (!s.backAnimStarted[axis]) {
if (_panOffset[axis] > _currPanBounds.min[axis]) {
s.backAnimDestination[axis] = _currPanBounds.min[axis];
} else if (_panOffset[axis] < _currPanBounds.max[axis]) {
s.backAnimDestination[axis] = _currPanBounds.max[axis];
}
if (s.backAnimDestination[axis] !== undefined) {
s.slowDownRatio[axis] = 0.7;
s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
if (s.speedDecelerationRatioAbs[axis] < 0.05) {
s.lastFlickSpeed[axis] = 0;
s.backAnimStarted[axis] = true;
_animateProp('bounceZoomPan' + axis, _panOffset[axis],
s.backAnimDestination[axis],
speed || 300,
framework.easing.sine.out,
function (pos) {
_panOffset[axis] = pos;
_applyCurrentZoomPan();
}
);
}
}
}
},
// Reduces the speed by slowDownRatio (per 10ms)
calculateAnimOffset: function (axis) {
if (!s.backAnimStarted[axis]) {
s.speedDecelerationRatio[axis] = s.speedDecelerationRatio[axis] * (s.slowDownRatio[axis] +
s.slowDownRatioReverse[axis] -
s.slowDownRatioReverse[axis] * s.timeDiff / 10);
s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis]);
s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff;
_panOffset[axis] += s.distanceOffset[axis];
}
},
panAnimLoop: function () {
if (_animations.zoomPan) {
_animations.zoomPan.raf = _requestAF(s.panAnimLoop);
s.now = _getCurrentTime();
s.timeDiff = s.now - s.lastNow;
s.lastNow = s.now;
s.calculateAnimOffset('x');
s.calculateAnimOffset('y');
_applyCurrentZoomPan();
s.calculateOverBoundsAnimOffset('x');
s.calculateOverBoundsAnimOffset('y');
if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) {
// round pan position
_panOffset.x = Math.round(_panOffset.x);
_panOffset.y = Math.round(_panOffset.y);
_applyCurrentZoomPan();
_stopAnimation('zoomPan');
return;
}
}
}
};
return s;
},
_completePanGesture = function (animData) {
// calculate swipe speed for Y axis (paanning)
animData.calculateSwipeSpeed('y');
_currPanBounds = self.currItem.bounds;
animData.backAnimDestination = {};
animData.backAnimStarted = {};
// Avoid acceleration animation if speed is too low
if (Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05) {
animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0;
// Run pan drag release animation. E.g. if you drag image and release finger without momentum.
animData.calculateOverBoundsAnimOffset('x');
animData.calculateOverBoundsAnimOffset('y');
return true;
}
// Animation loop that controls the acceleration after pan gesture ends
_registerStartAnimation('zoomPan');
animData.lastNow = _getCurrentTime();
animData.panAnimLoop();
},
_finishSwipeMainScrollGesture = function (gestureType, _releaseAnimData) {
var itemChanged;
if (!_mainScrollAnimating) {
_currZoomedItemIndex = _currentItemIndex;
}
var itemsDiff;
if (gestureType === 'swipe') {
var totalShiftDist = _currPoint.x - _startPoint.x,
isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10;
// if container is shifted for more than MIN_SWIPE_DISTANCE,
// and last flick gesture was in right direction
if (totalShiftDist > MIN_SWIPE_DISTANCE &&
(isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20)) {
// go to prev item
itemsDiff = -1;
} else if (totalShiftDist < -MIN_SWIPE_DISTANCE &&
(isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20)) {
// go to next item
itemsDiff = 1;
}
}
var nextCircle;
if (itemsDiff) {
_currentItemIndex += itemsDiff;
if (_currentItemIndex < 0) {
_currentItemIndex = _options.loop ? _getNumItems() - 1 : 0;
nextCircle = true;
} else if (_currentItemIndex >= _getNumItems()) {
_currentItemIndex = _options.loop ? 0 : _getNumItems() - 1;
nextCircle = true;
}
if (!nextCircle || _options.loop) {
_indexDiff += itemsDiff;
_currPositionIndex -= itemsDiff;
itemChanged = true;
}
}
var animateToX = _slideSize.x * _currPositionIndex;
var animateToDist = Math.abs(animateToX - _mainScrollPos.x);
var finishAnimDuration;
if (!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) {
// "return to current" duration, e.g. when dragging from slide 0 to -1
finishAnimDuration = 333;
} else {
finishAnimDuration = Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0 ?
animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x) :
333;
finishAnimDuration = Math.min(finishAnimDuration, 400);
finishAnimDuration = Math.max(finishAnimDuration, 250);
}
if (_currZoomedItemIndex === _currentItemIndex) {
itemChanged = false;
}
_mainScrollAnimating = true;
_shout('mainScrollAnimStart');
_animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out,
_moveMainScroll,
function () {
_stopAllAnimations();
_mainScrollAnimating = false;
_currZoomedItemIndex = -1;
if (itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
self.updateCurrItem();
}
_shout('mainScrollAnimComplete');
}
);
if (itemChanged) {
self.updateCurrItem(true);
}
return itemChanged;
},
_calculateZoomLevel = function (touchesDistance) {
return 1 / _startPointsDistance * touchesDistance * _startZoomLevel;
},
// Resets zoom if it's out of bounds
_completeZoomGesture = function () {
var destZoomLevel = _currZoomLevel,
minZoomLevel = _getMinZoomLevel(),
maxZoomLevel = _getMaxZoomLevel();
if (_currZoomLevel < minZoomLevel) {
destZoomLevel = minZoomLevel;
} else if (_currZoomLevel > maxZoomLevel) {
destZoomLevel = maxZoomLevel;
}
var destOpacity = 1,
onUpdate,
initialOpacity = _bgOpacity;
if (_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) {
//_closedByScroll = true;
self.close();
return true;
}
if (_opacityChanged) {
onUpdate = function (now) {
_applyBgOpacity((destOpacity - initialOpacity) * now + initialOpacity);
};
}
self.zoomTo(destZoomLevel, 0, 200, framework.easing.cubic.out, onUpdate);
return true;
};
_registerModule('Gestures', {
publicMethods: {
initGestures: function () {
// helper function that builds touch/pointer/mouse events
var addEventNames = function (pref, down, move, up, cancel) {
_dragStartEvent = pref + down;
_dragMoveEvent = pref + move;
_dragEndEvent = pref + up;
if (cancel) {
_dragCancelEvent = pref + cancel;
} else {
_dragCancelEvent = '';
}
};
_pointerEventEnabled = _features.pointerEvent;
if (_pointerEventEnabled && _features.touch) {
// we don't need touch events, if browser supports pointer events
_features.touch = false;
}
if (_pointerEventEnabled) {
if (navigator.pointerEnabled) {
addEventNames('pointer', 'down', 'move', 'up', 'cancel');
} else {
// IE10 pointer events are case-sensitive
addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
}
} else if (_features.touch) {
addEventNames('touch', 'start', 'move', 'end', 'cancel');
_likelyTouchDevice = true;
} else {
addEventNames('mouse', 'down', 'move', 'up');
}
_upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent + ' ' + _dragCancelEvent;
_downEvents = _dragStartEvent;
if (_pointerEventEnabled && !_likelyTouchDevice) {
_likelyTouchDevice = (navigator.maxTouchPoints > 1) || (navigator.msMaxTouchPoints > 1);
}
// make variable public
self.likelyTouchDevice = _likelyTouchDevice;
_globalEventHandlers[_dragStartEvent] = _onDragStart;
_globalEventHandlers[_dragMoveEvent] = _onDragMove;
_globalEventHandlers[_dragEndEvent] = _onDragRelease; // the Kraken
if (_dragCancelEvent) {
_globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent];
}
// Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input.
if (_features.touch) {
_downEvents += ' mousedown';
_upMoveEvents += ' mousemove mouseup';
_globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent];
_globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent];
_globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent];
}
if (!_likelyTouchDevice) {
// don't allow pan to next slide from zoomed state on Desktop
_options.allowPanToNext = false;
}
}
}
});
/*>>gestures*/
/*>>show-hide-transition*/
/**
* show-hide-transition.js:
*
* Manages initial opening or closing transition.
*
* If you're not planning to use transition for gallery at all,
* you may set options hideAnimationDuration and showAnimationDuration to 0,
* and just delete startAnimation function.
*
*/
var _showOrHideTimeout,
_showOrHide = function (item, img, out, completeFn) {
if (_showOrHideTimeout) {
clearTimeout(_showOrHideTimeout);
}
_initialZoomRunning = true;
_initialContentSet = true;
// dimensions of small thumbnail {x:,y:,w:}.
// Height is optional, as calculated based on large image.
var thumbBounds;
if (item.initialLayout) {
thumbBounds = item.initialLayout;
item.initialLayout = null;
} else {
thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
}
var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration;
var onComplete = function () {
_stopAnimation('initialZoom');
if (!out) {
_applyBgOpacity(1);
if (img) {
img.style.display = 'block';
}
framework.addClass(template, 'pswp--animated-in');
_shout('initialZoom' + (out ? 'OutEnd' : 'InEnd'));
} else {
self.template.removeAttribute('style');
self.bg.removeAttribute('style');
}
if (completeFn) {
completeFn();
}
_initialZoomRunning = false;
};
// if bounds aren't provided, just open gallery without animation
if (!duration || !thumbBounds || thumbBounds.x === undefined) {
_shout('initialZoom' + (out ? 'Out' : 'In'));
_currZoomLevel = item.initialZoomLevel;
_equalizePoints(_panOffset, item.initialPosition);
_applyCurrentZoomPan();
template.style.opacity = out ? 0 : 1;
_applyBgOpacity(1);
if (duration) {
setTimeout(function () {
onComplete();
}, duration);
} else {
onComplete();
}
return;
}
var startAnimation = function () {
var closeWithRaf = _closedByScroll,
fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity;
// apply hw-acceleration to image
if (item.miniImg) {
item.miniImg.style.webkitBackfaceVisibility = 'hidden';
}
if (!out) {
_currZoomLevel = thumbBounds.w / item.w;
_panOffset.x = thumbBounds.x;
_panOffset.y = thumbBounds.y - _initalWindowScrollY;
self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001;
_applyCurrentZoomPan();
}
_registerStartAnimation('initialZoom');
if (out && !closeWithRaf) {
framework.removeClass(template, 'pswp--animated-in');
}
if (fadeEverything) {
if (out) {
framework[(closeWithRaf ? 'remove' : 'add') + 'Class'](template, 'pswp--animate_opacity');
} else {
setTimeout(function () {
framework.addClass(template, 'pswp--animate_opacity');
}, 30);
}
}
_showOrHideTimeout = setTimeout(function () {
_shout('initialZoom' + (out ? 'Out' : 'In'));
if (!out) {
// "in" animation always uses CSS transitions (instead of rAF).
// CSS transition work faster here,
// as developer may also want to animate other things,
// like ui on top of sliding area, which can be animated just via CSS
_currZoomLevel = item.initialZoomLevel;
_equalizePoints(_panOffset, item.initialPosition);
_applyCurrentZoomPan();
_applyBgOpacity(1);
if (fadeEverything) {
template.style.opacity = 1;
} else {
_applyBgOpacity(1);
}
_showOrHideTimeout = setTimeout(onComplete, duration + 20);
} else {
// "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position
var destZoomLevel = thumbBounds.w / item.w,
initialPanOffset = {
x: _panOffset.x,
y: _panOffset.y
},
initialZoomLevel = _currZoomLevel,
initalBgOpacity = _bgOpacity,
onUpdate = function (now) {
if (now === 1) {
_currZoomLevel = destZoomLevel;
_panOffset.x = thumbBounds.x;
_panOffset.y = thumbBounds.y - _currentWindowScrollY;
} else {
_currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
_panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x;
_panOffset.y = (thumbBounds.y - _currentWindowScrollY - initialPanOffset.y) * now + initialPanOffset.y;
}
_applyCurrentZoomPan();
if (fadeEverything) {
template.style.opacity = 1 - now;
} else {
_applyBgOpacity(initalBgOpacity - now * initalBgOpacity);
}
};
if (closeWithRaf) {
_animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete);
} else {
onUpdate(1);
_showOrHideTimeout = setTimeout(onComplete, duration + 20);
}
}
}, out ? 25 : 90); // Main purpose of this delay is to give browser time to paint and
// create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows).
// Which avoids lag at the beginning of scale transition.
};
startAnimation();
};
/*>>show-hide-transition*/
/*>>items-controller*/
/**
*
* Controller manages gallery items, their dimensions, and their content.
*
*/
var _items,
_tempPanAreaSize = {},
_imagesToAppendPool = [],
_initialContentSet,
_initialZoomRunning,
_controllerDefaultOptions = {
index: 0,
errorMsg: '',
forceProgressiveLoading: false, // TODO
preload: [1, 1],
getNumItemsFn: function () {
return _items.length;
}
};
var _getItemAt,
_getNumItems,
_initialIsLoop,
_getZeroBounds = function () {
return {
center: { x: 0, y: 0 },
max: { x: 0, y: 0 },
min: { x: 0, y: 0 }
};
},
_calculateSingleItemPanBounds = function (item, realPanElementW, realPanElementH) {
var bounds = item.bounds;
// position of element when it's centered
bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2);
bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top;
// maximum pan position
bounds.max.x = (realPanElementW > _tempPanAreaSize.x) ?
Math.round(_tempPanAreaSize.x - realPanElementW) :
bounds.center.x;
bounds.max.y = (realPanElementH > _tempPanAreaSize.y) ?
Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top :
bounds.center.y;
// minimum pan position
bounds.min.x = (realPanElementW > _tempPanAreaSize.x) ? 0 : bounds.center.x;
bounds.min.y = (realPanElementH > _tempPanAreaSize.y) ? item.vGap.top : bounds.center.y;
},
_calculateItemSize = function (item, viewportSize, zoomLevel) {
if (item.src && !item.loadError) {
var isInitial = !zoomLevel;
if (isInitial) {
if (!item.vGap) {
item.vGap = { top: 0, bottom: 0 };
}
// allows overriding vertical margin for individual items
_shout('parseVerticalMargin', item);
}
_tempPanAreaSize.x = viewportSize.x;
_tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom;
if (isInitial) {
var hRatio = _tempPanAreaSize.x / item.w;
var vRatio = _tempPanAreaSize.y / item.h;
item.fitRatio = hRatio < vRatio ? hRatio : vRatio;
//item.fillRatio = hRatio > vRatio ? hRatio : vRatio;
var scaleMode = _options.scaleMode;
if (scaleMode === 'orig') {
zoomLevel = 1;
} else if (scaleMode === 'fit') {
zoomLevel = item.fitRatio;
}
if (zoomLevel > 1) {
zoomLevel = 1;
}
item.initialZoomLevel = zoomLevel;
if (!item.bounds) {
// reuse bounds object
item.bounds = _getZeroBounds();
}
}
if (!zoomLevel) {
return;
}
_calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel);
if (isInitial && zoomLevel === item.initialZoomLevel) {
item.initialPosition = item.bounds.center;
}
return item.bounds;
} else {
item.w = item.h = 0;
item.initialZoomLevel = item.fitRatio = 1;
item.bounds = _getZeroBounds();
item.initialPosition = item.bounds.center;
// if it's not image, we return zero bounds (content is not zoomable)
return item.bounds;
}
return false;
},
_appendImage = function (index, item, baseDiv, img, preventAnimation, keepPlaceholder) {
if (item.loadError) {
return;
}
if (img) {
item.imageAppended = true;
_setImageSize(item, img);
baseDiv.appendChild(img);
if (keepPlaceholder) {
setTimeout(function () {
if (item && item.loaded && item.placeholder) {
item.placeholder.style.display = 'none';
item.placeholder = null;
}
}, 500);
}
}
},
_preloadImage = function (item) {
item.loading = true;
item.loaded = false;
var img = item.img = framework.createEl('pswp__img', 'img');
var onComplete = function () {
item.loading = false;
item.loaded = true;
if (item.loadComplete) {
item.loadComplete(item);
} else {
item.img = null; // no need to store image object
}
img.onload = img.onerror = null;
img = null;
};
img.onload = onComplete;
img.onerror = function () {
item.loadError = true;
onComplete();
};
img.src = item.src;// + '?a=' + Math.random();
return img;
},
_checkForError = function (item, cleanUp) {
if (item.src && item.loadError && item.container) {
if (cleanUp) {
item.container.innerHTML = '';
}
item.container.innerHTML = _options.errorMsg.replace('%url%', item.src);
return true;
}
},
_setImageSize = function (item, img, maxRes) {
if (!item.src) {
return;
}
if (!img) {
img = item.container.lastChild;
}
var w = maxRes ? item.w : Math.round(item.w * item.fitRatio),
h = maxRes ? item.h : Math.round(item.h * item.fitRatio);
if (item.placeholder && !item.loaded) {
item.placeholder.style.width = w + 'px';
item.placeholder.style.height = h + 'px';
}
img.style.width = w + 'px';
img.style.height = h + 'px';
},
_appendImagesPool = function () {
if (_imagesToAppendPool.length) {
var poolItem;
for (var i = 0; i < _imagesToAppendPool.length; i++) {
poolItem = _imagesToAppendPool[i];
if (poolItem.holder.index === poolItem.index) {
_appendImage(poolItem.index, poolItem.item, poolItem.baseDiv, poolItem.img, false, poolItem.clearPlaceholder);
}
}
_imagesToAppendPool = [];
}
};
_registerModule('Controller', {
publicMethods: {
lazyLoadItem: function (index) {
index = _getLoopedId(index);
var item = _getItemAt(index);
if (!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) {
return;
}
_shout('gettingData', index, item);
if (!item.src) {
return;
}
_preloadImage(item);
},
initController: function () {
framework.extend(_options, _controllerDefaultOptions, true);
self.items = _items = items;
_getItemAt = self.getItemAt;
_getNumItems = _options.getNumItemsFn; //self.getNumItems;
_initialIsLoop = _options.loop;
if (_getNumItems() < 3) {
_options.loop = false; // disable loop if less then 3 items
}
_listen('beforeChange', function (diff) {
var p = _options.preload,
isNext = diff === null ? true : (diff >= 0),
preloadBefore = Math.min(p[0], _getNumItems()),
preloadAfter = Math.min(p[1], _getNumItems()),
i;
for (i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) {
self.lazyLoadItem(_currentItemIndex + i);
}
for (i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) {
self.lazyLoadItem(_currentItemIndex - i);
}
});
_listen('initialLayout', function () {
self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
});
_listen('mainScrollAnimComplete', _appendImagesPool);
_listen('initialZoomInEnd', _appendImagesPool);
_listen('destroy', function () {
var item;
for (var i = 0; i < _items.length; i++) {
item = _items[i];
// remove reference to DOM elements, for GC
if (item.container) {
item.container = null;
}
if (item.placeholder) {
item.placeholder = null;
}
if (item.img) {
item.img = null;
}
if (item.preloader) {
item.preloader = null;
}
if (item.loadError) {
item.loaded = item.loadError = false;
}
}
_imagesToAppendPool = null;
});
},
getItemAt: function (index) {
if (index >= 0) {
return _items[index] !== undefined ? _items[index] : false;
}
return false;
},
allowProgressiveImg: function () {
// 1. Progressive image loading isn't working on webkit/blink
// when hw-acceleration (e.g. translateZ) is applied to IMG element.
// That's why in PhotoSwipe parent element gets zoom transform, not image itself.
//
// 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element.
// That's why it's disabled on touch devices (mainly because of swipe transition)
//
// 3. Progressive image loading sometimes doesn't work in IE (up to 11).
// Don't allow progressive loading on non-large touch devices
return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200;
// 1200 - to eliminate touch devices with large screen (like Chromebook Pixel)
},
setContent: function (holder, index) {
if (_options.loop) {
index = _getLoopedId(index);
}
var prevItem = self.getItemAt(holder.index);
if (prevItem) {
prevItem.container = null;
}
var item = self.getItemAt(index),
img;
if (!item) {
holder.el.innerHTML = '';
return;
}
// allow to override data
_shout('gettingData', index, item);
holder.index = index;
holder.item = item;
// base container DIV is created only once for each of 3 holders
var baseDiv = item.container = framework.createEl('pswp__zoom-wrap');
if (!item.src && item.html) {
if (item.html.tagName) {
baseDiv.appendChild(item.html);
} else {
baseDiv.innerHTML = item.html;
}
}
_checkForError(item);
_calculateItemSize(item, _viewportSize);
if (item.src && !item.loadError && !item.loaded) {
item.loadComplete = function (item) {
// gallery closed before image finished loading
if (!_isOpen) {
return;
}
// check if holder hasn't changed while image was loading
if (holder && holder.index === index) {
if (_checkForError(item, true)) {
item.loadComplete = item.img = null;
_calculateItemSize(item, _viewportSize);
_applyZoomPanToItem(item);
if (holder.index === _currentItemIndex) {
// recalculate dimensions
self.updateCurrZoomItem();
}
return;
}
if (!item.imageAppended) {
if (_features.transform && (_mainScrollAnimating || _initialZoomRunning)) {
_imagesToAppendPool.push({
item: item,
baseDiv: baseDiv,
img: item.img,
index: index,
holder: holder,
clearPlaceholder: true
});
} else {
_appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true);
}
} else {
// remove preloader & mini-img
if (!_initialZoomRunning && item.placeholder) {
item.placeholder.style.display = 'none';
item.placeholder = null;
}
}
}
item.loadComplete = null;
item.img = null; // no need to store image element after it's added
_shout('imageLoadComplete', index, item);
};
if (framework.features.transform) {
var placeholderClassName = 'pswp__img pswp__img--placeholder';
placeholderClassName += (item.msrc ? '' : ' pswp__img--placeholder--blank');
var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : '');
if (item.msrc) {
placeholder.src = item.msrc;
}
_setImageSize(item, placeholder);
baseDiv.appendChild(placeholder);
item.placeholder = placeholder;
}
if (!item.loading) {
_preloadImage(item);
}
if (self.allowProgressiveImg()) {
// just append image
if (!_initialContentSet && _features.transform) {
_imagesToAppendPool.push({
item: item,
baseDiv: baseDiv,
img: item.img,
index: index,
holder: holder
});
} else {
_appendImage(index, item, baseDiv, item.img, true, true);
}
}
} else if (item.src && !item.loadError) {
// image object is created every time, due to bugs of image loading & delay when switching images
img = framework.createEl('pswp__img', 'img');
img.style.opacity = 1;
img.src = item.src;
_setImageSize(item, img);
_appendImage(index, item, baseDiv, img, true);
}
if (!_initialContentSet && index === _currentItemIndex) {
_currZoomElementStyle = baseDiv.style;
_showOrHide(item, (img || item.img));
} else {
_applyZoomPanToItem(item);
}
holder.el.innerHTML = '';
holder.el.appendChild(baseDiv);
},
cleanSlide: function (item) {
if (item.img) {
item.img.onload = item.img.onerror = null;
}
item.loaded = item.loading = item.img = item.imageAppended = false;
}
}
});
/*>>items-controller*/
/*>>tap*/
/**
* tap.js:
*
* Displatches tap and double-tap events.
*
*/
var tapTimer,
tapReleasePoint = {},
_dispatchTapEvent = function (origEvent, releasePoint, pointerType) {
var e = document.createEvent('CustomEvent'),
eDetail = {
origEvent: origEvent,
target: origEvent.target,
releasePoint: releasePoint,
pointerType: pointerType || 'touch'
};
e.initCustomEvent('pswpTap', true, true, eDetail);
origEvent.target.dispatchEvent(e);
};
_registerModule('Tap', {
publicMethods: {
initTap: function () {
_listen('firstTouchStart', self.onTapStart);
_listen('touchRelease', self.onTapRelease);
_listen('destroy', function () {
tapReleasePoint = {};
tapTimer = null;
});
},
onTapStart: function (touchList) {
if (touchList.length > 1) {
clearTimeout(tapTimer);
tapTimer = null;
}
},
onTapRelease: function (e, releasePoint) {
if (!releasePoint) {
return;
}
if (!_moved && !_isMultitouch && !_numAnimations) {
var p0 = releasePoint;
if (tapTimer) {
clearTimeout(tapTimer);
tapTimer = null;
// Check if taped on the same place
if (_isNearbyPoints(p0, tapReleasePoint)) {
_shout('doubleTap', p0);
return;
}
}
if (releasePoint.type === 'mouse') {
_dispatchTapEvent(e, releasePoint, 'mouse');
return;
}
var clickedTagName = e.target.tagName.toUpperCase();
// avoid double tap delay on buttons and elements that have class pswp__single-tap
if (clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap')) {
_dispatchTapEvent(e, releasePoint);
return;
}
_equalizePoints(tapReleasePoint, p0);
tapTimer = setTimeout(function () {
_dispatchTapEvent(e, releasePoint);
tapTimer = null;
}, 300);
}
}
}
});
/*>>tap*/
/*>>desktop-zoom*/
/**
*
* desktop-zoom.js:
*
* - Binds mousewheel event for paning zoomed image.
* - Manages "dragging", "zoomed-in", "zoom-out" classes.
* (which are used for cursors and zoom icon)
* - Adds toggleDesktopZoom function.
*
*/
var _wheelDelta;
_registerModule('DesktopZoom', {
publicMethods: {
initDesktopZoom: function () {
if (_oldIE) {
// no zoom for old IE (<=8)
return;
}
if (_likelyTouchDevice) {
// if detected hardware touch support, we wait until mouse is used,
// and only then apply desktop-zoom features
_listen('mouseUsed', function () {
self.setupDesktopZoom();
});
} else {
self.setupDesktopZoom(true);
}
},
setupDesktopZoom: function (onInit) {
_wheelDelta = {};
var events = 'wheel mousewheel DOMMouseScroll';
_listen('bindEvents', function () {
framework.bind(template, events, self.handleMouseWheel);
});
_listen('unbindEvents', function () {
if (_wheelDelta) {
framework.unbind(template, events, self.handleMouseWheel);
}
});
self.mouseZoomedIn = false;
var hasDraggingClass,
updateZoomable = function () {
if (self.mouseZoomedIn) {
framework.removeClass(template, 'pswp--zoomed-in');
self.mouseZoomedIn = false;
}
if (_currZoomLevel < 1) {
framework.addClass(template, 'pswp--zoom-allowed');
} else {
framework.removeClass(template, 'pswp--zoom-allowed');
}
removeDraggingClass();
},
removeDraggingClass = function () {
if (hasDraggingClass) {
framework.removeClass(template, 'pswp--dragging');
hasDraggingClass = false;
}
};
_listen('resize', updateZoomable);
_listen('afterChange', updateZoomable);
_listen('pointerDown', function () {
if (self.mouseZoomedIn) {
hasDraggingClass = true;
framework.addClass(template, 'pswp--dragging');
}
});
_listen('pointerUp', removeDraggingClass);
if (!onInit) {
updateZoomable();
}
},
handleMouseWheel: function (e) {
if (_currZoomLevel <= self.currItem.fitRatio) {
if (_options.modal) {
if (!_options.closeOnScroll || _numAnimations || _isDragging) {
e.preventDefault();
} else if (_transformKey && Math.abs(e.deltaY) > 2) {
// close PhotoSwipe
// if browser supports transforms & scroll changed enough
_closedByScroll = true;
self.close();
}
}
return true;
}
// allow just one event to fire
e.stopPropagation();
// https://developer.mozilla.org/en-US/docs/Web/Events/wheel
_wheelDelta.x = 0;
if ('deltaX' in e) {
if (e.deltaMode === 1 /* DOM_DELTA_LINE */) {
// 18 - average line height
_wheelDelta.x = e.deltaX * 18;
_wheelDelta.y = e.deltaY * 18;
} else {
_wheelDelta.x = e.deltaX;
_wheelDelta.y = e.deltaY;
}
} else if ('wheelDelta' in e) {
if (e.wheelDeltaX) {
_wheelDelta.x = -0.16 * e.wheelDeltaX;
}
if (e.wheelDeltaY) {
_wheelDelta.y = -0.16 * e.wheelDeltaY;
} else {
_wheelDelta.y = -0.16 * e.wheelDelta;
}
} else if ('detail' in e) {
_wheelDelta.y = e.detail;
} else {
return;
}
_calculatePanBounds(_currZoomLevel, true);
var newPanX = _panOffset.x - _wheelDelta.x,
newPanY = _panOffset.y - _wheelDelta.y;
// only prevent scrolling in nonmodal mode when not at edges
if (_options.modal ||
(
newPanX <= _currPanBounds.min.x && newPanX >= _currPanBounds.max.x &&
newPanY <= _currPanBounds.min.y && newPanY >= _currPanBounds.max.y
)) {
e.preventDefault();
}
// TODO: use rAF instead of mousewheel?
self.panTo(newPanX, newPanY);
},
toggleDesktopZoom: function (centerPoint) {
centerPoint = centerPoint || { x: _viewportSize.x / 2 + _offset.x, y: _viewportSize.y / 2 + _offset.y };
var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem);
var zoomOut = _currZoomLevel === doubleTapZoomLevel;
self.mouseZoomedIn = !zoomOut;
self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333);
framework[(!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in');
}
}
});
/*>>desktop-zoom*/
/*>>history*/
/**
*
* history.js:
*
* - Back button to close gallery.
*
* - Unique URL for each slide: example.com/&pid=1&gid=3
* (where PID is picture index, and GID and gallery index)
*
* - Switch URL when slides change.
*
*/
var _historyDefaultOptions = {
history: true,
galleryUID: 1
};
var _historyUpdateTimeout,
_hashChangeTimeout,
_hashAnimCheckTimeout,
_hashChangedByScript,
_hashChangedByHistory,
_hashReseted,
_initialHash,
_historyChanged,
_closedFromURL,
_urlChangedOnce,
_windowLoc,
_supportsPushState,
_getHash = function () {
return _windowLoc.hash.substring(1);
},
_cleanHistoryTimeouts = function () {
if (_historyUpdateTimeout) {
clearTimeout(_historyUpdateTimeout);
}
if (_hashAnimCheckTimeout) {
clearTimeout(_hashAnimCheckTimeout);
}
},
// pid - Picture index
// gid - Gallery index
_parseItemIndexFromURL = function () {
var hash = _getHash(),
params = {};
if (hash.length < 5) { // pid=1
return params;
}
var i, vars = hash.split('&');
for (i = 0; i < vars.length; i++) {
if (!vars[i]) {
continue;
}
var pair = vars[i].split('=');
if (pair.length < 2) {
continue;
}
params[pair[0]] = pair[1];
}
if (_options.galleryPIDs) {
// detect custom pid in hash and search for it among the items collection
var searchfor = params.pid;
params.pid = 0; // if custom pid cannot be found, fallback to the first item
for (i = 0; i < _items.length; i++) {
if (_items[i].pid === searchfor) {
params.pid = i;
break;
}
}
} else {
params.pid = parseInt(params.pid, 10) - 1;
}
if (params.pid < 0) {
params.pid = 0;
}
return params;
},
_updateHash = function () {
if (_hashAnimCheckTimeout) {
clearTimeout(_hashAnimCheckTimeout);
}
if (_numAnimations || _isDragging) {
// changing browser URL forces layout/paint in some browsers, which causes noticable lag during animation
// that's why we update hash only when no animations running
_hashAnimCheckTimeout = setTimeout(_updateHash, 500);
return;
}
if (_hashChangedByScript) {
clearTimeout(_hashChangeTimeout);
} else {
_hashChangedByScript = true;
}
var pid = (_currentItemIndex + 1);
var item = _getItemAt(_currentItemIndex);
if (item.hasOwnProperty('pid')) {
// carry forward any custom pid assigned to the item
pid = item.pid;
}
var newHash = _initialHash + '&' + 'gid=' + _options.galleryUID + '&' + 'pid=' + pid;
if (!_historyChanged) {
if (_windowLoc.hash.indexOf(newHash) === -1) {
_urlChangedOnce = true;
}
// first time - add new hisory record, then just replace
}
var newURL = _windowLoc.href.split('#')[0] + '#' + newHash;
if (_supportsPushState) {
if ('#' + newHash !== window.location.hash) {
history[_historyChanged ? 'replaceState' : 'pushState']('', document.title, newURL);
}
} else {
if (_historyChanged) {
_windowLoc.replace(newURL);
} else {
_windowLoc.hash = newHash;
}
}
_historyChanged = true;
_hashChangeTimeout = setTimeout(function () {
_hashChangedByScript = false;
}, 60);
};
_registerModule('History', {
publicMethods: {
initHistory: function () {
framework.extend(_options, _historyDefaultOptions, true);
if (!_options.history) {
return;
}
_windowLoc = window.location;
_urlChangedOnce = false;
_closedFromURL = false;
_historyChanged = false;
_initialHash = _getHash();
_supportsPushState = ('pushState' in history);
if (_initialHash.indexOf('gid=') > -1) {
_initialHash = _initialHash.split('&gid=')[0];
_initialHash = _initialHash.split('?gid=')[0];
}
_listen('afterChange', self.updateURL);
_listen('unbindEvents', function () {
framework.unbind(window, 'hashchange', self.onHashChange);
});
var returnToOriginal = function () {
_hashReseted = true;
if (!_closedFromURL) {
if (_urlChangedOnce) {
history.back();
} else {
if (_initialHash) {
_windowLoc.hash = _initialHash;
} else {
if (_supportsPushState) {
// remove hash from url without refreshing it or scrolling to top
history.pushState('', document.title, _windowLoc.pathname + _windowLoc.search);
} else {
_windowLoc.hash = '';
}
}
}
}
_cleanHistoryTimeouts();
};
_listen('unbindEvents', function () {
if (_closedByScroll) {
// if PhotoSwipe is closed by scroll, we go "back" before the closing animation starts
// this is done to keep the scroll position
returnToOriginal();
}
});
_listen('destroy', function () {
if (!_hashReseted) {
returnToOriginal();
}
});
_listen('firstUpdate', function () {
_currentItemIndex = _parseItemIndexFromURL().pid;
});
var index = _initialHash.indexOf('pid=');
if (index > -1) {
_initialHash = _initialHash.substring(0, index);
if (_initialHash.slice(-1) === '&') {
_initialHash = _initialHash.slice(0, -1);
}
}
setTimeout(function () {
if (_isOpen) { // hasn't destroyed yet
framework.bind(window, 'hashchange', self.onHashChange);
}
}, 40);
},
onHashChange: function () {
if (_getHash() === _initialHash) {
_closedFromURL = true;
self.close();
return;
}
if (!_hashChangedByScript) {
_hashChangedByHistory = true;
self.goTo(_parseItemIndexFromURL().pid);
_hashChangedByHistory = false;
}
},
updateURL: function () {
// Delay the update of URL, to avoid lag during transition,
// and to not to trigger actions like "refresh page sound" or "blinking favicon" to often
_cleanHistoryTimeouts();
if (_hashChangedByHistory) {
return;
}
if (!_historyChanged) {
_updateHash(); // first time
} else {
_historyUpdateTimeout = setTimeout(_updateHash, 800);
}
}
}
});
/*>>history*/
framework.extend(self, publicMethods);
};
return PhotoSwipe;
});
/*! PhotoSwipe Default UI - 4.1.0 - 2015-07-11
* http://photoswipe.com
* Copyright (c) 2015 Dmitry Semenov; */
/**
*
* 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: true,
shareEl: true,
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: ' / '
},
_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.className,
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 > 1200;
},
_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) {
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.className && e.type.indexOf('mouse') > -1 &&
(t.className.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;
});
/*!
* Copyright 2012, Chris Wanstrath
* Released under the MIT License
* https://github.com/defunkt/jquery-pjax
*/
(function($) {
// This is an auxiliary woodmart function to replace the outdated jquery method.
function wdTrim(data) {
if ( null == data ) {
return '';
} else if ( 'string' == typeof data ) {
return data.trim();
} else {
return (data + '').replace( '/^[\\s\uFEFF\xA0]+|[\\s\uFEFF\xA0]+$/g', '' );
}
}
// When called on a container with a selector, fetches the href with
// ajax into the container or with the data-pjax attribute on the link
// itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Exported as $.fn.pjax
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
//
// container - String selector for the element where to place the response body.
// push - Whether to pushState the URL. Defaults to true (of course).
// replace - Want to use replaceState instead? That's cool.
//
// For convenience the second parameter can be either the container or
// the options object.
//
// Returns the jQuery object
function fnPjax(selector, container, options) {
options = optionsFor(container, options);
return this.on('click.pjax', selector, function(event) {
var opts = options;
if (!opts.container) {
opts = $.extend({}, options);
opts.container = $(this).attr('data-pjax');
}
handleClick(event, opts);
});
}
// Public: pjax on click handler
//
// Exported as $.pjax.click.
//
// event - "click" jQuery.Event
// options - pjax options
//
// Examples
//
// $(document).on('click', 'a', $.pjax.click)
// // is the same as
// $(document).pjax('a')
//
// Returns nothing.
function handleClick(event, container, options) {
options = optionsFor(container, options);
var link = event.currentTarget;
var $link = $(link);
if (link.tagName.toUpperCase() !== 'A') {
throw '$.fn.pjax or $.pjax.click requires an anchor element';
}
// Middle click, cmd click, and ctrl click should open
// links in a new tab as normal.
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
// Ignore cross origin links
if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
return;
}
// Ignore case when a hash is being tacked on the current URL
if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location)) {
return;
}
// Ignore event with default prevented
if (event.isDefaultPrevented()) {
return;
}
var defaults = {
url : link.href,
container: $link.attr('data-pjax'),
target : link
};
var opts = $.extend({}, defaults, options);
var clickEvent = $.Event('pjax:click');
$link.trigger(clickEvent, [opts]);
if (!clickEvent.isDefaultPrevented()) {
pjax(opts);
event.preventDefault();
$link.trigger('pjax:clicked', [opts]);
}
}
// Public: pjax on form submit handler
//
// Exported as $.pjax.submit
//
// event - "click" jQuery.Event
// options - pjax options
//
// Examples
//
// $(document).on('submit', 'form', function(event) {
// $.pjax.submit(event, '[data-pjax-container]')
// })
//
// Returns nothing.
function handleSubmit(event, container, options) {
options = optionsFor(container, options);
var form = event.currentTarget;
var $form = $(form);
if (form.tagName.toUpperCase() !== 'FORM') {
throw '$.pjax.submit requires a form element';
}
var defaults = {
type : ($form.attr('method') || 'GET').toUpperCase(),
url : $form.attr('action'),
container: $form.attr('data-pjax'),
target : form
};
if (defaults.type !== 'GET' && window.FormData !== undefined) {
defaults.data = new FormData(form);
defaults.processData = false;
defaults.contentType = false;
} else {
// Can't handle file uploads, exit
if ($form.find(':file').length) {
return;
}
// Fallback to manually serializing the fields
defaults.data = $form.serializeArray();
}
pjax($.extend({}, defaults, options));
event.preventDefault();
}
// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - String selector for where to stick the response body.
// push - Whether to pushState the URL. Defaults to true (of course).
// replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
// var xhr = $.pjax({ url: this.href, container: '#main' })
// console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
function pjax(options) {
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options);
if (typeof options.url === 'function') {
options.url = options.url();
}
var hash = parseURL(options.url).hash;
var containerType = typeof options.container;
if (containerType !== 'string') {
throw 'expected string value for \'container\' option; got ' + containerType;
}
var context = options.context = $(options.container);
if (!context.length) {
throw 'the container selector \'' + options.container + '\' did not match anything';
}
// We want the browser to maintain two separate internal caches: one
// for pjax'd partial page loads and one for normal page loads.
// Without adding this secret parameter, some browsers will often
// confuse the two.
if (!options.data) {
options.data = {};
}
if (Array.isArray(options.data)) {
options.data.push({
name : '_pjax',
value: options.container
});
} else {
options.data._pjax = options.container;
}
function fire(type, args, props) {
if (!props) {
props = {};
}
props.relatedTarget = options.target;
var event = $.Event(type, props);
context.trigger(event, args);
return !event.isDefaultPrevented();
}
var timeoutTimer;
options.beforeSend = function(xhr, settings) {
// No timeout for non-GET requests
// Its not safe to request the resource again with a fallback method.
if (settings.type !== 'GET') {
settings.timeout = 0;
}
xhr.setRequestHeader('X-PJAX', 'true');
xhr.setRequestHeader('X-PJAX-Container', options.container);
if (!fire('pjax:beforeSend', [
xhr,
settings
])) {
return false;
}
if (settings.timeout > 0) {
timeoutTimer = setTimeout(function() {
if (fire('pjax:timeout', [
xhr,
options
])) {
xhr.abort('timeout');
}
}, settings.timeout);
// Clear timeout setting so jquerys internal timeout isn't invoked
settings.timeout = 0;
}
var url = parseURL(settings.url);
if (hash) {
url.hash = hash;
}
options.requestUrl = stripInternalParams(url);
};
options.complete = function(xhr, textStatus) {
if (timeoutTimer) {
clearTimeout(timeoutTimer);
}
fire('pjax:complete', [
xhr,
textStatus,
options
]);
fire('pjax:end', [
xhr,
options
]);
};
options.error = function(xhr, textStatus, errorThrown) {
var container = extractContainer('', xhr, options);
var allowed = fire('pjax:error', [
xhr,
textStatus,
errorThrown,
options
]);
if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
locationReplace(container.url);
}
};
options.success = function(data, status, xhr) {
var previousState = pjax.state;
// If $.pjax.defaults.version is a function, invoke it first.
// Otherwise it can be a static string.
var currentVersion = typeof $.pjax.defaults.version === 'function' ?
$.pjax.defaults.version() :
$.pjax.defaults.version;
var latestVersion = xhr.getResponseHeader('X-PJAX-Version');
var container = extractContainer(data, xhr, options);
var url = parseURL(container.url);
if (hash) {
url.hash = hash;
container.url = url.href;
}
// If there is a layout version mismatch, hard load the new url
if (currentVersion && latestVersion && currentVersion !== latestVersion) {
locationReplace(container.url);
return;
}
// If the new response is missing a body, hard load the page
if (!container.contents) {
locationReplace(container.url);
return;
}
pjax.state = {
id : options.id || uniqueId(),
url : container.url,
title : container.title,
container: options.container,
fragment : options.fragment,
timeout : options.timeout
};
if (options.push || options.replace) {
window.history.replaceState(pjax.state, container.title, container.url);
}
// Only blur the focus if the focused element is within the container.
var blurFocus = $.contains(context, document.activeElement);
// Clear out any focused controls before inserting new page contents.
if (blurFocus) {
try {
document.activeElement.blur();
}
catch (e) { /* ignore */
}
}
if (container.title) {
document.title = container.title;
}
fire('pjax:beforeReplace', [
container.contents,
options
], {
state : pjax.state,
previousState: previousState
});
if ('function' === typeof options.renderCallback) {
options.renderCallback(context, container.contents, afterRender);
} else {
context.html(container.contents);
afterRender();
}
function afterRender() {
// FF bug: Won't autofocus fields that are inserted via JS.
// This behavior is incorrect. So if theres no current focus, autofocus
// the last field.
//
// http://www.w3.org/html/wg/drafts/html/master/forms.html
var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0];
if (autofocusEl && document.activeElement !== autofocusEl) {
autofocusEl.trigger('focus');
}
executeScriptTags(container.scripts);
var scrollTo = options.scrollTo;
// Ensure browser scrolls to the element referenced by the URL anchor
if (hash) {
var name = decodeURIComponent(hash.slice(1));
var target = document.getElementById(name) || document.getElementsByName(name)[0];
if (target) {
scrollTo = $(target).offset().top;
}
}
if (typeof scrollTo == 'number') {
$(window).scrollTop(scrollTo);
}
fire('pjax:success', [
data,
status,
xhr,
options
]);
}
};
// Initialize pjax.state for the initial page load. Assume we're
// using the container and options of the link we're loading for the
// back button to the initial page. This ensures good back button
// behavior.
if (!pjax.state) {
pjax.state = {
id : uniqueId(),
url : window.location.href,
title : document.title,
container: options.container,
fragment : options.fragment,
timeout : options.timeout
};
window.history.replaceState(pjax.state, document.title);
}
// Cancel the current request if we're already pjaxing
abortXHR(pjax.xhr);
pjax.options = options;
var xhr = pjax.xhr = $.ajax(options);
if (xhr.readyState > 0) {
if (options.push && !options.replace) {
// Cache current container element before replacing it
cachePush(pjax.state.id, [
options.container,
cloneContents(context)
]);
window.history.pushState(null, '', options.requestUrl);
}
fire('pjax:start', [
xhr,
options
]);
fire('pjax:send', [
xhr,
options
]);
}
return pjax.xhr;
}
// Public: Reload current page with pjax.
//
// Returns whatever $.pjax returns.
function pjaxReload(container, options) {
var defaults = {
url : window.location.href,
push : false,
replace : true,
scrollTo: false
};
return pjax($.extend(defaults, optionsFor(container, options)));
}
// Internal: Hard replace current state with url.
//
// Work for around WebKit
// https://bugs.webkit.org/show_bug.cgi?id=93506
//
// Returns nothing.
function locationReplace(url) {
window.history.replaceState(null, '', pjax.state.url);
window.location.replace(url);
}
var initialPop = true;
var initialURL = window.location.href;
var initialState = window.history.state;
// Initialize $.pjax.state if possible
// Happens when reloading a page and coming forward from a different
// session history.
if (initialState && initialState.container) {
pjax.state = initialState;
}
// Non-webkit browsers don't fire an initial popstate event
if ('state' in window.history) {
initialPop = false;
}
// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
function onPjaxPopstate(event) {
// Hitting back or forward should override any pending PJAX request.
if (!initialPop) {
abortXHR(pjax.xhr);
}
var previousState = pjax.state;
var state = event.state;
var direction;
if (state && state.container) {
// When coming forward from a separate history session, will get an
// initial pop with a state we are already at. Skip reloading the current
// page.
if (initialPop && initialURL == state.url) {
return;
}
if (previousState) {
// If popping back to the same state, just skip.
// Could be clicking back from hashchange rather than a pushState.
if (previousState.id === state.id) {
return;
}
// Since state IDs always increase, we can deduce the navigation direction
direction = previousState.id < state.id ? 'forward' : 'back';
}
var cache = cacheMapping[state.id] || [];
var containerSelector = cache[0] || state.container;
var container = $(containerSelector), contents = cache[1];
if (container.length) {
if (previousState) {
// Cache current container before replacement and inform the
// cache which direction the history shifted.
cachePop(direction, previousState.id, [
containerSelector,
cloneContents(container)
]);
}
var popstateEvent = $.Event('pjax:popstate', {
state : state,
direction: direction
});
container.trigger(popstateEvent);
var options = {
id : state.id,
url : state.url,
container: containerSelector,
push : false,
fragment : state.fragment,
timeout : state.timeout,
scrollTo : false
};
if (contents) {
container.trigger('pjax:start', [
null,
options
]);
pjax.state = state;
if (state.title) {
document.title = state.title;
}
var beforeReplaceEvent = $.Event('pjax:beforeReplace', {
state : state,
previousState: previousState
});
container.trigger(beforeReplaceEvent, [
contents,
options
]);
container.html(contents);
container.trigger('pjax:end', [
null,
options
]);
} else {
pjax(options);
}
// Force reflow/relayout before the browser tries to restore the
// scroll position.
container[0].offsetHeight; // eslint-disable-line no-unused-expressions
} else {
locationReplace(location.href);
}
}
initialPop = false;
}
// Fallback version of main pjax function for browsers that don't
// support pushState.
//
// Returns nothing since it retriggers a hard form submission.
function fallbackPjax(options) {
var url = typeof options.url === 'function' ? options.url() : options.url,
method = options.type ? options.type.toUpperCase() : 'GET';
var form = $('