You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

34112 lines
1.3 MiB

/**
* Ajax Autocomplete for jQuery, version 1.2.24
* (c) 2015 Tomas Kirda
*
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
*/
/*jslint browser: true, white: true, plusplus: true, vars: true */
/*global define, window, document, jQuery, exports, require */
// Expose plugin as an AMD module if AMD loader is present:
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object' && typeof require === 'function') {
// Browserify
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
'use strict';
var
utils = (function () {
return {
escapeRegExChars: function (value) {
return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
createNode: function (containerClass) {
var div = document.createElement('div');
div.className = containerClass;
div.style.position = 'absolute';
div.style.display = 'none';
return div;
}
};
}()),
keys = {
ESC: 27,
TAB: 9,
RETURN: 13,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};
function Autocomplete(el, options) {
var noop = function () { },
that = this,
defaults = {
ajaxSettings: {},
autoSelectFirst: false,
appendTo: document.body,
serviceUrl: null,
lookup: null,
onSelect: null,
width: 'auto',
minChars: 1,
maxHeight: 300,
deferRequestBy: 0,
params: {},
formatResult: Autocomplete.formatResult,
delimiter: null,
zIndex: 9999,
type: 'GET',
noCache: false,
onSearchStart: noop,
onSearchComplete: noop,
onSearchError: noop,
preserveInput: false,
containerClass: 'autocomplete-suggestions',
tabDisabled: false,
dataType: 'text',
currentRequest: null,
triggerSelectOnValidInput: true,
preventBadQueries: true,
lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
},
paramName: 'query',
transformResult: function (response) {
return typeof response === 'string' ? $.parseJSON(response) : response;
},
showNoSuggestionNotice: false,
noSuggestionNotice: 'No results',
orientation: 'bottom',
forceFixPosition: false
};
// Shared variables:
that.element = el;
that.el = $(el);
that.suggestions = [];
that.badQueries = [];
that.selectedIndex = -1;
that.currentValue = that.element.value;
that.intervalId = 0;
that.cachedResponse = {};
that.onChangeInterval = null;
that.onChange = null;
that.isLocal = false;
that.suggestionsContainer = null;
that.noSuggestionsContainer = null;
that.options = $.extend({}, defaults, options);
that.classes = {
selected: 'autocomplete-selected',
suggestion: 'autocomplete-suggestion'
};
that.hint = null;
that.hintValue = '';
that.selection = null;
// Initialize and set options:
that.initialize();
that.setOptions(options);
}
Autocomplete.utils = utils;
$.Autocomplete = Autocomplete;
Autocomplete.formatResult = function (suggestion, currentValue) {
var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
return suggestion.value
.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/&lt;(\/?strong)&gt;/g, '<$1>');
};
Autocomplete.prototype = {
killerFn: null,
initialize: function () {
var that = this,
suggestionSelector = '.' + that.classes.suggestion,
selected = that.classes.selected,
options = that.options,
container;
// Remove autocomplete attribute to prevent native suggestions:
that.element.setAttribute('autocomplete', 'off');
that.killerFn = function (e) {
if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
that.killSuggestions();
that.disableKillerFn();
}
};
// html() deals with many types: htmlString or Element or Array or jQuery
that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
.html(this.options.noSuggestionNotice).get(0);
that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
container = $(that.suggestionsContainer);
container.appendTo(options.appendTo);
// Only set width if it was provided:
if (options.width !== 'auto') {
container.width(options.width);
}
// Listen for mouse over event on suggestions list:
container.on('mouseover.autocomplete', suggestionSelector, function () {
that.activate($(this).data('index'));
});
// Deselect active element when mouse leaves suggestions container:
container.on('mouseout.autocomplete', function () {
that.selectedIndex = -1;
container.children('.' + selected).removeClass(selected);
});
// Listen for click event on suggestions list:
container.on('click.autocomplete', suggestionSelector, function () {
that.select($(this).data('index'));
});
that.fixPositionCapture = function () {
if (that.visible) {
that.fixPosition();
}
};
$(window).on('resize.autocomplete', that.fixPositionCapture);
that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
that.el.on('blur.autocomplete', function () { that.onBlur(); });
that.el.on('focus.autocomplete', function () { that.onFocus(); });
that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); });
},
onFocus: function () {
var that = this;
that.fixPosition();
if (that.options.minChars === 0 && that.el.val().length === 0) {
that.onValueChange();
}
},
onBlur: function () {
this.enableKillerFn();
},
abortAjax: function () {
var that = this;
if (that.currentRequest) {
that.currentRequest.abort();
that.currentRequest = null;
}
},
setOptions: function (suppliedOptions) {
var that = this,
options = that.options;
$.extend(options, suppliedOptions);
that.isLocal = $.isArray(options.lookup);
if (that.isLocal) {
options.lookup = that.verifySuggestionsFormat(options.lookup);
}
options.orientation = that.validateOrientation(options.orientation, 'bottom');
// Adjust height, width and z-index:
$(that.suggestionsContainer).css({
'max-height': options.maxHeight + 'px',
'width': options.width + 'px',
'z-index': options.zIndex
});
},
clearCache: function () {
this.cachedResponse = {};
this.badQueries = [];
},
clear: function () {
this.clearCache();
this.currentValue = '';
this.suggestions = [];
},
disable: function () {
var that = this;
that.disabled = true;
clearInterval(that.onChangeInterval);
that.abortAjax();
},
enable: function () {
this.disabled = false;
},
fixPosition: function () {
// Use only when container has already its content
var that = this,
$container = $(that.suggestionsContainer),
containerParent = $container.parent().get(0);
// Fix position automatically when appended to body.
// In other cases force parameter must be given.
if (containerParent !== document.body && !that.options.forceFixPosition) {
return;
}
// Choose orientation
var orientation = that.options.orientation,
containerHeight = $container.outerHeight(),
height = that.el.outerHeight(),
offset = that.el.offset(),
styles = { 'top': offset.top, 'left': offset.left };
if (orientation === 'auto') {
var viewPortHeight = $(window).height(),
scrollTop = $(window).scrollTop(),
topOverflow = -scrollTop + offset.top - containerHeight,
bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom';
}
if (orientation === 'top') {
styles.top += -containerHeight;
} else {
styles.top += height;
}
// If container is not positioned to body,
// correct its position using offset parent offset
if(containerParent !== document.body) {
var opacity = $container.css('opacity'),
parentOffsetDiff;
if (!that.visible){
$container.css('opacity', 0).show();
}
parentOffsetDiff = $container.offsetParent().offset();
styles.top -= parentOffsetDiff.top;
styles.left -= parentOffsetDiff.left;
if (!that.visible){
$container.css('opacity', opacity).hide();
}
}
// -2px to account for suggestions border.
if (that.options.width === 'auto') {
styles.width = (that.el.outerWidth() - 2) + 'px';
}
$container.css(styles);
},
enableKillerFn: function () {
var that = this;
$(document).on('click.autocomplete', that.killerFn);
},
disableKillerFn: function () {
var that = this;
$(document).off('click.autocomplete', that.killerFn);
},
killSuggestions: function () {
var that = this;
that.stopKillSuggestions();
that.intervalId = window.setInterval(function () {
if (that.visible) {
that.el.val(that.currentValue);
that.hide();
}
that.stopKillSuggestions();
}, 50);
},
stopKillSuggestions: function () {
window.clearInterval(this.intervalId);
},
isCursorAtEnd: function () {
var that = this,
valLength = that.el.val().length,
selectionStart = that.element.selectionStart,
range;
if (typeof selectionStart === 'number') {
return selectionStart === valLength;
}
if (document.selection) {
range = document.selection.createRange();
range.moveStart('character', -valLength);
return valLength === range.text.length;
}
return true;
},
onKeyPress: function (e) {
var that = this;
// If suggestions are hidden and user presses arrow down, display suggestions:
if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
that.suggest();
return;
}
if (that.disabled || !that.visible) {
return;
}
switch (e.which) {
case keys.ESC:
that.el.val(that.currentValue);
that.hide();
break;
case keys.RIGHT:
if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
that.selectHint();
break;
}
return;
case keys.TAB:
if (that.hint && that.options.onHint) {
that.selectHint();
return;
}
if (that.selectedIndex === -1) {
that.hide();
return;
}
that.select(that.selectedIndex);
if (that.options.tabDisabled === false) {
return;
}
break;
case keys.RETURN:
if (that.selectedIndex === -1) {
that.hide();
return;
}
that.select(that.selectedIndex);
break;
case keys.UP:
that.moveUp();
break;
case keys.DOWN:
that.moveDown();
break;
default:
return;
}
// Cancel event if function did not return:
e.stopImmediatePropagation();
e.preventDefault();
},
onKeyUp: function (e) {
var that = this;
if (that.disabled) {
return;
}
switch (e.which) {
case keys.UP:
case keys.DOWN:
return;
}
clearInterval(that.onChangeInterval);
if (that.currentValue !== that.el.val()) {
that.findBestHint();
if (that.options.deferRequestBy > 0) {
// Defer lookup in case when value changes very quickly:
that.onChangeInterval = setInterval(function () {
that.onValueChange();
}, that.options.deferRequestBy);
} else {
that.onValueChange();
}
}
},
onValueChange: function () {
var that = this,
options = that.options,
value = that.el.val(),
query = that.getQuery(value);
if (that.selection && that.currentValue !== query) {
that.selection = null;
(options.onInvalidateSelection || $.noop).call(that.element);
}
clearInterval(that.onChangeInterval);
that.currentValue = value;
that.selectedIndex = -1;
// Check existing suggestion for the match before proceeding:
if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
that.select(0);
return;
}
if (query.length < options.minChars) {
that.hide();
} else {
that.getSuggestions(query);
}
},
isExactMatch: function (query) {
var suggestions = this.suggestions;
return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase());
},
getQuery: function (value) {
var delimiter = this.options.delimiter,
parts;
if (!delimiter) {
return value;
}
parts = value.split(delimiter);
return $.trim(parts[parts.length - 1]);
},
getSuggestionsLocal: function (query) {
var that = this,
options = that.options,
queryLowerCase = query.toLowerCase(),
filter = options.lookupFilter,
limit = parseInt(options.lookupLimit, 10),
data;
data = {
suggestions: $.grep(options.lookup, function (suggestion) {
return filter(suggestion, query, queryLowerCase);
})
};
if (limit && data.suggestions.length > limit) {
data.suggestions = data.suggestions.slice(0, limit);
}
return data;
},
getSuggestions: function (q) {
var response,
that = this,
options = that.options,
serviceUrl = options.serviceUrl,
params,
cacheKey,
ajaxSettings;
options.params[options.paramName] = q;
params = options.ignoreParams ? null : options.params;
if (options.onSearchStart.call(that.element, options.params) === false) {
return;
}
if (typeof options.lookup === "function"){
options.lookup(q, function (data) {
that.suggestions = data.suggestions;
that.suggest();
options.onSearchComplete.call(that.element, q, data.suggestions);
});
return;
}
if (that.isLocal) {
response = that.getSuggestionsLocal(q);
} else {
if (typeof serviceUrl === "function") {
serviceUrl = serviceUrl.call(that.element, q);
}
cacheKey = serviceUrl + '?' + $.param(params || {});
response = that.cachedResponse[cacheKey];
}
if (response && $.isArray(response.suggestions)) {
that.suggestions = response.suggestions;
that.suggest();
options.onSearchComplete.call(that.element, q, response.suggestions);
} else if (!that.isBadQuery(q)) {
that.abortAjax();
ajaxSettings = {
url: serviceUrl,
data: params,
type: options.type,
dataType: options.dataType
};
$.extend(ajaxSettings, options.ajaxSettings);
that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
var result;
that.currentRequest = null;
result = options.transformResult(data, q);
that.processResponse(result, q, cacheKey);
options.onSearchComplete.call(that.element, q, result.suggestions);
}).fail(function (jqXHR, textStatus, errorThrown) {
options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown);
});
} else {
options.onSearchComplete.call(that.element, q, []);
}
},
isBadQuery: function (q) {
if (!this.options.preventBadQueries){
return false;
}
var badQueries = this.badQueries,
i = badQueries.length;
while (i--) {
if (q.indexOf(badQueries[i]) === 0) {
return true;
}
}
return false;
},
hide: function () {
var that = this,
container = $(that.suggestionsContainer);
if (typeof that.options.onHide === "function" && that.visible) {
that.options.onHide.call(that.element, container);
}
that.visible = false;
that.selectedIndex = -1;
clearInterval(that.onChangeInterval);
$(that.suggestionsContainer).hide();
that.signalHint(null);
},
suggest: function () {
if (this.suggestions.length === 0) {
if (this.options.showNoSuggestionNotice) {
this.noSuggestions();
} else {
this.hide();
}
return;
}
var that = this,
options = that.options,
groupBy = options.groupBy,
formatResult = options.formatResult,
value = that.getQuery(that.currentValue),
className = that.classes.suggestion,
classSelected = that.classes.selected,
container = $(that.suggestionsContainer),
noSuggestionsContainer = $(that.noSuggestionsContainer),
beforeRender = options.beforeRender,
html = '',
category,
formatGroup = function (suggestion, index) {
var currentCategory = suggestion.data[groupBy];
if (category === currentCategory){
return '';
}
category = currentCategory;
return '<div class="autocomplete-group"><strong>' + category + '</strong></div>';
};
if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
that.select(0);
return;
}
// Build suggestions inner HTML:
$.each(that.suggestions, function (i, suggestion) {
if (groupBy){
html += formatGroup(suggestion, value, i);
}
html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
});
this.adjustContainerWidth();
noSuggestionsContainer.detach();
container.html(html);
if (typeof beforeRender === "function") {
beforeRender.call(that.element, container);
}
that.fixPosition();
container.show();
// Select first value by default:
if (options.autoSelectFirst) {
that.selectedIndex = 0;
container.scrollTop(0);
container.children('.' + className).first().addClass(classSelected);
}
that.visible = true;
that.findBestHint();
},
noSuggestions: function() {
var that = this,
container = $(that.suggestionsContainer),
noSuggestionsContainer = $(that.noSuggestionsContainer);
this.adjustContainerWidth();
// Some explicit steps. Be careful here as it easy to get
// noSuggestionsContainer removed from DOM if not detached properly.
noSuggestionsContainer.detach();
container.empty(); // clean suggestions if any
container.append(noSuggestionsContainer);
that.fixPosition();
container.show();
that.visible = true;
},
adjustContainerWidth: function() {
var that = this,
options = that.options,
width,
container = $(that.suggestionsContainer);
// If width is auto, adjust width before displaying suggestions,
// because if instance was created before input had width, it will be zero.
// Also it adjusts if input width has changed.
// -2px to account for suggestions border.
if (options.width === 'auto') {
width = that.el.outerWidth() - 2;
container.width(width > 0 ? width : 300);
}
},
findBestHint: function () {
var that = this,
value = that.el.val().toLowerCase(),
bestMatch = null;
if (!value) {
return;
}
$.each(that.suggestions, function (i, suggestion) {
var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
if (foundMatch) {
bestMatch = suggestion;
}
return !foundMatch;
});
that.signalHint(bestMatch);
},
signalHint: function (suggestion) {
var hintValue = '',
that = this;
if (suggestion) {
hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
}
if (that.hintValue !== hintValue) {
that.hintValue = hintValue;
that.hint = suggestion;
(this.options.onHint || $.noop)(hintValue);
}
},
verifySuggestionsFormat: function (suggestions) {
// If suggestions is string array, convert them to supported format:
if (suggestions.length && typeof suggestions[0] === 'string') {
return $.map(suggestions, function (value) {
return { value: value, data: null };
});
}
return suggestions;
},
validateOrientation: function(orientation, fallback) {
orientation = $.trim(orientation || '').toLowerCase();
if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
orientation = fallback;
}
return orientation;
},
processResponse: function (result, originalQuery, cacheKey) {
var that = this,
options = that.options;
result.suggestions = that.verifySuggestionsFormat(result.suggestions);
// Cache results if cache is not disabled:
if (!options.noCache) {
that.cachedResponse[cacheKey] = result;
if (options.preventBadQueries && result.suggestions.length === 0) {
that.badQueries.push(originalQuery);
}
}
// Return if originalQuery is not matching current query:
if (originalQuery !== that.getQuery(that.currentValue)) {
return;
}
that.suggestions = result.suggestions;
that.suggest();
},
activate: function (index) {
var that = this,
activeItem,
selected = that.classes.selected,
container = $(that.suggestionsContainer),
children = container.find('.' + that.classes.suggestion);
container.find('.' + selected).removeClass(selected);
that.selectedIndex = index;
if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
activeItem = children.get(that.selectedIndex);
$(activeItem).addClass(selected);
return activeItem;
}
return null;
},
selectHint: function () {
var that = this,
i = $.inArray(that.hint, that.suggestions);
that.select(i);
},
select: function (i) {
var that = this;
that.hide();
that.onSelect(i);
},
moveUp: function () {
var that = this;
if (that.selectedIndex === -1) {
return;
}
if (that.selectedIndex === 0) {
$(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
that.selectedIndex = -1;
that.el.val(that.currentValue);
that.findBestHint();
return;
}
that.adjustScroll(that.selectedIndex - 1);
},
moveDown: function () {
var that = this;
if (that.selectedIndex === (that.suggestions.length - 1)) {
return;
}
that.adjustScroll(that.selectedIndex + 1);
},
adjustScroll: function (index) {
var that = this,
activeItem = that.activate(index);
if (!activeItem) {
return;
}
var offsetTop,
upperBound,
lowerBound,
heightDelta = $(activeItem).outerHeight();
offsetTop = activeItem.offsetTop;
upperBound = $(that.suggestionsContainer).scrollTop();
lowerBound = upperBound + that.options.maxHeight - heightDelta;
if (offsetTop < upperBound) {
$(that.suggestionsContainer).scrollTop(offsetTop);
} else if (offsetTop > lowerBound) {
$(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
}
if (!that.options.preserveInput) {
that.el.val(that.getValue(that.suggestions[index].value));
}
that.signalHint(null);
},
onSelect: function (index) {
var that = this,
onSelectCallback = that.options.onSelect,
suggestion = that.suggestions[index];
that.currentValue = that.getValue(suggestion.value);
if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
that.el.val(that.currentValue);
}
that.signalHint(null);
that.suggestions = [];
that.selection = suggestion;
if (typeof onSelectCallback === "function") {
onSelectCallback.call(that.element, suggestion);
}
},
getValue: function (value) {
var that = this,
delimiter = that.options.delimiter,
currentValue,
parts;
if (!delimiter) {
return value;
}
currentValue = that.currentValue;
parts = currentValue.split(delimiter);
if (parts.length === 1) {
return value;
}
return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
},
dispose: function () {
var that = this;
that.el.off('.autocomplete').removeData('autocomplete');
that.disableKillerFn();
$(window).off('resize.autocomplete', that.fixPositionCapture);
$(that.suggestionsContainer).remove();
}
};
// Create chainable jQuery plugin:
$.fn.devbridgeAutocomplete = function (options, args) {
var dataKey = 'autocomplete';
// If function invoked without argument return
// instance of the first matched element:
if (arguments.length === 0) {
return this.first().data(dataKey);
}
return this.each(function () {
var inputElement = $(this),
instance = inputElement.data(dataKey);
if (typeof options === 'string') {
if (instance && typeof instance[options] === 'function') {
instance[options](args);
}
} else {
// If instance already exists, destroy it:
if (instance && instance.dispose) {
instance.dispose();
}
instance = new Autocomplete(this, options);
inputElement.data(dataKey, instance);
}
});
};
}));
/*!
* JavaScript Cookie v2.1.4
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
; (function (factory) {
var registeredInModuleLoader = false;
if (typeof define === 'function' && define.amd) {
define(factory);
registeredInModuleLoader = true;
}
if (typeof exports === 'object') {
module.exports = factory();
registeredInModuleLoader = true;
}
if (!registeredInModuleLoader) {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
}(function () {
function extend() {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[i];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init(converter) {
function api(key, value, attributes) {
var result;
if (typeof document === 'undefined') {
return;
}
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
// We're using "expires" because "max-age" is not supported by IE
attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) { }
if (!converter.write) {
value = encodeURIComponent(String(value))
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
} else {
value = converter.write(value, key);
}
key = encodeURIComponent(String(key));
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
key = key.replace(/[\(\)]/g, escape);
var stringifiedAttributes = '';
for (var attributeName in attributes) {
if (!attributes[attributeName]) {
continue;
}
stringifiedAttributes += '; ' + attributeName;
if (attributes[attributeName] === true) {
continue;
}
stringifiedAttributes += '=' + attributes[attributeName];
}
return (document.cookie = key + '=' + value + stringifiedAttributes);
}
// Read
if (!key) {
result = {};
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling "get()"
var cookies = document.cookie ? document.cookie.split('; ') : [];
var rdecode = /(%[0-9A-Z]{2})+/g;
var i = 0;
for (; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var cookie = parts.slice(1).join('=');
if (cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
try {
var name = parts[0].replace(rdecode, decodeURIComponent);
cookie = converter.read ?
converter.read(cookie, name) : converter(cookie, name) ||
cookie.replace(rdecode, decodeURIComponent);
if (this.json) {
try {
cookie = JSON.parse(cookie);
} catch (e) { }
}
if (key === name) {
result = cookie;
break;
}
if (!key) {
result[name] = cookie;
}
} catch (e) { }
}
return result;
}
api.set = api;
api.get = function (key) {
return api.call(api, key);
};
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {
api(key, '', extend(attributes, {
expires: -1
}));
};
api.withConverter = init;
return api;
}
return init(function () { });
}));
/*!
* The Final Countdown for jQuery v2.1.0 (http://hilios.github.io/jQuery.countdown/)
* Copyright (c) 2015 Edson Hilios
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
(function (factory) {
"use strict";
if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
} else {
factory(jQuery);
}
})(function ($) {
"use strict";
var instances = [], matchers = [], defaultOptions = {
precision: 100,
elapse: false
};
matchers.push(/^[0-9]*$/.source);
matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source);
matchers.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source);
matchers = new RegExp(matchers.join("|"));
function parseDateString(dateString) {
if (dateString instanceof Date) {
return dateString;
}
if (String(dateString).match(matchers)) {
if (String(dateString).match(/^[0-9]*$/)) {
dateString = Number(dateString);
}
if (String(dateString).match(/\-/)) {
dateString = String(dateString).replace(/\-/g, "/");
}
return new Date(dateString);
} else {
throw new Error("Couldn't cast `" + dateString + "` to a date object.");
}
}
var DIRECTIVE_KEY_MAP = {
Y: "years",
m: "months",
n: "daysToMonth",
w: "weeks",
d: "daysToWeek",
D: "totalDays",
H: "hours",
M: "minutes",
S: "seconds"
};
function escapedRegExp(str) {
var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
return new RegExp(sanitize);
}
function strftime(offsetObject) {
return function (format) {
var directives = format.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);
if (directives) {
for (var i = 0, len = directives.length; i < len; ++i) {
var directive = directives[i].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/), regexp = escapedRegExp(directive[0]), modifier = directive[1] || "", plural = directive[3] || "", value = null;
directive = directive[2];
if (DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) {
value = DIRECTIVE_KEY_MAP[directive];
value = Number(offsetObject[value]);
}
if (value !== null) {
if (modifier === "!") {
value = pluralize(plural, value);
}
if (modifier === "") {
if (value < 10) {
value = "0" + value.toString();
}
}
format = format.replace(regexp, value.toString());
}
}
}
format = format.replace(/%%/, "%");
return format;
};
}
function pluralize(format, count) {
var plural = "s", singular = "";
if (format) {
format = format.replace(/(:|;|\s)/gi, "").split(/\,/);
if (format.length === 1) {
plural = format[0];
} else {
singular = format[0];
plural = format[1];
}
}
if (Math.abs(count) === 1) {
return singular;
} else {
return plural;
}
}
var Countdown = function (el, finalDate, options) {
this.el = el;
this.$el = $(el);
this.interval = null;
this.offset = {};
this.options = $.extend({}, defaultOptions);
this.instanceNumber = instances.length;
instances.push(this);
this.$el.data("countdown-instance", this.instanceNumber);
if (options) {
if (typeof options === "function") {
this.$el.on("update.countdown", options);
this.$el.on("stoped.countdown", options);
this.$el.on("finish.countdown", options);
} else {
this.options = $.extend({}, defaultOptions, options);
}
}
this.setFinalDate(finalDate);
this.start();
};
$.extend(Countdown.prototype, {
start: function () {
if (this.interval !== null) {
clearInterval(this.interval);
}
var self = this;
this.update();
this.interval = setInterval(function () {
self.update.call(self);
}, this.options.precision);
},
stop: function () {
clearInterval(this.interval);
this.interval = null;
this.dispatchEvent("stoped");
},
toggle: function () {
if (this.interval) {
this.stop();
} else {
this.start();
}
},
pause: function () {
this.stop();
},
resume: function () {
this.start();
},
remove: function () {
this.stop.call(this);
instances[this.instanceNumber] = null;
delete this.$el.data().countdownInstance;
},
setFinalDate: function (value) {
this.finalDate = parseDateString(value);
},
update: function () {
if (this.$el.closest("html").length === 0) {
this.remove();
return;
}
var hasEventsAttached = $._data(this.el, "events") !== undefined, now = new Date(), newTotalSecsLeft;
newTotalSecsLeft = this.finalDate.getTime() - now.getTime();
newTotalSecsLeft = Math.ceil(newTotalSecsLeft / 1e3);
newTotalSecsLeft = !this.options.elapse && newTotalSecsLeft < 0 ? 0 : Math.abs(newTotalSecsLeft);
if (this.totalSecsLeft === newTotalSecsLeft || !hasEventsAttached) {
return;
} else {
this.totalSecsLeft = newTotalSecsLeft;
}
this.elapsed = now >= this.finalDate;
this.offset = {
seconds: this.totalSecsLeft % 60,
minutes: Math.floor(this.totalSecsLeft / 60) % 60,
hours: Math.floor(this.totalSecsLeft / 60 / 60) % 24,
days: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
daysToWeek: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
daysToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 % 30.4368),
totalDays: Math.floor(this.totalSecsLeft / 60 / 60 / 24),
weeks: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7),
months: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30.4368),
years: Math.abs(this.finalDate.getFullYear() - now.getFullYear())
};
if (!this.options.elapse && this.totalSecsLeft === 0) {
this.stop();
this.dispatchEvent("finish");
} else {
this.dispatchEvent("update");
}
},
dispatchEvent: function (eventName) {
var event = $.Event(eventName + ".countdown");
event.finalDate = this.finalDate;
event.elapsed = this.elapsed;
event.offset = $.extend({}, this.offset);
event.strftime = strftime(this.offset);
this.$el.trigger(event);
}
});
$.fn.countdown = function () {
var argumentsArray = Array.prototype.slice.call(arguments, 0);
return this.each(function () {
var instanceNumber = $(this).data("countdown-instance");
if (instanceNumber !== undefined) {
var instance = instances[instanceNumber], method = argumentsArray[0];
if (Countdown.prototype.hasOwnProperty(method)) {
instance[method].apply(instance, argumentsArray.slice(1));
} else if (String(method).match(/^[$A-Z_][0-9A-Z_$]*$/i) === null) {
instance.setFinalDate.call(instance, method);
instance.start();
} else {
$.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi, method));
}
} else {
new Countdown(this, argumentsArray[0], argumentsArray[1]);
}
});
};
});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.dayjs=e()}(this,function(){"use strict";var t="millisecond",e="second",n="minute",r="hour",i="day",s="week",u="month",a="quarter",o="year",f="date",h=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d+)?$/,c=/\[([^\]]+)]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,d={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},$=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},l={s:$,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+$(r,2,"0")+":"+$(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,u),s=n-i<0,a=e.clone().add(r+(s?-1:1),u);return+(-(r+(n-i)/(s?i-a:a-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(h){return{M:u,y:o,w:s,d:i,D:f,h:r,m:n,s:e,ms:t,Q:a}[h]||String(h||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},y="en",M={};M[y]=d;var m=function(t){return t instanceof S},D=function(t,e,n){var r;if(!t)return y;if("string"==typeof t)M[t]&&(r=t),e&&(M[t]=e,r=t);else{var i=t.name;M[i]=t,r=i}return!n&&r&&(y=r),r||!n&&y},v=function(t,e){if(m(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new S(n)},g=l;g.l=D,g.i=m,g.w=function(t,e){return v(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var S=function(){function d(t){this.$L=D(t.locale,null,!0),this.parse(t)}var $=d.prototype;return $.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(g.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(h);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},$.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},$.$utils=function(){return g},$.isValid=function(){return!("Invalid Date"===this.$d.toString())},$.isSame=function(t,e){var n=v(t);return this.startOf(e)<=n&&n<=this.endOf(e)},$.isAfter=function(t,e){return v(t)<this.startOf(e)},$.isBefore=function(t,e){return this.endOf(e)<v(t)},$.$g=function(t,e,n){return g.u(t)?this[e]:this.set(n,t)},$.unix=function(){return Math.floor(this.valueOf()/1e3)},$.valueOf=function(){return this.$d.getTime()},$.startOf=function(t,a){var h=this,c=!!g.u(a)||a,d=g.p(t),$=function(t,e){var n=g.w(h.$u?Date.UTC(h.$y,e,t):new Date(h.$y,e,t),h);return c?n:n.endOf(i)},l=function(t,e){return g.w(h.toDate()[t].apply(h.toDate("s"),(c?[0,0,0,0]:[23,59,59,999]).slice(e)),h)},y=this.$W,M=this.$M,m=this.$D,D="set"+(this.$u?"UTC":"");switch(d){case o:return c?$(1,0):$(31,11);case u:return c?$(1,M):$(0,M+1);case s:var v=this.$locale().weekStart||0,S=(y<v?y+7:y)-v;return $(c?m-S:m+(6-S),M);case i:case f:return l(D+"Hours",0);case r:return l(D+"Minutes",1);case n:return l(D+"Seconds",2);case e:return l(D+"Milliseconds",3);default:return this.clone()}},$.endOf=function(t){return this.startOf(t,!1)},$.$set=function(s,a){var h,c=g.p(s),d="set"+(this.$u?"UTC":""),$=(h={},h[i]=d+"Date",h[f]=d+"Date",h[u]=d+"Month",h[o]=d+"FullYear",h[r]=d+"Hours",h[n]=d+"Minutes",h[e]=d+"Seconds",h[t]=d+"Milliseconds",h)[c],l=c===i?this.$D+(a-this.$W):a;if(c===u||c===o){var y=this.clone().set(f,1);y.$d[$](l),y.init(),this.$d=y.set(f,Math.min(this.$D,y.daysInMonth())).$d}else $&&this.$d[$](l);return this.init(),this},$.set=function(t,e){return this.clone().$set(t,e)},$.get=function(t){return this[g.p(t)]()},$.add=function(t,a){var f,h=this;t=Number(t);var c=g.p(a),d=function(e){var n=v(h);return g.w(n.date(n.date()+Math.round(e*t)),h)};if(c===u)return this.set(u,this.$M+t);if(c===o)return this.set(o,this.$y+t);if(c===i)return d(1);if(c===s)return d(7);var $=(f={},f[n]=6e4,f[r]=36e5,f[e]=1e3,f)[c]||1,l=this.$d.getTime()+t*$;return g.w(l,this)},$.subtract=function(t,e){return this.add(-1*t,e)},$.format=function(t){var e=this;if(!this.isValid())return"Invalid Date";var n=t||"YYYY-MM-DDTHH:mm:ssZ",r=g.z(this),i=this.$locale(),s=this.$H,u=this.$m,a=this.$M,o=i.weekdays,f=i.months,h=function(t,r,i,s){return t&&(t[r]||t(e,n))||i[r].substr(0,s)},d=function(t){return g.s(s%12||12,t,"0")},$=i.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},l={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:g.s(a+1,2,"0"),MMM:h(i.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:g.s(this.$D,2,"0"),d:String(this.$W),dd:h(i.weekdaysMin,this.$W,o,2),ddd:h(i.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:g.s(s,2,"0"),h:d(1),hh:d(2),a:$(s,u,!0),A:$(s,u,!1),m:String(u),mm:g.s(u,2,"0"),s:String(this.$s),ss:g.s(this.$s,2,"0"),SSS:g.s(this.$ms,3,"0"),Z:r};return n.replace(c,function(t,e){return e||l[t]||r.replace(":","")})},$.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},$.diff=function(t,f,h){var c,d=g.p(f),$=v(t),l=6e4*($.utcOffset()-this.utcOffset()),y=this-$,M=g.m(this,$);return M=(c={},c[o]=M/12,c[u]=M,c[a]=M/3,c[s]=(y-l)/6048e5,c[i]=(y-l)/864e5,c[r]=y/36e5,c[n]=y/6e4,c[e]=y/1e3,c)[d]||y,h?M:g.a(M)},$.daysInMonth=function(){return this.endOf(u).$D},$.$locale=function(){return M[this.$L]},$.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=D(t,e,!0);return r&&(n.$L=r),n},$.clone=function(){return g.w(this.$d,this)},$.toDate=function(){return new Date(this.valueOf())},$.toJSON=function(){return this.isValid()?this.toISOString():null},$.toISOString=function(){return this.$d.toISOString()},$.toString=function(){return this.$d.toUTCString()},d}(),p=S.prototype;return v.prototype=p,[["$ms",t],["$s",e],["$m",n],["$H",r],["$W",i],["$M",u],["$y",o],["$D",f]].forEach(function(t){p[t[1]]=function(e){return this.$g(e,t[0],t[1])}}),v.extend=function(t,e){return t(e,S,v),v},v.locale=D,v.isDayjs=m,v.unix=function(t){return v(1e3*t)},v.en=M[y],v.Ls=M,v.p={},v});
!function(a,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((a=a||self).timezoneSupport={})}(this,function(a){"use strict";function l(a){return 96<a?a-87:64<a?a-29:a-48}function r(a){var i=a.split("."),e=i[0],r=i[1]||"",A=1,c=0,o=0,n=1;45===a.charCodeAt(0)&&(n=-(c=1));for(var t=c,s=e.length;t<s;++t){o=60*o+l(e.charCodeAt(t))}for(var u=0,m=r.length;u<m;++u){o+=l(r.charCodeAt(u))*(A/=60)}return o*n}function t(a){for(var i=0,e=a.length;i<e;++i)a[i]=r(a[i])}function s(a,i){for(var e=[],r=0,A=i.length;r<A;++r)e[r]=a[i[r]];return e}function A(a){var i=a.split("|"),e=i[2].split(" "),r=i[3].split(""),A=i[4].split(" ");t(e),t(r),t(A),function(a,i){for(var e=0;e<i;++e)a[e]=Math.round((a[e-1]||0)+6e4*a[e]);a[i-1]=1/0}(A,r.length);var c=i[0],o=s(i[1].split(" "),r),n=0|i[5];return{name:c,abbreviations:o,offsets:e=s(e,r),untils:A,population:n}}var c,i,o,n;function d(a){var i=a.year,e=a.month,r=a.day,A=a.hours,c=void 0===A?0:A,o=a.minutes,n=void 0===o?0:o,t=a.seconds,s=void 0===t?0:t,u=a.milliseconds,m=void 0===u?0:u;return Date.UTC(i,e-1,r,c,n,s,m)}function E(a){return{year:a.getUTCFullYear(),month:a.getUTCMonth()+1,day:a.getUTCDate(),dayOfWeek:a.getUTCDay(),hours:a.getUTCHours(),minutes:a.getUTCMinutes(),seconds:a.getUTCSeconds()||0,milliseconds:a.getUTCMilliseconds()||0}}function h(a){return{year:a.getFullYear(),month:a.getMonth()+1,day:a.getDate(),dayOfWeek:a.getDay(),hours:a.getHours(),minutes:a.getMinutes(),seconds:a.getSeconds()||0,milliseconds:a.getMilliseconds()||0}}function T(a,i){var e=function(a,i){for(var e=i.untils,r=0,A=e.length;r<A;++r)if(a<e[r])return r}(a,i);return{abbreviation:i.abbreviations[e],offset:i.offsets[e]}}function z(a,i){Object.defineProperty(a,"epoch",{value:i})}var e,u,m;u=(e={version:"2019a",zones:["Africa/Abidjan|GMT|0|0||48e5","Africa/Nairobi|EAT|-30|0||47e5","Africa/Algiers|CET|-10|0||26e5","Africa/Lagos|WAT|-10|0||17e6","Africa/Maputo|CAT|-20|0||26e5","Africa/Cairo|EET EEST|-20 -30|01010|1M2m0 gL0 e10 mn0|15e6","Africa/Casablanca|+00 +01|0 -10|010101010101010101010101010101010101|1H3C0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 28M0 e00 2600 e00 28M0 e00 2600 gM0|32e5","Europe/Paris|CET CEST|-10 -20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|11e6","Africa/Johannesburg|SAST|-20|0||84e5","Africa/Khartoum|EAT CAT|-30 -20|01|1Usl0|51e5","Africa/Sao_Tome|GMT WAT|0 -10|010|1UQN0 2q00","Africa/Tripoli|EET CET CEST|-20 -10 -20|0120|1IlA0 TA0 1o00|11e5","Africa/Windhoek|CAT WAT|-20 -10|0101010101010|1GQo0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0|32e4","America/Adak|HST HDT|a0 90|01010101010101010101010|1GIc0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|326","America/Anchorage|AKST AKDT|90 80|01010101010101010101010|1GIb0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|30e4","America/Santo_Domingo|AST|40|0||29e5","America/Araguaina|-03 -02|30 20|010|1IdD0 Lz0|14e4","America/Fortaleza|-03|30|0||34e5","America/Asuncion|-03 -04|30 40|01010101010101010101010|1GTf0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0|28e5","America/Panama|EST|50|0||15e5","America/Mexico_City|CST CDT|60 50|01010101010101010101010|1GQw0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0|20e6","America/Bahia|-02 -03|20 30|01|1GCq0|27e5","America/Managua|CST|60|0||22e5","America/La_Paz|-04|40|0||19e5","America/Lima|-05|50|0||11e6","America/Denver|MST MDT|70 60|01010101010101010101010|1GI90 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|26e5","America/Campo_Grande|-03 -04|30 40|01010101010101010101010|1GCr0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1HB0 FX0 1HB0 FX0 1HB0 IL0 1HB0 FX0 1HB0|77e4","America/Cancun|CST CDT EST|60 50 50|01010102|1GQw0 1nX0 14p0 1lb0 14p0 1lb0 Dd0|63e4","America/Caracas|-0430 -04|4u 40|01|1QMT0|29e5","America/Chicago|CST CDT|60 50|01010101010101010101010|1GI80 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|92e5","America/Chihuahua|MST MDT|70 60|01010101010101010101010|1GQx0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0|81e4","America/Phoenix|MST|70|0||42e5","America/Los_Angeles|PST PDT|80 70|01010101010101010101010|1GIa0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|15e6","America/New_York|EST EDT|50 40|01010101010101010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|21e6","America/Rio_Branco|-04 -05|40 50|01|1KLE0|31e4","America/Fort_Nelson|PST PDT MST|80 70 70|01010102|1GIa0 1zb0 Op0 1zb0 Op0 1zb0 Op0|39e2","America/Halifax|AST ADT|40 30|01010101010101010101010|1GI60 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|39e4","America/Godthab|-03 -02|30 20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|17e3","America/Grand_Turk|EST EDT AST|50 40 40|0101010121010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 5Ip0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|37e2","America/Havana|CST CDT|50 40|01010101010101010101010|1GQt0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0|21e5","America/Metlakatla|PST AKST AKDT|80 90 80|01212120121212121|1PAa0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 uM0 jB0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|14e2","America/Miquelon|-03 -02|30 20|01010101010101010101010|1GI50 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|61e2","America/Montevideo|-02 -03|20 30|01010101|1GI40 1o10 11z0 1o10 11z0 1o10 11z0|17e5","America/Noronha|-02|20|0||30e2","America/Port-au-Prince|EST EDT|50 40|010101010101010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 3iN0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|23e5","Antarctica/Palmer|-03 -04|30 40|010101010|1H3D0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0|40","America/Santiago|-03 -04|30 40|010101010101010101010|1H3D0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1zb0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0|62e5","America/Sao_Paulo|-02 -03|20 30|01010101010101010101010|1GCq0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1HB0 FX0 1HB0 FX0 1HB0 IL0 1HB0 FX0 1HB0|20e6","Atlantic/Azores|-01 +00|10 0|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|25e4","America/St_Johns|NST NDT|3u 2u|01010101010101010101010|1GI5u 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|11e4","Antarctica/Casey|+11 +08|-b0 -80|0101|1GAF0 blz0 3m10|10","Antarctica/Davis|+05 +07|-50 -70|01|1GAI0|70","Pacific/Port_Moresby|+10|-a0|0||25e4","Pacific/Guadalcanal|+11|-b0|0||11e4","Asia/Tashkent|+05|-50|0||23e5","Pacific/Auckland|NZDT NZST|-d0 -c0|01010101010101010101010|1GQe0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00|14e5","Asia/Baghdad|+03|-30|0||66e5","Antarctica/Troll|+00 +02|0 -20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|40","Asia/Dhaka|+06|-60|0||16e6","Asia/Amman|EET EEST|-20 -30|010101010101010101010|1GPy0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00|25e5","Asia/Kamchatka|+12|-c0|0||18e4","Asia/Baku|+04 +05|-40 -50|010101010|1GNA0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00|27e5","Asia/Bangkok|+07|-70|0||15e6","Asia/Barnaul|+07 +06|-70 -60|010|1N7v0 3rd0","Asia/Beirut|EET EEST|-20 -30|01010101010101010101010|1GNy0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0|22e5","Asia/Kuala_Lumpur|+08|-80|0||71e5","Asia/Kolkata|IST|-5u|0||15e6","Asia/Chita|+10 +08 +09|-a0 -80 -90|012|1N7s0 3re0|33e4","Asia/Ulaanbaatar|+08 +09|-80 -90|01010|1O8G0 1cJ0 1cP0 1cJ0|12e5","Asia/Shanghai|CST|-80|0||23e6","Asia/Colombo|+0530|-5u|0||22e5","Asia/Damascus|EET EEST|-20 -30|01010101010101010101010|1GPy0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0|26e5","Asia/Dili|+09|-90|0||19e4","Asia/Dubai|+04|-40|0||39e5","Asia/Famagusta|EET EEST +03|-20 -30 -30|0101010101201010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 15U0 2Ks0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0","Asia/Gaza|EET EEST|-20 -30|01010101010101010101010|1GPy0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0|18e5","Asia/Hong_Kong|HKT|-80|0||73e5","Asia/Hovd|+07 +08|-70 -80|01010|1O8H0 1cJ0 1cP0 1cJ0|81e3","Asia/Irkutsk|+09 +08|-90 -80|01|1N7t0|60e4","Europe/Istanbul|EET EEST +03|-20 -30 -30|01010101012|1GNB0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 15w0|13e6","Asia/Jakarta|WIB|-70|0||31e6","Asia/Jayapura|WIT|-90|0||26e4","Asia/Jerusalem|IST IDT|-20 -30|01010101010101010101010|1GPA0 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0|81e4","Asia/Kabul|+0430|-4u|0||46e5","Asia/Karachi|PKT|-50|0||24e6","Asia/Kathmandu|+0545|-5J|0||12e5","Asia/Yakutsk|+10 +09|-a0 -90|01|1N7s0|28e4","Asia/Krasnoyarsk|+08 +07|-80 -70|01|1N7u0|10e5","Asia/Magadan|+12 +10 +11|-c0 -a0 -b0|012|1N7q0 3Cq0|95e3","Asia/Makassar|WITA|-80|0||15e5","Asia/Manila|PST|-80|0||24e6","Europe/Athens|EET EEST|-20 -30|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|35e5","Asia/Novosibirsk|+07 +06|-70 -60|010|1N7v0 4eN0|15e5","Asia/Omsk|+07 +06|-70 -60|01|1N7v0|12e5","Asia/Pyongyang|KST KST|-90 -8u|010|1P4D0 6BA0|29e5","Asia/Qyzylorda|+06 +05|-60 -50|01|1Xei0|73e4","Asia/Rangoon|+0630|-6u|0||48e5","Asia/Sakhalin|+11 +10|-b0 -a0|010|1N7r0 3rd0|58e4","Asia/Seoul|KST|-90|0||23e6","Asia/Srednekolymsk|+12 +11|-c0 -b0|01|1N7q0|35e2","Asia/Tehran|+0330 +0430|-3u -4u|01010101010101010101010|1GLUu 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0|14e6","Asia/Tokyo|JST|-90|0||38e6","Asia/Tomsk|+07 +06|-70 -60|010|1N7v0 3Qp0|10e5","Asia/Vladivostok|+11 +10|-b0 -a0|01|1N7r0|60e4","Asia/Yekaterinburg|+06 +05|-60 -50|01|1N7w0|14e5","Europe/Lisbon|WET WEST|0 -10|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|27e5","Atlantic/Cape_Verde|-01|10|0||50e4","Australia/Sydney|AEDT AEST|-b0 -a0|01010101010101010101010|1GQg0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0|40e5","Australia/Adelaide|ACDT ACST|-au -9u|01010101010101010101010|1GQgu 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0|11e5","Australia/Brisbane|AEST|-a0|0||20e5","Australia/Darwin|ACST|-9u|0||12e4","Australia/Eucla|+0845|-8J|0||368","Australia/Lord_Howe|+11 +1030|-b0 -au|01010101010101010101010|1GQf0 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu|347","Australia/Perth|AWST|-80|0||18e5","Pacific/Easter|-05 -06|50 60|010101010101010101010|1H3D0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1zb0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0|30e2","Europe/Dublin|GMT IST|0 -10|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|12e5","Etc/GMT-1|+01|-10|0|","Pacific/Fakaofo|+13|-d0|0||483","Pacific/Kiritimati|+14|-e0|0||51e2","Etc/GMT-2|+02|-20|0|","Pacific/Tahiti|-10|a0|0||18e4","Pacific/Niue|-11|b0|0||12e2","Etc/GMT+12|-12|c0|0|","Pacific/Galapagos|-06|60|0||25e3","Etc/GMT+7|-07|70|0|","Pacific/Pitcairn|-08|80|0||56","Pacific/Gambier|-09|90|0||125","Etc/UTC|UTC|0|0|","Europe/Ulyanovsk|+04 +03|-40 -30|010|1N7y0 3rd0|13e5","Europe/London|GMT BST|0 -10|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|10e6","Europe/Chisinau|EET EEST|-20 -30|01010101010101010101010|1GNA0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|67e4","Europe/Kaliningrad|+03 EET|-30 -20|01|1N7z0|44e4","Europe/Kirov|+04 +03|-40 -30|01|1N7y0|48e4","Europe/Moscow|MSK MSK|-40 -30|01|1N7y0|16e6","Europe/Saratov|+04 +03|-40 -30|010|1N7y0 5810","Europe/Simferopol|EET EEST MSK MSK|-20 -30 -40 -30|0101023|1GNB0 1qM0 11A0 1o00 11z0 1nW0|33e4","Europe/Volgograd|+04 +03|-40 -30|010|1N7y0 9Jd0|10e5","Pacific/Honolulu|HST|a0|0||37e4","MET|MET MEST|-10 -20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0","Pacific/Chatham|+1345 +1245|-dJ -cJ|01010101010101010101010|1GQe0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00|600","Pacific/Apia|+14 +13|-e0 -d0|01010101010101010101010|1GQe0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00|37e3","Pacific/Bougainville|+10 +11|-a0 -b0|01|1NwE0|18e4","Pacific/Fiji|+13 +12|-d0 -c0|01010101010101010101010|1Goe0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1VA0 s00 1VA0|88e4","Pacific/Guam|ChST|-a0|0||17e4","Pacific/Marquesas|-0930|9u|0||86e2","Pacific/Pago_Pago|SST|b0|0||37e2","Pacific/Norfolk|+1130 +11|-bu -b0|01|1PoCu|25e4","Pacific/Tongatapu|+13 +14|-d0 -e0|010|1S4d0 s00|75e3"],links:["Africa/Abidjan|Africa/Accra","Africa/Abidjan|Africa/Bamako","Africa/Abidjan|Africa/Banjul","Africa/Abidjan|Africa/Bissau","Africa/Abidjan|Africa/Conakry","Africa/Abidjan|Africa/Dakar","Africa/Abidjan|Africa/Freetown","Africa/Abidjan|Africa/Lome","Africa/Abidjan|Africa/Monrovia","Africa/Abidjan|Africa/Nouakchott","Africa/Abidjan|Africa/Ouagadougou","Africa/Abidjan|Africa/Timbuktu","Africa/Abidjan|America/Danmarkshavn","Africa/Abidjan|Atlantic/Reykjavik","Africa/Abidjan|Atlantic/St_Helena","Africa/Abidjan|Etc/GMT","Africa/Abidjan|Etc/GMT+0","Africa/Abidjan|Etc/GMT-0","Africa/Abidjan|Etc/GMT0","Africa/Abidjan|Etc/Greenwich","Africa/Abidjan|GMT","Africa/Abidjan|GMT+0","Africa/Abidjan|GMT-0","Africa/Abidjan|GMT0","Africa/Abidjan|Greenwich","Africa/Abidjan|Iceland","Africa/Algiers|Africa/Tunis","Africa/Cairo|Egypt","Africa/Casablanca|Africa/El_Aaiun","Africa/Johannesburg|Africa/Maseru","Africa/Johannesburg|Africa/Mbabane","Africa/Lagos|Africa/Bangui","Africa/Lagos|Africa/Brazzaville","Africa/Lagos|Africa/Douala","Africa/Lagos|Africa/Kinshasa","Africa/Lagos|Africa/Libreville","Africa/Lagos|Africa/Luanda","Africa/Lagos|Africa/Malabo","Africa/Lagos|Africa/Ndjamena","Africa/Lagos|Africa/Niamey","Africa/Lagos|Africa/Porto-Novo","Africa/Maputo|Africa/Blantyre","Africa/Maputo|Africa/Bujumbura","Africa/Maputo|Africa/Gaborone","Africa/Maputo|Africa/Harare","Africa/Maputo|Africa/Kigali","Africa/Maputo|Africa/Lubumbashi","Africa/Maputo|Africa/Lusaka","Africa/Nairobi|Africa/Addis_Ababa","Africa/Nairobi|Africa/Asmara","Africa/Nairobi|Africa/Asmera","Africa/Nairobi|Africa/Dar_es_Salaam","Africa/Nairobi|Africa/Djibouti","Africa/Nairobi|Africa/Juba","Africa/Nairobi|Africa/Kampala","Africa/Nairobi|Africa/Mogadishu","Africa/Nairobi|Indian/Antananarivo","Africa/Nairobi|Indian/Comoro","Africa/Nairobi|Indian/Mayotte","Africa/Tripoli|Libya","America/Adak|America/Atka","America/Adak|US/Aleutian","America/Anchorage|America/Juneau","America/Anchorage|America/Nome","America/Anchorage|America/Sitka","America/Anchorage|America/Yakutat","America/Anchorage|US/Alaska","America/Campo_Grande|America/Cuiaba","America/Chicago|America/Indiana/Knox","America/Chicago|America/Indiana/Tell_City","America/Chicago|America/Knox_IN","America/Chicago|America/Matamoros","America/Chicago|America/Menominee","America/Chicago|America/North_Dakota/Beulah","America/Chicago|America/North_Dakota/Center","America/Chicago|America/North_Dakota/New_Salem","America/Chicago|America/Rainy_River","America/Chicago|America/Rankin_Inlet","America/Chicago|America/Resolute","America/Chicago|America/Winnipeg","America/Chicago|CST6CDT","America/Chicago|Canada/Central","America/Chicago|US/Central","America/Chicago|US/Indiana-Starke","America/Chihuahua|America/Mazatlan","America/Chihuahua|Mexico/BajaSur","America/Denver|America/Boise","America/Denver|America/Cambridge_Bay","America/Denver|America/Edmonton","America/Denver|America/Inuvik","America/Denver|America/Ojinaga","America/Denver|America/Shiprock","America/Denver|America/Yellowknife","America/Denver|Canada/Mountain","America/Denver|MST7MDT","America/Denver|Navajo","America/Denver|US/Mountain","America/Fortaleza|America/Argentina/Buenos_Aires","America/Fortaleza|America/Argentina/Catamarca","America/Fortaleza|America/Argentina/ComodRivadavia","America/Fortaleza|America/Argentina/Cordoba","America/Fortaleza|America/Argentina/Jujuy","America/Fortaleza|America/Argentina/La_Rioja","America/Fortaleza|America/Argentina/Mendoza","America/Fortaleza|America/Argentina/Rio_Gallegos","America/Fortaleza|America/Argentina/Salta","America/Fortaleza|America/Argentina/San_Juan","America/Fortaleza|America/Argentina/San_Luis","America/Fortaleza|America/Argentina/Tucuman","America/Fortaleza|America/Argentina/Ushuaia","America/Fortaleza|America/Belem","America/Fortaleza|America/Buenos_Aires","America/Fortaleza|America/Catamarca","America/Fortaleza|America/Cayenne","America/Fortaleza|America/Cordoba","America/Fortaleza|America/Jujuy","America/Fortaleza|America/Maceio","America/Fortaleza|America/Mendoza","America/Fortaleza|America/Paramaribo","America/Fortaleza|America/Recife","America/Fortaleza|America/Rosario","America/Fortaleza|America/Santarem","America/Fortaleza|Antarctica/Rothera","America/Fortaleza|Atlantic/Stanley","America/Fortaleza|Etc/GMT+3","America/Halifax|America/Glace_Bay","America/Halifax|America/Goose_Bay","America/Halifax|America/Moncton","America/Halifax|America/Thule","America/Halifax|Atlantic/Bermuda","America/Halifax|Canada/Atlantic","America/Havana|Cuba","America/La_Paz|America/Boa_Vista","America/La_Paz|America/Guyana","America/La_Paz|America/Manaus","America/La_Paz|America/Porto_Velho","America/La_Paz|Brazil/West","America/La_Paz|Etc/GMT+4","America/Lima|America/Bogota","America/Lima|America/Guayaquil","America/Lima|Etc/GMT+5","America/Los_Angeles|America/Dawson","America/Los_Angeles|America/Ensenada","America/Los_Angeles|America/Santa_Isabel","America/Los_Angeles|America/Tijuana","America/Los_Angeles|America/Vancouver","America/Los_Angeles|America/Whitehorse","America/Los_Angeles|Canada/Pacific","America/Los_Angeles|Canada/Yukon","America/Los_Angeles|Mexico/BajaNorte","America/Los_Angeles|PST8PDT","America/Los_Angeles|US/Pacific","America/Los_Angeles|US/Pacific-New","America/Managua|America/Belize","America/Managua|America/Costa_Rica","America/Managua|America/El_Salvador","America/Managua|America/Guatemala","America/Managua|America/Regina","America/Managua|America/Swift_Current","America/Managua|America/Tegucigalpa","America/Managua|Canada/Saskatchewan","America/Mexico_City|America/Bahia_Banderas","America/Mexico_City|America/Merida","America/Mexico_City|America/Monterrey","America/Mexico_City|Mexico/General","America/New_York|America/Detroit","America/New_York|America/Fort_Wayne","America/New_York|America/Indiana/Indianapolis","America/New_York|America/Indiana/Marengo","America/New_York|America/Indiana/Petersburg","America/New_York|America/Indiana/Vevay","America/New_York|America/Indiana/Vincennes","America/New_York|America/Indiana/Winamac","America/New_York|America/Indianapolis","America/New_York|America/Iqaluit","America/New_York|America/Kentucky/Louisville","America/New_York|America/Kentucky/Monticello","America/New_York|America/Louisville","America/New_York|America/Montreal","America/New_York|America/Nassau","America/New_York|America/Nipigon","America/New_York|America/Pangnirtung","America/New_York|America/Thunder_Bay","America/New_York|America/Toronto","America/New_York|Canada/Eastern","America/New_York|EST5EDT","America/New_York|US/East-Indiana","America/New_York|US/Eastern","America/New_York|US/Michigan","America/Noronha|Atlantic/South_Georgia","America/Noronha|Brazil/DeNoronha","America/Noronha|Etc/GMT+2","America/Panama|America/Atikokan","America/Panama|America/Cayman","America/Panama|America/Coral_Harbour","America/Panama|America/Jamaica","America/Panama|EST","America/Panama|Jamaica","America/Phoenix|America/Creston","America/Phoenix|America/Dawson_Creek","America/Phoenix|America/Hermosillo","America/Phoenix|MST","America/Phoenix|US/Arizona","America/Rio_Branco|America/Eirunepe","America/Rio_Branco|America/Porto_Acre","America/Rio_Branco|Brazil/Acre","America/Santiago|Chile/Continental","America/Santo_Domingo|America/Anguilla","America/Santo_Domingo|America/Antigua","America/Santo_Domingo|America/Aruba","America/Santo_Domingo|America/Barbados","America/Santo_Domingo|America/Blanc-Sablon","America/Santo_Domingo|America/Curacao","America/Santo_Domingo|America/Dominica","America/Santo_Domingo|America/Grenada","America/Santo_Domingo|America/Guadeloupe","America/Santo_Domingo|America/Kralendijk","America/Santo_Domingo|America/Lower_Princes","America/Santo_Domingo|America/Marigot","America/Santo_Domingo|America/Martinique","America/Santo_Domingo|America/Montserrat","America/Santo_Domingo|America/Port_of_Spain","America/Santo_Domingo|America/Puerto_Rico","America/Santo_Domingo|America/St_Barthelemy","America/Santo_Domingo|America/St_Kitts","America/Santo_Domingo|America/St_Lucia","America/Santo_Domingo|America/St_Thomas","America/Santo_Domingo|America/St_Vincent","America/Santo_Domingo|America/Tortola","America/Santo_Domingo|America/Virgin","America/Sao_Paulo|Brazil/East","America/St_Johns|Canada/Newfoundland","Antarctica/Palmer|America/Punta_Arenas","Asia/Baghdad|Antarctica/Syowa","Asia/Baghdad|Asia/Aden","Asia/Baghdad|Asia/Bahrain","Asia/Baghdad|Asia/Kuwait","Asia/Baghdad|Asia/Qatar","Asia/Baghdad|Asia/Riyadh","Asia/Baghdad|Etc/GMT-3","Asia/Baghdad|Europe/Minsk","Asia/Bangkok|Asia/Ho_Chi_Minh","Asia/Bangkok|Asia/Novokuznetsk","Asia/Bangkok|Asia/Phnom_Penh","Asia/Bangkok|Asia/Saigon","Asia/Bangkok|Asia/Vientiane","Asia/Bangkok|Etc/GMT-7","Asia/Bangkok|Indian/Christmas","Asia/Dhaka|Antarctica/Vostok","Asia/Dhaka|Asia/Almaty","Asia/Dhaka|Asia/Bishkek","Asia/Dhaka|Asia/Dacca","Asia/Dhaka|Asia/Kashgar","Asia/Dhaka|Asia/Qostanay","Asia/Dhaka|Asia/Thimbu","Asia/Dhaka|Asia/Thimphu","Asia/Dhaka|Asia/Urumqi","Asia/Dhaka|Etc/GMT-6","Asia/Dhaka|Indian/Chagos","Asia/Dili|Etc/GMT-9","Asia/Dili|Pacific/Palau","Asia/Dubai|Asia/Muscat","Asia/Dubai|Asia/Tbilisi","Asia/Dubai|Asia/Yerevan","Asia/Dubai|Etc/GMT-4","Asia/Dubai|Europe/Samara","Asia/Dubai|Indian/Mahe","Asia/Dubai|Indian/Mauritius","Asia/Dubai|Indian/Reunion","Asia/Gaza|Asia/Hebron","Asia/Hong_Kong|Hongkong","Asia/Jakarta|Asia/Pontianak","Asia/Jerusalem|Asia/Tel_Aviv","Asia/Jerusalem|Israel","Asia/Kamchatka|Asia/Anadyr","Asia/Kamchatka|Etc/GMT-12","Asia/Kamchatka|Kwajalein","Asia/Kamchatka|Pacific/Funafuti","Asia/Kamchatka|Pacific/Kwajalein","Asia/Kamchatka|Pacific/Majuro","Asia/Kamchatka|Pacific/Nauru","Asia/Kamchatka|Pacific/Tarawa","Asia/Kamchatka|Pacific/Wake","Asia/Kamchatka|Pacific/Wallis","Asia/Kathmandu|Asia/Katmandu","Asia/Kolkata|Asia/Calcutta","Asia/Kuala_Lumpur|Asia/Brunei","Asia/Kuala_Lumpur|Asia/Kuching","Asia/Kuala_Lumpur|Asia/Singapore","Asia/Kuala_Lumpur|Etc/GMT-8","Asia/Kuala_Lumpur|Singapore","Asia/Makassar|Asia/Ujung_Pandang","Asia/Rangoon|Asia/Yangon","Asia/Rangoon|Indian/Cocos","Asia/Seoul|ROK","Asia/Shanghai|Asia/Chongqing","Asia/Shanghai|Asia/Chungking","Asia/Shanghai|Asia/Harbin","Asia/Shanghai|Asia/Macao","Asia/Shanghai|Asia/Macau","Asia/Shanghai|Asia/Taipei","Asia/Shanghai|PRC","Asia/Shanghai|ROC","Asia/Tashkent|Antarctica/Mawson","Asia/Tashkent|Asia/Aqtau","Asia/Tashkent|Asia/Aqtobe","Asia/Tashkent|Asia/Ashgabat","Asia/Tashkent|Asia/Ashkhabad","Asia/Tashkent|Asia/Atyrau","Asia/Tashkent|Asia/Dushanbe","Asia/Tashkent|Asia/Oral","Asia/Tashkent|Asia/Samarkand","Asia/Tashkent|Etc/GMT-5","Asia/Tashkent|Indian/Kerguelen","Asia/Tashkent|Indian/Maldives","Asia/Tehran|Iran","Asia/Tokyo|Japan","Asia/Ulaanbaatar|Asia/Choibalsan","Asia/Ulaanbaatar|Asia/Ulan_Bator","Asia/Vladivostok|Asia/Ust-Nera","Asia/Yakutsk|Asia/Khandyga","Atlantic/Azores|America/Scoresbysund","Atlantic/Cape_Verde|Etc/GMT+1","Australia/Adelaide|Australia/Broken_Hill","Australia/Adelaide|Australia/South","Australia/Adelaide|Australia/Yancowinna","Australia/Brisbane|Australia/Lindeman","Australia/Brisbane|Australia/Queensland","Australia/Darwin|Australia/North","Australia/Lord_Howe|Australia/LHI","Australia/Perth|Australia/West","Australia/Sydney|Australia/ACT","Australia/Sydney|Australia/Canberra","Australia/Sydney|Australia/Currie","Australia/Sydney|Australia/Hobart","Australia/Sydney|Australia/Melbourne","Australia/Sydney|Australia/NSW","Australia/Sydney|Australia/Tasmania","Australia/Sydney|Australia/Victoria","Etc/UTC|Etc/UCT","Etc/UTC|Etc/Universal","Etc/UTC|Etc/Zulu","Etc/UTC|UCT","Etc/UTC|UTC","Etc/UTC|Universal","Etc/UTC|Zulu","Europe/Athens|Asia/Nicosia","Europe/Athens|EET","Europe/Athens|Europe/Bucharest","Europe/Athens|Europe/Helsinki","Europe/Athens|Europe/Kiev","Europe/Athens|Europe/Mariehamn","Europe/Athens|Europe/Nicosia","Europe/Athens|Europe/Riga","Europe/Athens|Europe/Sofia","Europe/Athens|Europe/Tallinn","Europe/Athens|Europe/Uzhgorod","Europe/Athens|Europe/Vilnius","Europe/Athens|Europe/Zaporozhye","Europe/Chisinau|Europe/Tiraspol","Europe/Dublin|Eire","Europe/Istanbul|Asia/Istanbul","Europe/Istanbul|Turkey","Europe/Lisbon|Atlantic/Canary","Europe/Lisbon|Atlantic/Faeroe","Europe/Lisbon|Atlantic/Faroe","Europe/Lisbon|Atlantic/Madeira","Europe/Lisbon|Portugal","Europe/Lisbon|WET","Europe/London|Europe/Belfast","Europe/London|Europe/Guernsey","Europe/London|Europe/Isle_of_Man","Europe/London|Europe/Jersey","Europe/London|GB","Europe/London|GB-Eire","Europe/Moscow|W-SU","Europe/Paris|Africa/Ceuta","Europe/Paris|Arctic/Longyearbyen","Europe/Paris|Atlantic/Jan_Mayen","Europe/Paris|CET","Europe/Paris|Europe/Amsterdam","Europe/Paris|Europe/Andorra","Europe/Paris|Europe/Belgrade","Europe/Paris|Europe/Berlin","Europe/Paris|Europe/Bratislava","Europe/Paris|Europe/Brussels","Europe/Paris|Europe/Budapest","Europe/Paris|Europe/Busingen","Europe/Paris|Europe/Copenhagen","Europe/Paris|Europe/Gibraltar","Europe/Paris|Europe/Ljubljana","Europe/Paris|Europe/Luxembourg","Europe/Paris|Europe/Madrid","Europe/Paris|Europe/Malta","Europe/Paris|Europe/Monaco","Europe/Paris|Europe/Oslo","Europe/Paris|Europe/Podgorica","Europe/Paris|Europe/Prague","Europe/Paris|Europe/Rome","Europe/Paris|Europe/San_Marino","Europe/Paris|Europe/Sarajevo","Europe/Paris|Europe/Skopje","Europe/Paris|Europe/Stockholm","Europe/Paris|Europe/Tirane","Europe/Paris|Europe/Vaduz","Europe/Paris|Europe/Vatican","Europe/Paris|Europe/Vienna","Europe/Paris|Europe/Warsaw","Europe/Paris|Europe/Zagreb","Europe/Paris|Europe/Zurich","Europe/Paris|Poland","Europe/Ulyanovsk|Europe/Astrakhan","Pacific/Auckland|Antarctica/McMurdo","Pacific/Auckland|Antarctica/South_Pole","Pacific/Auckland|NZ","Pacific/Chatham|NZ-CHAT","Pacific/Easter|Chile/EasterIsland","Pacific/Fakaofo|Etc/GMT-13","Pacific/Fakaofo|Pacific/Enderbury","Pacific/Galapagos|Etc/GMT+6","Pacific/Gambier|Etc/GMT+9","Pacific/Guadalcanal|Antarctica/Macquarie","Pacific/Guadalcanal|Etc/GMT-11","Pacific/Guadalcanal|Pacific/Efate","Pacific/Guadalcanal|Pacific/Kosrae","Pacific/Guadalcanal|Pacific/Noumea","Pacific/Guadalcanal|Pacific/Pohnpei","Pacific/Guadalcanal|Pacific/Ponape","Pacific/Guam|Pacific/Saipan","Pacific/Honolulu|HST","Pacific/Honolulu|Pacific/Johnston","Pacific/Honolulu|US/Hawaii","Pacific/Kiritimati|Etc/GMT-14","Pacific/Niue|Etc/GMT+11","Pacific/Pago_Pago|Pacific/Midway","Pacific/Pago_Pago|Pacific/Samoa","Pacific/Pago_Pago|US/Samoa","Pacific/Pitcairn|Etc/GMT+8","Pacific/Port_Moresby|Antarctica/DumontDUrville","Pacific/Port_Moresby|Etc/GMT-10","Pacific/Port_Moresby|Pacific/Chuuk","Pacific/Port_Moresby|Pacific/Truk","Pacific/Port_Moresby|Pacific/Yap","Pacific/Tahiti|Etc/GMT+10","Pacific/Tahiti|Pacific/Rarotonga"]}).zones,m=e.links,c={},i=u.map(function(a){var i=a.substr(0,a.indexOf("|"));return c[i]=a,i}),o=m.reduce(function(a,i){var e=i.split("|"),r=e[0];return a[e[1]]=r,a},{}),n={},a.convertDateToTime=function(a){var i=h(a),e=/\(([^)]+)\)$/.exec(a.toTimeString());return i.zone={abbreviation:e?e[1]:"???",offset:a.getTimezoneOffset()},z(i,a.getTime()),i},a.convertTimeToDate=function(a){var i=a.epoch;if(void 0!==i)return new Date(i);var e=(a.zone||{}).offset;if(void 0===e)return function(a){var i=a.year,e=a.month,r=a.day,A=a.hours,c=void 0===A?0:A,o=a.minutes,n=void 0===o?0:o,t=a.seconds,s=void 0===t?0:t,u=a.milliseconds;return new Date(i,e-1,r,c,n,s,void 0===u?0:u)}(a);var r=d(a);return new Date(r+6e4*e)},a.findTimeZone=function(a){var i=o[a]||a,e=n[i];if(!e){var r=c[i];if(!r)throw new Error('Unknown time zone "'+i+'".');e=n[i]=A(r)}return e},a.getUTCOffset=function(a,i){var e=T("number"==typeof a?a:a.getTime(),i);return{abbreviation:e.abbreviation,offset:e.offset}},a.getUnixTime=function(a,i){var e=a.zone,r=a.epoch;if(r){if(i)throw new Error("Both epoch and other time zone specified. Omit the other one.");return r}var A=d(a);if(e){if(i)throw new Error("Both own and other time zones specified. Omit the other one.")}else{if(!i)throw new Error("Missing other time zone.");e=T(A,i)}return A+6e4*e.offset},a.getZonedTime=function(a,i){var e="number"==typeof a,r=e?a:a.getTime(),A=T(r,i),c=A.abbreviation,o=A.offset;(e||o)&&(a=new Date(r-6e4*o));var n=E(a);return n.zone={abbreviation:c,offset:o},z(n,r),n},a.listTimeZones=function(){return i.slice()},a.setTimeZone=function(a,i,e){if(a instanceof Date)a=function(a,i){var e,r=(i||{}).useUTC;if(!0===r)e=E;else{if(!1!==r)throw new Error("Extract local or UTC date? Set useUTC option.");e=h}return e(a)}(a,e);else{var r=a,A=r.year,c=r.month,o=r.day,n=r.hours,t=r.minutes,s=r.seconds,u=void 0===s?0:s,m=r.milliseconds;a={year:A,month:c,day:o,hours:n,minutes:t,seconds:u,milliseconds:void 0===m?0:m}}var l=d(a),f=new Date(l);a.dayOfWeek=f.getUTCDay();var p=T(l,i),M=p.abbreviation,b=p.offset;return a.zone={abbreviation:M,offset:b},z(a,l+6e4*b),a},Object.defineProperty(a,"__esModule",{value:!0})});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.dayjs_plugin_timezone=e()}(this,function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,u=o().utcOffset(),a=function(t,n,i){void 0===i&&(i={});var o=new Date(t);return function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i).formatToParts(o)},f=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],v=24===d?0:d,h=r[0]+"-"+r[1]+"-"+r[2]+" "+v+":"+r[4]+":"+r[5]+":000",l=+e;return(o.utc(h).valueOf()-(l-=l%1e3))/6e4},s=i.prototype;s.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate().toLocaleString("en-US",{timeZone:t}),a=Math.round((this.toDate()-new Date(i))/1e3/60),f=o(i).$set("millisecond",this.$ms).utcOffset(u-a,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},s.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find(function(t){return"timezonename"===t.type.toLowerCase()});return n&&n.value},o.tz=function(t,e,n){var i=n&&e,u=n||e||r,a=f(+o(),u);if("string"!=typeof t)return o(t).tz(u);var s=function(t,e,n){var i=t-60*e*1e3,o=f(i,n);if(e===o)return[i,e];var r=f(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),a,u),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=u,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}});
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):t.dayjs_plugin_utc=i()}(this,function(){"use strict";return function(t,i,e){var s=i.prototype;e.utc=function(t){return new i({date:t,utc:!0,args:arguments})},s.utc=function(t){var i=e(this.toDate(),{locale:this.$L,utc:!0});return t?i.add(this.utcOffset(),"minute"):i},s.local=function(){return e(this.toDate(),{locale:this.$L,utc:!1})};var f=s.parse;s.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),f.call(this,t)};var n=s.init;s.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else n.call(this)};var u=s.utcOffset;s.utcOffset=function(t,i){var e=this.$utils().u;if(e(t))return this.$u?0:e(this.$offset)?u.call(this):this.$offset;var s=Math.abs(t)<=16?60*t:t,f=this;if(i)return f.$offset=s,f.$u=0===t,f;if(0!==t){var n=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(f=this.local().add(s+n,"minute")).$offset=s,f.$x.$localOffset=n}else f=this.utc();return f};var o=s.format;s.format=function(t){var i=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return o.call(this,i)},s.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||(new Date).getTimezoneOffset());return this.$d.valueOf()-6e4*t},s.isUTC=function(){return!!this.$u},s.toISOString=function(){return this.toDate().toISOString()},s.toString=function(){return this.toDate().toUTCString()};var r=s.toDate;s.toDate=function(t){return"s"===t&&this.$offset?e(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():r.call(this)};var a=s.diff;s.diff=function(t,i,s){if(this.$u===t.$u)return a.call(this,t,i,s);var f=this.local(),n=e(t).local();return a.call(f,n,i,s)}}});
/**
* JavaScript Client Detection
* (C) viazenetti GmbH (Christian Ludwig)
*/
(function (window) {
{
var unknown = '-';
// screen
var screenSize = '';
if (screen.width) {
width = (screen.width) ? screen.width : '';
height = (screen.height) ? screen.height : '';
screenSize += '' + width + " x " + height;
}
// browser
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var browser = navigator.appName;
var version = '' + parseFloat(navigator.appVersion);
var majorVersion = parseInt(navigator.appVersion, 10);
var nameOffset, verOffset, ix;
// Opera
if ((verOffset = nAgt.indexOf('Opera')) != -1) {
browser = 'Opera';
version = nAgt.substring(verOffset + 6);
if ((verOffset = nAgt.indexOf('Version')) != -1) {
version = nAgt.substring(verOffset + 8);
}
}
// Opera Next
if ((verOffset = nAgt.indexOf('OPR')) != -1) {
browser = 'Opera';
version = nAgt.substring(verOffset + 4);
}
// Legacy Edge
else if ((verOffset = nAgt.indexOf('Edge')) != -1) {
browser = 'Edge';
version = nAgt.substring(verOffset + 5);
}
// Edge (Chromium)
else if ((verOffset = nAgt.indexOf('Edg')) != -1) {
browser = 'Microsoft Edge';
version = nAgt.substring(verOffset + 4);
}
// MSIE
else if ((verOffset = nAgt.indexOf('MSIE')) != -1) {
browser = 'Internet';
version = nAgt.substring(verOffset + 5);
}
// Chrome
else if ((verOffset = nAgt.indexOf('Chrome')) != -1) {
browser = 'Chrome';
version = nAgt.substring(verOffset + 7);
}
// Safari
else if ((verOffset = nAgt.indexOf('Safari')) != -1) {
browser = 'Safari';
version = nAgt.substring(verOffset + 7);
if ((verOffset = nAgt.indexOf('Version')) != -1) {
version = nAgt.substring(verOffset + 8);
}
}
// Firefox
else if ((verOffset = nAgt.indexOf('Firefox')) != -1) {
browser = 'Firefox';
version = nAgt.substring(verOffset + 8);
}
// MSIE 11+
else if (nAgt.indexOf('Trident/') != -1) {
browser = 'Internet';
version = nAgt.substring(nAgt.indexOf('rv:') + 3);
}
// Other browsers
else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
browser = nAgt.substring(nameOffset, verOffset);
version = nAgt.substring(verOffset + 1);
if (browser.toLowerCase() == browser.toUpperCase()) {
browser = navigator.appName;
}
}
// trim the version string
if ((ix = version.indexOf(';')) != -1) version = version.substring(0, ix);
if ((ix = version.indexOf(' ')) != -1) version = version.substring(0, ix);
if ((ix = version.indexOf(')')) != -1) version = version.substring(0, ix);
majorVersion = parseInt('' + version, 10);
if (isNaN(majorVersion)) {
version = '' + parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion, 10);
}
// mobile version
var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);
// system
var os = unknown;
var clientStrings = [
{s:'Windows 10', r:/(Windows 10.0|Windows NT 10.0)/},
{s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
{s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
{s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
{s:'Windows Vista', r:/Windows NT 6.0/},
{s:'Windows Server 2003', r:/Windows NT 5.2/},
{s:'Windows XP', r:/(Windows NT 5.1|Windows XP)/},
{s:'Windows 2000', r:/(Windows NT 5.0|Windows 2000)/},
{s:'Windows ME', r:/(Win 9x 4.90|Windows ME)/},
{s:'Windows 98', r:/(Windows 98|Win98)/},
{s:'Windows 95', r:/(Windows 95|Win95|Windows_95)/},
{s:'Windows NT 4.0', r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
{s:'Windows CE', r:/Windows CE/},
{s:'Windows 3.11', r:/Win16/},
{s:'Android', r:/Android/},
{s:'Open BSD', r:/OpenBSD/},
{s:'Sun OS', r:/SunOS/},
{s:'Chrome OS', r:/CrOS/},
{s:'Linux', r:/(Linux|X11(?!.*CrOS))/},
{s:'iOS', r:/(iPhone|iPad|iPod)/},
{s:'Mac OS X', r:/Mac OS X/},
{s:'Mac OS', r:/(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
{s:'QNX', r:/QNX/},
{s:'UNIX', r:/UNIX/},
{s:'BeOS', r:/BeOS/},
{s:'OS/2', r:/OS\/2/},
{s:'Search Bot', r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
];
for (var id in clientStrings) {
var cs = clientStrings[id];
if (cs.r.test(nAgt)) {
os = cs.s;
break;
}
}
var osVersion = unknown;
if (/Windows/.test(os)) {
osVersion = /Windows (.*)/.exec(os)[1];
os = 'Windows';
}
switch (os) {
case 'Mac OS':
case 'Mac OS X':
case 'Android':
osVersion = /(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([\.\_\d]+)/.exec(nAgt)[1];
break;
case 'iOS':
osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
break;
}
// flash (you'll need to include swfobject)
/* script src="//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" */
var flashVersion = 'no check';
if (typeof swfobject != 'undefined') {
var fv = swfobject.getFlashPlayerVersion();
if (fv.major > 0) {
flashVersion = fv.major + '.' + fv.minor + ' r' + fv.release;
}
else {
flashVersion = unknown;
}
}
}
window.jscd = {
screen: screenSize,
browser: browser,
browserVersion: version,
browserMajorVersion: majorVersion,
mobile: mobile,
os: os,
osVersion: osVersion,
flashVersion: flashVersion
};
}(this));
(function($) {
var $html = $('html');
$html.addClass('browser-' + jscd.browser);
$html.addClass('platform-' + jscd.os);
})(jQuery);
/*!
* Isotope PACKAGED v2.2.2
*
* Licensed GPLv3 for open source use
* or Isotope Commercial License for commercial use
*
* http://isotope.metafizzy.co
* Copyright 2015 Metafizzy
*/
/**
* Bridget makes jQuery widgets
* v1.1.0
* MIT license
*/
( function( window ) {
// -------------------------- utils -------------------------- //
var slice = Array.prototype.slice;
function noop() {}
// -------------------------- definition -------------------------- //
function defineBridget( $ ) {
// bail if no jQuery
if ( !$ ) {
return;
}
// -------------------------- addOptionMethod -------------------------- //
/**
* adds option method -> $().plugin('option', {...})
* @param {Function} PluginClass - constructor class
*/
function addOptionMethod( PluginClass ) {
// don't overwrite original option method
if ( PluginClass.prototype.option ) {
return;
}
// option setter
PluginClass.prototype.option = function( opts ) {
// bail out if not an object
if ( !$.isPlainObject( opts ) ){
return;
}
this.options = $.extend( true, this.options, opts );
};
}
// -------------------------- plugin bridge -------------------------- //
// helper function for logging errors
// $.error breaks jQuery chaining
var logError = typeof console === 'undefined' ? noop :
function( message ) {
console.error( message );
};
/**
* jQuery plugin bridge, access methods like $elem.plugin('method')
* @param {String} namespace - plugin name
* @param {Function} PluginClass - constructor class
*/
function bridge( namespace, PluginClass ) {
// add to jQuery fn namespace
$.fn[ namespace ] = function( options ) {
if ( typeof options === 'string' ) {
// call plugin method when first argument is a string
// get arguments for method
var args = slice.call( arguments, 1 );
for ( var i=0, len = this.length; i < len; i++ ) {
var elem = this[i];
var instance = $.data( elem, namespace );
if ( !instance ) {
logError( "cannot call methods on " + namespace + " prior to initialization; " +
"attempted to call '" + options + "'" );
continue;
}
if ( typeof instance[options] !== "function" || options.charAt(0) === '_' ) {
logError( "no such method '" + options + "' for " + namespace + " instance" );
continue;
}
// trigger method with arguments
var returnValue = instance[ options ].apply( instance, args );
// break look and return first value if provided
if ( returnValue !== undefined ) {
return returnValue;
}
}
// return this if no return value
return this;
} else {
return this.each( function() {
var instance = $.data( this, namespace );
if ( instance ) {
// apply options & init
instance.option( options );
instance._init();
} else {
// initialize new instance
instance = new PluginClass( this, options );
$.data( this, namespace, instance );
}
});
}
};
}
// -------------------------- bridget -------------------------- //
/**
* converts a Prototypical class into a proper jQuery plugin
* the class must have a ._init method
* @param {String} namespace - plugin name, used in $().pluginName
* @param {Function} PluginClass - constructor class
*/
$.bridget = function( namespace, PluginClass ) {
addOptionMethod( PluginClass );
bridge( namespace, PluginClass );
};
return $.bridget;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'jquery-bridget/jquery.bridget',[ 'jquery' ], defineBridget );
} else if ( typeof exports === 'object' ) {
defineBridget( require('jquery') );
} else {
// get jquery from browser global
defineBridget( window.jQuery );
}
})( window );
/*!
* eventie v1.0.6
* event binding helper
* eventie.bind( elem, 'click', myFn )
* eventie.unbind( elem, 'click', myFn )
* MIT license
*/
/*jshint browser: true, undef: true, unused: true */
/*global define: false, module: false */
( function( window ) {
var docElem = document.documentElement;
var bind = function() {};
function getIEEvent( obj ) {
var event = window.event;
// add event.target
event.target = event.target || event.srcElement || obj;
return event;
}
if ( docElem.addEventListener ) {
bind = function( obj, type, fn ) {
obj.addEventListener( type, fn, false );
};
} else if ( docElem.attachEvent ) {
bind = function( obj, type, fn ) {
obj[ type + fn ] = fn.handleEvent ?
function() {
var event = getIEEvent( obj );
fn.handleEvent.call( fn, event );
} :
function() {
var event = getIEEvent( obj );
fn.call( obj, event );
};
obj.attachEvent( "on" + type, obj[ type + fn ] );
};
}
var unbind = function() {};
if ( docElem.removeEventListener ) {
unbind = function( obj, type, fn ) {
obj.removeEventListener( type, fn, false );
};
} else if ( docElem.detachEvent ) {
unbind = function( obj, type, fn ) {
obj.detachEvent( "on" + type, obj[ type + fn ] );
try {
delete obj[ type + fn ];
} catch ( err ) {
// can't delete window object properties
obj[ type + fn ] = undefined;
}
};
}
var eventie = {
bind: bind,
unbind: unbind
};
// ----- module definition ----- //
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'eventie/eventie',eventie );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = eventie;
} else {
// browser global
window.eventie = eventie;
}
})( window );
/*!
* EventEmitter v4.2.11 - git.io/ee
* Unlicense - http://unlicense.org/
* Oliver Caldwell - http://oli.me.uk/
* @preserve
*/
;(function () {
'use strict';
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class EventEmitter Manages event registering and emitting.
*/
function EventEmitter() {}
// Shortcuts to improve speed and size
var proto = EventEmitter.prototype;
var exports = this;
var originalGlobalValue = exports.EventEmitter;
/**
* Finds the index of the listener for the event in its storage array.
*
* @param {Function[]} listeners Array of listeners to search through.
* @param {Function} listener Method to look for.
* @return {Number} Index of the specified listener, -1 if not found
* @api private
*/
function indexOfListener(listeners, listener) {
var i = listeners.length;
while (i--) {
if (listeners[i].listener === listener) {
return i;
}
}
return -1;
}
/**
* Alias a method while keeping the context correct, to allow for overwriting of target method.
*
* @param {String} name The name of the target method.
* @return {Function} The aliased method
* @api private
*/
function alias(name) {
return function aliasClosure() {
return this[name].apply(this, arguments);
};
}
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
* Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
* Each property in the object response is an array of listener functions.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Function[]|Object} All listener functions for the event.
*/
proto.getListeners = function getListeners(evt) {
var events = this._getEvents();
var response;
var key;
// Return a concatenated array of all matching events if
// the selector is a regular expression.
if (evt instanceof RegExp) {
response = {};
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
response[key] = events[key];
}
}
}
else {
response = events[evt] || (events[evt] = []);
}
return response;
};
/**
* Takes a list of listener objects and flattens it into a list of listener functions.
*
* @param {Object[]} listeners Raw listener objects.
* @return {Function[]} Just the listener functions.
*/
proto.flattenListeners = function flattenListeners(listeners) {
var flatListeners = [];
var i;
for (i = 0; i < listeners.length; i += 1) {
flatListeners.push(listeners[i].listener);
}
return flatListeners;
};
/**
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Object} All listener functions for an event in an object.
*/
proto.getListenersAsObject = function getListenersAsObject(evt) {
var listeners = this.getListeners(evt);
var response;
if (listeners instanceof Array) {
response = {};
response[evt] = listeners;
}
return response || listeners;
};
/**
* Adds a listener function to the specified event.
* The listener will not be added if it is a duplicate.
* If the listener returns true then it will be removed after it is called.
* If you pass a regular expression as the event name then the listener will be added to all events that match it.
*
* @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addListener = function addListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var listenerIsWrapped = typeof listener === 'object';
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
listeners[key].push(listenerIsWrapped ? listener : {
listener: listener,
once: false
});
}
}
return this;
};
/**
* Alias of addListener
*/
proto.on = alias('addListener');
/**
* Semi-alias of addListener. It will add a listener that will be
* automatically removed after its first execution.
*
* @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addOnceListener = function addOnceListener(evt, listener) {
return this.addListener(evt, {
listener: listener,
once: true
});
};
/**
* Alias of addOnceListener.
*/
proto.once = alias('addOnceListener');
/**
* Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
* You need to tell it what event names should be matched by a regex.
*
* @param {String} evt Name of the event to create.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.defineEvent = function defineEvent(evt) {
this.getListeners(evt);
return this;
};
/**
* Uses defineEvent to define multiple events.
*
* @param {String[]} evts An array of event names to define.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.defineEvents = function defineEvents(evts) {
for (var i = 0; i < evts.length; i += 1) {
this.defineEvent(evts[i]);
}
return this;
};
/**
* Removes a listener function from the specified event.
* When passed a regular expression as the event name, it will remove the listener from all events that match it.
*
* @param {String|RegExp} evt Name of the event to remove the listener from.
* @param {Function} listener Method to remove from the event.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeListener = function removeListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var index;
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
index = indexOfListener(listeners[key], listener);
if (index !== -1) {
listeners[key].splice(index, 1);
}
}
}
return this;
};
/**
* Alias of removeListener
*/
proto.off = alias('removeListener');
/**
* Adds listeners in bulk using the manipulateListeners method.
* If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
* You can also pass it a regular expression to add the array of listeners to all events that match it.
* Yeah, this function does quite a bit. That's probably a bad thing.
*
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to add.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addListeners = function addListeners(evt, listeners) {
// Pass through to manipulateListeners
return this.manipulateListeners(false, evt, listeners);
};
/**
* Removes listeners in bulk using the manipulateListeners method.
* If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
* You can also pass it an event name and an array of listeners to be removed.
* You can also pass it a regular expression to remove the listeners from all events that match it.
*
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to remove.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeListeners = function removeListeners(evt, listeners) {
// Pass through to manipulateListeners
return this.manipulateListeners(true, evt, listeners);
};
/**
* Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
* The first argument will determine if the listeners are removed (true) or added (false).
* If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
* You can also pass it an event name and an array of listeners to be added/removed.
* You can also pass it a regular expression to manipulate the listeners of all events that match it.
*
* @param {Boolean} remove True if you want to remove listeners, false if you want to add.
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to add/remove.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
var i;
var value;
var single = remove ? this.removeListener : this.addListener;
var multiple = remove ? this.removeListeners : this.addListeners;
// If evt is an object then pass each of its properties to this method
if (typeof evt === 'object' && !(evt instanceof RegExp)) {
for (i in evt) {
if (evt.hasOwnProperty(i) && (value = evt[i])) {
// Pass the single listener straight through to the singular method
if (typeof value === 'function') {
single.call(this, i, value);
}
else {
// Otherwise pass back to the multiple function
multiple.call(this, i, value);
}
}
}
}
else {
// So evt must be a string
// And listeners must be an array of listeners
// Loop over it and pass each one to the multiple method
i = listeners.length;
while (i--) {
single.call(this, evt, listeners[i]);
}
}
return this;
};
/**
* Removes all listeners from a specified event.
* If you do not specify an event then all listeners will be removed.
* That means every event will be emptied.
* You can also pass a regex to remove all events that match it.
*
* @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeEvent = function removeEvent(evt) {
var type = typeof evt;
var events = this._getEvents();
var key;
// Remove different things depending on the state of evt
if (type === 'string') {
// Remove all listeners for the specified event
delete events[evt];
}
else if (evt instanceof RegExp) {
// Remove all events matching the regex.
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
delete events[key];
}
}
}
else {
// Remove all listeners in all events
delete this._events;
}
return this;
};
/**
* Alias of removeEvent.
*
* Added to mirror the node API.
*/
proto.removeAllListeners = alias('removeEvent');
/**
* Emits an event of your choice.
* When emitted, every listener attached to that event will be executed.
* If you pass the optional argument array then those arguments will be passed to every listener upon execution.
* Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
* So they will not arrive within the array on the other side, they will be separate.
* You can also pass a regular expression to emit to all events that match it.
*
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
* @param {Array} [args] Optional array of arguments to be passed to each listener.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.emitEvent = function emitEvent(evt, args) {
var listeners = this.getListenersAsObject(evt);
var listener;
var i;
var key;
var response;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
i = listeners[key].length;
while (i--) {
// If the listener returns true then it shall be removed from the event
// The function is executed either with a basic call or an apply if there is an args array
listener = listeners[key][i];
if (listener.once === true) {
this.removeListener(evt, listener.listener);
}
response = listener.listener.apply(this, args || []);
if (response === this._getOnceReturnValue()) {
this.removeListener(evt, listener.listener);
}
}
}
}
return this;
};
/**
* Alias of emitEvent
*/
proto.trigger = alias('emitEvent');
/**
* Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
* As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
*
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
* @param {...*} Optional additional arguments to be passed to each listener.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.emit = function emit(evt) {
var args = Array.prototype.slice.call(arguments, 1);
return this.emitEvent(evt, args);
};
/**
* Sets the current value to check against when executing listeners. If a
* listeners return value matches the one set here then it will be removed
* after execution. This value defaults to true.
*
* @param {*} value The new value to check for when executing listeners.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.setOnceReturnValue = function setOnceReturnValue(value) {
this._onceReturnValue = value;
return this;
};
/**
* Fetches the current value to check against when executing listeners. If
* the listeners return value matches this one then it should be removed
* automatically. It will return true by default.
*
* @return {*|Boolean} The current value to check for or the default, true.
* @api private
*/
proto._getOnceReturnValue = function _getOnceReturnValue() {
if (this.hasOwnProperty('_onceReturnValue')) {
return this._onceReturnValue;
}
else {
return true;
}
};
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
* @api private
*/
proto._getEvents = function _getEvents() {
return this._events || (this._events = {});
};
/**
* Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
*
* @return {Function} Non conflicting EventEmitter class.
*/
EventEmitter.noConflict = function noConflict() {
exports.EventEmitter = originalGlobalValue;
return EventEmitter;
};
// Expose the class either via AMD, CommonJS or the global object
if (typeof define === 'function' && define.amd) {
define('eventEmitter/EventEmitter',[],function () {
return EventEmitter;
});
}
else if (typeof module === 'object' && module.exports){
module.exports = EventEmitter;
}
else {
exports.EventEmitter = EventEmitter;
}
}.call(this));
/*!
* getStyleProperty v1.0.4
* original by kangax
* http://perfectionkills.com/feature-testing-css-properties/
* MIT license
*/
/*jshint browser: true, strict: true, undef: true */
/*global define: false, exports: false, module: false */
( function( window ) {
var prefixes = 'Webkit Moz ms Ms O'.split(' ');
var docElemStyle = document.documentElement.style;
function getStyleProperty( propName ) {
if ( !propName ) {
return;
}
// test standard property first
if ( typeof docElemStyle[ propName ] === 'string' ) {
return propName;
}
// capitalize
propName = propName.charAt(0).toUpperCase() + propName.slice(1);
// test vendor specific properties
var prefixed;
for ( var i=0, len = prefixes.length; i < len; i++ ) {
prefixed = prefixes[i] + propName;
if ( typeof docElemStyle[ prefixed ] === 'string' ) {
return prefixed;
}
}
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'get-style-property/get-style-property',[],function() {
return getStyleProperty;
});
} else if ( typeof exports === 'object' ) {
// CommonJS for Component
module.exports = getStyleProperty;
} else {
// browser global
window.getStyleProperty = getStyleProperty;
}
})( window );
/*!
* getSize v1.2.2
* measure size of elements
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false, exports: false, require: false, module: false, console: false */
( function( window, undefined ) {
// -------------------------- helpers -------------------------- //
// get a number from a string, not a percentage
function getStyleSize( value ) {
var num = parseFloat( value );
// not a percent like '100%', and a number
var isValid = value.indexOf('%') === -1 && !isNaN( num );
return isValid && num;
}
function noop() {}
var logError = typeof console === 'undefined' ? noop :
function( message ) {
console.error( message );
};
// -------------------------- measurements -------------------------- //
var measurements = [
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
'borderLeftWidth',
'borderRightWidth',
'borderTopWidth',
'borderBottomWidth'
];
function getZeroSize() {
var size = {
width: 0,
height: 0,
innerWidth: 0,
innerHeight: 0,
outerWidth: 0,
outerHeight: 0
};
for ( var i=0, len = measurements.length; i < len; i++ ) {
var measurement = measurements[i];
size[ measurement ] = 0;
}
return size;
}
function defineGetSize( getStyleProperty ) {
// -------------------------- setup -------------------------- //
var isSetup = false;
var getStyle, boxSizingProp, isBoxSizeOuter;
/**
* setup vars and functions
* do it on initial getSize(), rather than on script load
* For Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=548397
*/
function setup() {
// setup once
if ( isSetup ) {
return;
}
isSetup = true;
var getComputedStyle = window.getComputedStyle;
getStyle = ( function() {
var getStyleFn = getComputedStyle ?
function( elem ) {
return getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
return function getStyle( elem ) {
var style = getStyleFn( elem );
if ( !style ) {
logError( 'Style returned ' + style +
'. Are you running this code in a hidden iframe on Firefox? ' +
'See http://bit.ly/getsizebug1' );
}
return style;
};
})();
// -------------------------- box sizing -------------------------- //
boxSizingProp = getStyleProperty('boxSizing');
/**
* WebKit measures the outer-width on style.width on border-box elems
* IE & Firefox measures the inner-width
*/
if ( boxSizingProp ) {
var div = document.createElement('div');
div.style.width = '200px';
div.style.padding = '1px 2px 3px 4px';
div.style.borderStyle = 'solid';
div.style.borderWidth = '1px 2px 3px 4px';
div.style[ boxSizingProp ] = 'border-box';
var body = document.body || document.documentElement;
body.appendChild( div );
var style = getStyle( div );
isBoxSizeOuter = getStyleSize( style.width ) === 200;
body.removeChild( div );
}
}
// -------------------------- getSize -------------------------- //
function getSize( elem ) {
setup();
// use querySeletor if elem is string
if ( typeof elem === 'string' ) {
elem = document.querySelector( elem );
}
// do not proceed on non-objects
if ( !elem || typeof elem !== 'object' || !elem.nodeType ) {
return;
}
var style = getStyle( elem );
// if hidden, everything is 0
if ( style.display === 'none' ) {
return getZeroSize();
}
var size = {};
size.width = elem.offsetWidth;
size.height = elem.offsetHeight;
var isBorderBox = size.isBorderBox = !!( boxSizingProp &&
style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' );
// get all measurements
for ( var i=0, len = measurements.length; i < len; i++ ) {
var measurement = measurements[i];
var value = style[ measurement ];
value = mungeNonPixel( elem, value );
var num = parseFloat( value );
// any 'auto', 'medium' value will be 0
size[ measurement ] = !isNaN( num ) ? num : 0;
}
var paddingWidth = size.paddingLeft + size.paddingRight;
var paddingHeight = size.paddingTop + size.paddingBottom;
var marginWidth = size.marginLeft + size.marginRight;
var marginHeight = size.marginTop + size.marginBottom;
var borderWidth = size.borderLeftWidth + size.borderRightWidth;
var borderHeight = size.borderTopWidth + size.borderBottomWidth;
var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
// overwrite width and height if we can get it from style
var styleWidth = getStyleSize( style.width );
if ( styleWidth !== false ) {
size.width = styleWidth +
// add padding and border unless it's already including it
( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
}
var styleHeight = getStyleSize( style.height );
if ( styleHeight !== false ) {
size.height = styleHeight +
// add padding and border unless it's already including it
( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
}
size.innerWidth = size.width - ( paddingWidth + borderWidth );
size.innerHeight = size.height - ( paddingHeight + borderHeight );
size.outerWidth = size.width + marginWidth;
size.outerHeight = size.height + marginHeight;
return size;
}
// IE8 returns percent values, not pixels
// taken from jQuery's curCSS
function mungeNonPixel( elem, value ) {
// IE8 and has percent value
if ( window.getComputedStyle || value.indexOf('%') === -1 ) {
return value;
}
var style = elem.style;
// Remember the original values
var left = style.left;
var rs = elem.runtimeStyle;
var rsLeft = rs && rs.left;
// Put in the new values to get a computed value out
if ( rsLeft ) {
rs.left = elem.currentStyle.left;
}
style.left = value;
value = style.pixelLeft;
// Revert the changed values
style.left = left;
if ( rsLeft ) {
rs.left = rsLeft;
}
return value;
}
return getSize;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD for RequireJS
define( 'get-size/get-size',[ 'get-style-property/get-style-property' ], defineGetSize );
} else if ( typeof exports === 'object' ) {
// CommonJS for Component
module.exports = defineGetSize( require('desandro-get-style-property') );
} else {
// browser global
window.getSize = defineGetSize( window.getStyleProperty );
}
})( window );
/*!
* docReady v1.0.4
* Cross browser DOMContentLoaded event emitter
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true*/
/*global define: false, require: false, module: false */
( function( window ) {
var document = window.document;
// collection of functions to be triggered on ready
var queue = [];
function docReady( fn ) {
// throw out non-functions
if ( typeof fn !== 'function' ) {
return;
}
if ( docReady.isReady ) {
// ready now, hit it
fn();
} else {
// queue function when ready
queue.push( fn );
}
}
docReady.isReady = false;
// triggered on various doc ready events
function onReady( event ) {
// bail if already triggered or IE8 document is not ready just yet
var isIE8NotReady = event.type === 'readystatechange' && document.readyState !== 'complete';
if ( docReady.isReady || isIE8NotReady ) {
return;
}
trigger();
}
function trigger() {
docReady.isReady = true;
// process queue
for ( var i=0, len = queue.length; i < len; i++ ) {
var fn = queue[i];
fn();
}
}
function defineDocReady( eventie ) {
// trigger ready if page is ready
if ( document.readyState === 'complete' ) {
trigger();
} else {
// listen for events
eventie.bind( document, 'DOMContentLoaded', onReady );
eventie.bind( document, 'readystatechange', onReady );
eventie.bind( window, 'load', onReady );
}
return docReady;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'doc-ready/doc-ready',[ 'eventie/eventie' ], defineDocReady );
} else if ( typeof exports === 'object' ) {
module.exports = defineDocReady( require('eventie') );
} else {
// browser global
window.docReady = defineDocReady( window.eventie );
}
})( window );
/**
* matchesSelector v1.0.3
* matchesSelector( element, '.selector' )
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false, module: false */
( function( ElemProto ) {
'use strict';
var matchesMethod = ( function() {
// check for the standard method name first
if ( ElemProto.matches ) {
return 'matches';
}
// check un-prefixed
if ( ElemProto.matchesSelector ) {
return 'matchesSelector';
}
// check vendor prefixes
var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];
for ( var i=0, len = prefixes.length; i < len; i++ ) {
var prefix = prefixes[i];
var method = prefix + 'MatchesSelector';
if ( ElemProto[ method ] ) {
return method;
}
}
})();
// ----- match ----- //
function match( elem, selector ) {
return elem[ matchesMethod ]( selector );
}
// ----- appendToFragment ----- //
function checkParent( elem ) {
// not needed if already has parent
if ( elem.parentNode ) {
return;
}
var fragment = document.createDocumentFragment();
fragment.appendChild( elem );
}
// ----- query ----- //
// fall back to using QSA
// thx @jonathantneal https://gist.github.com/3062955
function query( elem, selector ) {
// append to fragment if no parent
checkParent( elem );
// match elem with all selected elems of parent
var elems = elem.parentNode.querySelectorAll( selector );
for ( var i=0, len = elems.length; i < len; i++ ) {
// return true if match
if ( elems[i] === elem ) {
return true;
}
}
// otherwise return false
return false;
}
// ----- matchChild ----- //
function matchChild( elem, selector ) {
checkParent( elem );
return match( elem, selector );
}
// ----- matchesSelector ----- //
var matchesSelector;
if ( matchesMethod ) {
// IE9 supports matchesSelector, but doesn't work on orphaned elems
// check for that
var div = document.createElement('div');
var supportsOrphans = match( div, 'div' );
matchesSelector = supportsOrphans ? match : matchChild;
} else {
matchesSelector = query;
}
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'matches-selector/matches-selector',[],function() {
return matchesSelector;
});
} else if ( typeof exports === 'object' ) {
module.exports = matchesSelector;
}
else {
// browser global
window.matchesSelector = matchesSelector;
}
})( Element.prototype );
/**
* Fizzy UI utils v1.0.1
* MIT license
*/
/*jshint browser: true, undef: true, unused: true, strict: true */
( function( window, factory ) {
/*global define: false, module: false, require: false */
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'fizzy-ui-utils/utils',[
'doc-ready/doc-ready',
'matches-selector/matches-selector'
], function( docReady, matchesSelector ) {
return factory( window, docReady, matchesSelector );
});
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
window,
require('doc-ready'),
require('desandro-matches-selector')
);
} else {
// browser global
window.fizzyUIUtils = factory(
window,
window.docReady,
window.matchesSelector
);
}
}( window, function factory( window, docReady, matchesSelector ) {
var utils = {};
// ----- extend ----- //
// extends objects
utils.extend = function( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
};
// ----- modulo ----- //
utils.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
// ----- isArray ----- //
var objToString = Object.prototype.toString;
utils.isArray = function( obj ) {
return objToString.call( obj ) == '[object Array]';
};
// ----- makeArray ----- //
// turn element or nodeList into an array
utils.makeArray = function( obj ) {
var ary = [];
if ( utils.isArray( obj ) ) {
// use object if already an array
ary = obj;
} else if ( obj && typeof obj.length == 'number' ) {
// convert nodeList to array
for ( var i=0, len = obj.length; i < len; i++ ) {
ary.push( obj[i] );
}
} else {
// array of single index
ary.push( obj );
}
return ary;
};
// ----- indexOf ----- //
// index of helper cause IE8
utils.indexOf = Array.prototype.indexOf ? function( ary, obj ) {
return ary.indexOf( obj );
} : function( ary, obj ) {
for ( var i=0, len = ary.length; i < len; i++ ) {
if ( ary[i] === obj ) {
return i;
}
}
return -1;
};
// ----- removeFrom ----- //
utils.removeFrom = function( ary, obj ) {
var index = utils.indexOf( ary, obj );
if ( index != -1 ) {
ary.splice( index, 1 );
}
};
// ----- isElement ----- //
// http://stackoverflow.com/a/384380/182183
utils.isElement = typeof HTMLElement == 'object' ?
function isElementDOM2( obj ) {
return obj instanceof HTMLElement;
} :
function isElementQuirky( obj ) {
return obj && typeof obj == 'object' && obj !== null &&
obj.nodeType == 1 && typeof obj.nodeName == 'string';
};
// ----- setText ----- //
utils.setText = ( function() {
var setTextProperty;
function setText( elem, text ) {
// only check setTextProperty once
setTextProperty = setTextProperty || ( document.documentElement.textContent !== undefined ? 'textContent' : 'innerText' );
elem[ setTextProperty ] = text;
}
return setText;
})();
// ----- getParent ----- //
utils.getParent = function( elem, selector ) {
while ( elem != document.body ) {
elem = elem.parentNode;
if ( matchesSelector( elem, selector ) ) {
return elem;
}
}
};
// ----- getQueryElement ----- //
// use element as selector string
utils.getQueryElement = function( elem ) {
if ( typeof elem == 'string' ) {
return document.querySelector( elem );
}
return elem;
};
// ----- handleEvent ----- //
// enable .ontype to trigger from .addEventListener( elem, 'type' )
utils.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// ----- filterFindElements ----- //
utils.filterFindElements = function( elems, selector ) {
// make array of elems
elems = utils.makeArray( elems );
var ffElems = [];
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
// check that elem is an actual element
if ( !utils.isElement( elem ) ) {
continue;
}
// filter & find items if we have a selector
if ( selector ) {
// filter siblings
if ( matchesSelector( elem, selector ) ) {
ffElems.push( elem );
}
// find children
var childElems = elem.querySelectorAll( selector );
// concat childElems to filterFound array
for ( var j=0, jLen = childElems.length; j < jLen; j++ ) {
ffElems.push( childElems[j] );
}
} else {
ffElems.push( elem );
}
}
return ffElems;
};
// ----- debounceMethod ----- //
utils.debounceMethod = function( _class, methodName, threshold ) {
// original method
var method = _class.prototype[ methodName ];
var timeoutName = methodName + 'Timeout';
_class.prototype[ methodName ] = function() {
var timeout = this[ timeoutName ];
if ( timeout ) {
clearTimeout( timeout );
}
var args = arguments;
var _this = this;
this[ timeoutName ] = setTimeout( function() {
method.apply( _this, args );
delete _this[ timeoutName ];
}, threshold || 100 );
};
};
// ----- htmlInit ----- //
// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
utils.toDashed = function( str ) {
return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
return $1 + '-' + $2;
}).toLowerCase();
};
var console = window.console;
/**
* allow user to initialize classes via .js-namespace class
* htmlInit( Widget, 'widgetName' )
* options are parsed from data-namespace-option attribute
*/
utils.htmlInit = function( WidgetClass, namespace ) {
docReady( function() {
var dashedNamespace = utils.toDashed( namespace );
var elems = document.querySelectorAll( '.js-' + dashedNamespace );
var dataAttr = 'data-' + dashedNamespace + '-options';
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
var attr = elem.getAttribute( dataAttr );
var options;
try {
options = attr && JSON.parse( attr );
} catch ( error ) {
// log error, do not initialize
if ( console ) {
console.error( 'Error parsing ' + dataAttr + ' on ' +
elem.nodeName.toLowerCase() + ( elem.id ? '#' + elem.id : '' ) + ': ' +
error );
}
continue;
}
// initialize
var instance = new WidgetClass( elem, options );
// make available via $().data('layoutname')
var jQuery = window.jQuery;
if ( jQuery ) {
jQuery.data( elem, namespace, instance );
}
}
});
};
// ----- ----- //
return utils;
}));
/**
* Outlayer Item
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'outlayer/item',[
'eventEmitter/EventEmitter',
'get-size/get-size',
'get-style-property/get-style-property',
'fizzy-ui-utils/utils'
],
function( EventEmitter, getSize, getStyleProperty, utils ) {
return factory( window, EventEmitter, getSize, getStyleProperty, utils );
}
);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(
window,
require('wolfy87-eventemitter'),
require('get-size'),
require('desandro-get-style-property'),
require('fizzy-ui-utils')
);
} else {
// browser global
window.Outlayer = {};
window.Outlayer.Item = factory(
window,
window.EventEmitter,
window.getSize,
window.getStyleProperty,
window.fizzyUIUtils
);
}
}( window, function factory( window, EventEmitter, getSize, getStyleProperty, utils ) {
'use strict';
// ----- helpers ----- //
var getComputedStyle = window.getComputedStyle;
var getStyle = getComputedStyle ?
function( elem ) {
return getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
function isEmptyObj( obj ) {
for ( var prop in obj ) {
return false;
}
prop = null;
return true;
}
// -------------------------- CSS3 support -------------------------- //
var transitionProperty = getStyleProperty('transition');
var transformProperty = getStyleProperty('transform');
var supportsCSS3 = transitionProperty && transformProperty;
var is3d = !!getStyleProperty('perspective');
var transitionEndEvent = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'otransitionend',
transition: 'transitionend'
}[ transitionProperty ];
// properties that could have vendor prefix
var prefixableProperties = [
'transform',
'transition',
'transitionDuration',
'transitionProperty'
];
// cache all vendor properties
var vendorProperties = ( function() {
var cache = {};
for ( var i=0, len = prefixableProperties.length; i < len; i++ ) {
var prop = prefixableProperties[i];
var supportedProp = getStyleProperty( prop );
if ( supportedProp && supportedProp !== prop ) {
cache[ prop ] = supportedProp;
}
}
return cache;
})();
// -------------------------- Item -------------------------- //
function Item( element, layout ) {
if ( !element ) {
return;
}
this.element = element;
// parent layout class, i.e. Masonry, Isotope, or Packery
this.layout = layout;
this.position = {
x: 0,
y: 0
};
this._create();
}
// inherit EventEmitter
utils.extend( Item.prototype, EventEmitter.prototype );
Item.prototype._create = function() {
// transition objects
this._transn = {
ingProperties: {},
clean: {},
onEnd: {}
};
this.css({
position: 'absolute'
});
};
// trigger specified handler for event type
Item.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
Item.prototype.getSize = function() {
this.size = getSize( this.element );
};
/**
* apply CSS styles to element
* @param {Object} style
*/
Item.prototype.css = function( style ) {
var elemStyle = this.element.style;
for ( var prop in style ) {
// use vendor property if available
var supportedProp = vendorProperties[ prop ] || prop;
elemStyle[ supportedProp ] = style[ prop ];
}
};
// measure position, and sets it
Item.prototype.getPosition = function() {
var style = getStyle( this.element );
var layoutOptions = this.layout.options;
var isOriginLeft = layoutOptions.isOriginLeft;
var isOriginTop = layoutOptions.isOriginTop;
var xValue = style[ isOriginLeft ? 'left' : 'right' ];
var yValue = style[ isOriginTop ? 'top' : 'bottom' ];
// convert percent to pixels
var layoutSize = this.layout.size;
var x = xValue.indexOf('%') != -1 ?
( parseFloat( xValue ) / 100 ) * layoutSize.width : parseInt( xValue, 10 );
var y = yValue.indexOf('%') != -1 ?
( parseFloat( yValue ) / 100 ) * layoutSize.height : parseInt( yValue, 10 );
// clean up 'auto' or other non-integer values
x = isNaN( x ) ? 0 : x;
y = isNaN( y ) ? 0 : y;
// remove padding from measurement
x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight;
y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom;
this.position.x = x;
this.position.y = y;
};
// set settled position, apply padding
Item.prototype.layoutPosition = function() {
var layoutSize = this.layout.size;
var layoutOptions = this.layout.options;
var style = {};
// x
var xPadding = layoutOptions.isOriginLeft ? 'paddingLeft' : 'paddingRight';
var xProperty = layoutOptions.isOriginLeft ? 'left' : 'right';
var xResetProperty = layoutOptions.isOriginLeft ? 'right' : 'left';
var x = this.position.x + layoutSize[ xPadding ];
// set in percentage or pixels
style[ xProperty ] = this.getXValue( x );
// reset other property
style[ xResetProperty ] = '';
// y
var yPadding = layoutOptions.isOriginTop ? 'paddingTop' : 'paddingBottom';
var yProperty = layoutOptions.isOriginTop ? 'top' : 'bottom';
var yResetProperty = layoutOptions.isOriginTop ? 'bottom' : 'top';
var y = this.position.y + layoutSize[ yPadding ];
// set in percentage or pixels
style[ yProperty ] = this.getYValue( y );
// reset other property
style[ yResetProperty ] = '';
this.css( style );
this.emitEvent( 'layout', [ this ] );
};
Item.prototype.getXValue = function( x ) {
var layoutOptions = this.layout.options;
return layoutOptions.percentPosition && !layoutOptions.isHorizontal ?
( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px';
};
Item.prototype.getYValue = function( y ) {
var layoutOptions = this.layout.options;
return layoutOptions.percentPosition && layoutOptions.isHorizontal ?
( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px';
};
Item.prototype._transitionTo = function( x, y ) {
this.getPosition();
// get current x & y from top/left
var curX = this.position.x;
var curY = this.position.y;
var compareX = parseInt( x, 10 );
var compareY = parseInt( y, 10 );
var didNotMove = compareX === this.position.x && compareY === this.position.y;
// save end position
this.setPosition( x, y );
// if did not move and not transitioning, just go to layout
if ( didNotMove && !this.isTransitioning ) {
this.layoutPosition();
return;
}
var transX = x - curX;
var transY = y - curY;
var transitionStyle = {};
transitionStyle.transform = this.getTranslate( transX, transY );
this.transition({
to: transitionStyle,
onTransitionEnd: {
transform: this.layoutPosition
},
isCleaning: true
});
};
Item.prototype.getTranslate = function( x, y ) {
// flip cooridinates if origin on right or bottom
var layoutOptions = this.layout.options;
x = layoutOptions.isOriginLeft ? x : -x;
y = layoutOptions.isOriginTop ? y : -y;
if ( is3d ) {
return 'translate3d(' + x + 'px, ' + y + 'px, 0)';
}
return 'translate(' + x + 'px, ' + y + 'px)';
};
// non transition + transform support
Item.prototype.goTo = function( x, y ) {
this.setPosition( x, y );
this.layoutPosition();
};
// use transition and transforms if supported
Item.prototype.moveTo = supportsCSS3 ?
Item.prototype._transitionTo : Item.prototype.goTo;
Item.prototype.setPosition = function( x, y ) {
this.position.x = parseInt( x, 10 );
this.position.y = parseInt( y, 10 );
};
// ----- transition ----- //
/**
* @param {Object} style - CSS
* @param {Function} onTransitionEnd
*/
// non transition, just trigger callback
Item.prototype._nonTransition = function( args ) {
this.css( args.to );
if ( args.isCleaning ) {
this._removeStyles( args.to );
}
for ( var prop in args.onTransitionEnd ) {
args.onTransitionEnd[ prop ].call( this );
}
};
/**
* proper transition
* @param {Object} args - arguments
* @param {Object} to - style to transition to
* @param {Object} from - style to start transition from
* @param {Boolean} isCleaning - removes transition styles after transition
* @param {Function} onTransitionEnd - callback
*/
Item.prototype._transition = function( args ) {
// redirect to nonTransition if no transition duration
if ( !parseFloat( this.layout.options.transitionDuration ) ) {
this._nonTransition( args );
return;
}
var _transition = this._transn;
// keep track of onTransitionEnd callback by css property
for ( var prop in args.onTransitionEnd ) {
_transition.onEnd[ prop ] = args.onTransitionEnd[ prop ];
}
// keep track of properties that are transitioning
for ( prop in args.to ) {
_transition.ingProperties[ prop ] = true;
// keep track of properties to clean up when transition is done
if ( args.isCleaning ) {
_transition.clean[ prop ] = true;
}
}
// set from styles
if ( args.from ) {
this.css( args.from );
// force redraw. http://blog.alexmaccaw.com/css-transitions
var h = this.element.offsetHeight;
// hack for JSHint to hush about unused var
h = null;
}
// enable transition
this.enableTransition( args.to );
// set styles that are transitioning
this.css( args.to );
this.isTransitioning = true;
};
// dash before all cap letters, including first for
// WebkitTransform => -webkit-transform
function toDashedAll( str ) {
return str.replace( /([A-Z])/g, function( $1 ) {
return '-' + $1.toLowerCase();
});
}
var transitionProps = 'opacity,' +
toDashedAll( vendorProperties.transform || 'transform' );
Item.prototype.enableTransition = function(/* style */) {
// HACK changing transitionProperty during a transition
// will cause transition to jump
if ( this.isTransitioning ) {
return;
}
// make `transition: foo, bar, baz` from style object
// HACK un-comment this when enableTransition can work
// while a transition is happening
// var transitionValues = [];
// for ( var prop in style ) {
// // dash-ify camelCased properties like WebkitTransition
// prop = vendorProperties[ prop ] || prop;
// transitionValues.push( toDashedAll( prop ) );
// }
// enable transition styles
this.css({
transitionProperty: transitionProps,
transitionDuration: this.layout.options.transitionDuration
});
// listen for transition end event
this.element.addEventListener( transitionEndEvent, this, false );
};
Item.prototype.transition = Item.prototype[ transitionProperty ? '_transition' : '_nonTransition' ];
// ----- events ----- //
Item.prototype.onwebkitTransitionEnd = function( event ) {
this.ontransitionend( event );
};
Item.prototype.onotransitionend = function( event ) {
this.ontransitionend( event );
};
// properties that I munge to make my life easier
var dashedVendorProperties = {
'-webkit-transform': 'transform',
'-moz-transform': 'transform',
'-o-transform': 'transform'
};
Item.prototype.ontransitionend = function( event ) {
// disregard bubbled events from children
if ( event.target !== this.element ) {
return;
}
var _transition = this._transn;
// get property name of transitioned property, convert to prefix-free
var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName;
// remove property that has completed transitioning
delete _transition.ingProperties[ propertyName ];
// check if any properties are still transitioning
if ( isEmptyObj( _transition.ingProperties ) ) {
// all properties have completed transitioning
this.disableTransition();
}
// clean style
if ( propertyName in _transition.clean ) {
// clean up style
this.element.style[ event.propertyName ] = '';
delete _transition.clean[ propertyName ];
}
// trigger onTransitionEnd callback
if ( propertyName in _transition.onEnd ) {
var onTransitionEnd = _transition.onEnd[ propertyName ];
onTransitionEnd.call( this );
delete _transition.onEnd[ propertyName ];
}
this.emitEvent( 'transitionEnd', [ this ] );
};
Item.prototype.disableTransition = function() {
this.removeTransitionStyles();
this.element.removeEventListener( transitionEndEvent, this, false );
this.isTransitioning = false;
};
/**
* removes style property from element
* @param {Object} style
**/
Item.prototype._removeStyles = function( style ) {
// clean up transition styles
var cleanStyle = {};
for ( var prop in style ) {
cleanStyle[ prop ] = '';
}
this.css( cleanStyle );
};
var cleanTransitionStyle = {
transitionProperty: '',
transitionDuration: ''
};
Item.prototype.removeTransitionStyles = function() {
// remove transition
this.css( cleanTransitionStyle );
};
// ----- show/hide/remove ----- //
// remove element from DOM
Item.prototype.removeElem = function() {
this.element.parentNode.removeChild( this.element );
// remove display: none
this.css({ display: '' });
this.emitEvent( 'remove', [ this ] );
};
Item.prototype.remove = function() {
// just remove element if no transition support or no transition
if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) {
this.removeElem();
return;
}
// start transition
var _this = this;
this.once( 'transitionEnd', function() {
_this.removeElem();
});
this.hide();
};
Item.prototype.reveal = function() {
delete this.isHidden;
// remove display: none
this.css({ display: '' });
var options = this.layout.options;
var onTransitionEnd = {};
var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle');
onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd;
this.transition({
from: options.hiddenStyle,
to: options.visibleStyle,
isCleaning: true,
onTransitionEnd: onTransitionEnd
});
};
Item.prototype.onRevealTransitionEnd = function() {
// check if still visible
// during transition, item may have been hidden
if ( !this.isHidden ) {
this.emitEvent('reveal');
}
};
/**
* get style property use for hide/reveal transition end
* @param {String} styleProperty - hiddenStyle/visibleStyle
* @returns {String}
*/
Item.prototype.getHideRevealTransitionEndProperty = function( styleProperty ) {
var optionStyle = this.layout.options[ styleProperty ];
// use opacity
if ( optionStyle.opacity ) {
return 'opacity';
}
// get first property
for ( var prop in optionStyle ) {
return prop;
}
};
Item.prototype.hide = function() {
// set flag
this.isHidden = true;
// remove display: none
this.css({ display: '' });
var options = this.layout.options;
var onTransitionEnd = {};
var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle');
onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd;
this.transition({
from: options.visibleStyle,
to: options.hiddenStyle,
// keep hidden stuff hidden
isCleaning: true,
onTransitionEnd: onTransitionEnd
});
};
Item.prototype.onHideTransitionEnd = function() {
// check if still hidden
// during transition, item may have been un-hidden
if ( this.isHidden ) {
this.css({ display: 'none' });
this.emitEvent('hide');
}
};
Item.prototype.destroy = function() {
this.css({
position: '',
left: '',
right: '',
top: '',
bottom: '',
transition: '',
transform: ''
});
};
return Item;
}));
/*!
* Outlayer v1.4.2
* the brains and guts of a layout library
* MIT license
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'outlayer/outlayer',[
'eventie/eventie',
'eventEmitter/EventEmitter',
'get-size/get-size',
'fizzy-ui-utils/utils',
'./item'
],
function( eventie, EventEmitter, getSize, utils, Item ) {
return factory( window, eventie, EventEmitter, getSize, utils, Item);
}
);
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
window,
require('eventie'),
require('wolfy87-eventemitter'),
require('get-size'),
require('fizzy-ui-utils'),
require('./item')
);
} else {
// browser global
window.Outlayer = factory(
window,
window.eventie,
window.EventEmitter,
window.getSize,
window.fizzyUIUtils,
window.Outlayer.Item
);
}
}( window, function factory( window, eventie, EventEmitter, getSize, utils, Item ) {
'use strict';
// ----- vars ----- //
var console = window.console;
var jQuery = window.jQuery;
var noop = function() {};
// -------------------------- Outlayer -------------------------- //
// globally unique identifiers
var GUID = 0;
// internal store of all Outlayer intances
var instances = {};
/**
* @param {Element, String} element
* @param {Object} options
* @constructor
*/
function Outlayer( element, options ) {
var queryElement = utils.getQueryElement( element );
if ( !queryElement ) {
if ( console ) {
console.error( 'Bad element for ' + this.constructor.namespace +
': ' + ( queryElement || element ) );
}
return;
}
this.element = queryElement;
// add jQuery
if ( jQuery ) {
this.$element = jQuery( this.element );
}
// options
this.options = utils.extend( {}, this.constructor.defaults );
this.option( options );
// add id for Outlayer.getFromElement
var id = ++GUID;
this.element.outlayerGUID = id; // expando
instances[ id ] = this; // associate via id
// kick it off
this._create();
if ( this.options.isInitLayout ) {
this.layout();
}
}
// settings are for internal use only
Outlayer.namespace = 'outlayer';
Outlayer.Item = Item;
// default options
Outlayer.defaults = {
containerStyle: {
position: 'relative'
},
isInitLayout: true,
isOriginLeft: true,
isOriginTop: true,
isResizeBound: true,
isResizingContainer: true,
// item options
transitionDuration: '0.4s',
hiddenStyle: {
opacity: 0,
transform: 'scale(0.001)'
},
visibleStyle: {
opacity: 1,
transform: 'scale(1)'
}
};
// inherit EventEmitter
utils.extend( Outlayer.prototype, EventEmitter.prototype );
/**
* set options
* @param {Object} opts
*/
Outlayer.prototype.option = function( opts ) {
utils.extend( this.options, opts );
};
Outlayer.prototype._create = function() {
// get items from children
this.reloadItems();
// elements that affect layout, but are not laid out
this.stamps = [];
this.stamp( this.options.stamp );
// set container style
utils.extend( this.element.style, this.options.containerStyle );
// bind resize method
if ( this.options.isResizeBound ) {
this.bindResize();
}
};
// goes through all children again and gets bricks in proper order
Outlayer.prototype.reloadItems = function() {
// collection of item elements
this.items = this._itemize( this.element.children );
};
/**
* turn elements into Outlayer.Items to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - collection of new Outlayer Items
*/
Outlayer.prototype._itemize = function( elems ) {
var itemElems = this._filterFindItemElements( elems );
var Item = this.constructor.Item;
// create new Outlayer Items for collection
var items = [];
for ( var i=0, len = itemElems.length; i < len; i++ ) {
var elem = itemElems[i];
var item = new Item( elem, this );
items.push( item );
}
return items;
};
/**
* get item elements to be used in layout
* @param {Array or NodeList or HTMLElement} elems
* @returns {Array} items - item elements
*/
Outlayer.prototype._filterFindItemElements = function( elems ) {
return utils.filterFindElements( elems, this.options.itemSelector );
};
/**
* getter method for getting item elements
* @returns {Array} elems - collection of item elements
*/
Outlayer.prototype.getItemElements = function() {
var elems = [];
for ( var i=0, len = this.items.length; i < len; i++ ) {
elems.push( this.items[i].element );
}
return elems;
};
// ----- init & layout ----- //
/**
* lays out all items
*/
Outlayer.prototype.layout = function() {
this._resetLayout();
this._manageStamps();
// don't animate first layout
var isInstant = this.options.isLayoutInstant !== undefined ?
this.options.isLayoutInstant : !this._isLayoutInited;
this.layoutItems( this.items, isInstant );
// flag for initalized
this._isLayoutInited = true;
};
// _init is alias for layout
Outlayer.prototype._init = Outlayer.prototype.layout;
/**
* logic before any new layout
*/
Outlayer.prototype._resetLayout = function() {
this.getSize();
};
Outlayer.prototype.getSize = function() {
this.size = getSize( this.element );
};
/**
* get measurement from option, for columnWidth, rowHeight, gutter
* if option is String -> get element from selector string, & get size of element
* if option is Element -> get size of element
* else use option as a number
*
* @param {String} measurement
* @param {String} size - width or height
* @private
*/
Outlayer.prototype._getMeasurement = function( measurement, size ) {
var option = this.options[ measurement ];
var elem;
if ( !option ) {
// default to 0
this[ measurement ] = 0;
} else {
// use option as an element
if ( typeof option === 'string' ) {
elem = this.element.querySelector( option );
} else if ( utils.isElement( option ) ) {
elem = option;
}
// use size of element, if element
this[ measurement ] = elem ? getSize( elem )[ size ] : option;
}
};
/**
* layout a collection of item elements
* @api public
*/
Outlayer.prototype.layoutItems = function( items, isInstant ) {
items = this._getItemsForLayout( items );
this._layoutItems( items, isInstant );
this._postLayout();
};
/**
* get the items to be laid out
* you may want to skip over some items
* @param {Array} items
* @returns {Array} items
*/
Outlayer.prototype._getItemsForLayout = function( items ) {
var layoutItems = [];
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
if ( !item.isIgnored ) {
layoutItems.push( item );
}
}
return layoutItems;
};
/**
* layout items
* @param {Array} items
* @param {Boolean} isInstant
*/
Outlayer.prototype._layoutItems = function( items, isInstant ) {
this._emitCompleteOnItems( 'layout', items );
if ( !items || !items.length ) {
// no items, emit event with empty array
return;
}
var queue = [];
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
// get x/y object from method
var position = this._getItemLayoutPosition( item );
// enqueue
position.item = item;
position.isInstant = isInstant || item.isLayoutInstant;
queue.push( position );
}
this._processLayoutQueue( queue );
};
/**
* get item layout position
* @param {Outlayer.Item} item
* @returns {Object} x and y position
*/
Outlayer.prototype._getItemLayoutPosition = function( /* item */ ) {
return {
x: 0,
y: 0
};
};
/**
* iterate over array and position each item
* Reason being - separating this logic prevents 'layout invalidation'
* thx @paul_irish
* @param {Array} queue
*/
Outlayer.prototype._processLayoutQueue = function( queue ) {
for ( var i=0, len = queue.length; i < len; i++ ) {
var obj = queue[i];
this._positionItem( obj.item, obj.x, obj.y, obj.isInstant );
}
};
/**
* Sets position of item in DOM
* @param {Outlayer.Item} item
* @param {Number} x - horizontal position
* @param {Number} y - vertical position
* @param {Boolean} isInstant - disables transitions
*/
Outlayer.prototype._positionItem = function( item, x, y, isInstant ) {
if ( isInstant ) {
// if not transition, just set CSS
item.goTo( x, y );
} else {
item.moveTo( x, y );
}
};
/**
* Any logic you want to do after each layout,
* i.e. size the container
*/
Outlayer.prototype._postLayout = function() {
this.resizeContainer();
};
Outlayer.prototype.resizeContainer = function() {
if ( !this.options.isResizingContainer ) {
return;
}
var size = this._getContainerSize();
if ( size ) {
this._setContainerMeasure( size.width, true );
this._setContainerMeasure( size.height, false );
}
};
/**
* Sets width or height of container if returned
* @returns {Object} size
* @param {Number} width
* @param {Number} height
*/
Outlayer.prototype._getContainerSize = noop;
/**
* @param {Number} measure - size of width or height
* @param {Boolean} isWidth
*/
Outlayer.prototype._setContainerMeasure = function( measure, isWidth ) {
if ( measure === undefined ) {
return;
}
var elemSize = this.size;
// add padding and border width if border box
if ( elemSize.isBorderBox ) {
measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
elemSize.borderLeftWidth + elemSize.borderRightWidth :
elemSize.paddingBottom + elemSize.paddingTop +
elemSize.borderTopWidth + elemSize.borderBottomWidth;
}
measure = Math.max( measure, 0 );
this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px';
};
/**
* emit eventComplete on a collection of items events
* @param {String} eventName
* @param {Array} items - Outlayer.Items
*/
Outlayer.prototype._emitCompleteOnItems = function( eventName, items ) {
var _this = this;
function onComplete() {
_this.dispatchEvent( eventName + 'Complete', null, [ items ] );
}
var count = items.length;
if ( !items || !count ) {
onComplete();
return;
}
var doneCount = 0;
function tick() {
doneCount++;
if ( doneCount === count ) {
onComplete();
}
}
// bind callback
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
item.once( eventName, tick );
}
};
/**
* emits events via eventEmitter and jQuery events
* @param {String} type - name of event
* @param {Event} event - original event
* @param {Array} args - extra arguments
*/
Outlayer.prototype.dispatchEvent = function( type, event, args ) {
// add original event to arguments
var emitArgs = event ? [ event ].concat( args ) : args;
this.emitEvent( type, emitArgs );
if ( jQuery ) {
// set this.$element
this.$element = this.$element || jQuery( this.element );
if ( event ) {
// create jQuery event
var $event = jQuery.Event( event );
$event.type = type;
this.$element.trigger( $event, args );
} else {
// just trigger with type if no event available
this.$element.trigger( type, args );
}
}
};
// -------------------------- ignore & stamps -------------------------- //
/**
* keep item in collection, but do not lay it out
* ignored items do not get skipped in layout
* @param {Element} elem
*/
Outlayer.prototype.ignore = function( elem ) {
var item = this.getItem( elem );
if ( item ) {
item.isIgnored = true;
}
};
/**
* return item to layout collection
* @param {Element} elem
*/
Outlayer.prototype.unignore = function( elem ) {
var item = this.getItem( elem );
if ( item ) {
delete item.isIgnored;
}
};
/**
* adds elements to stamps
* @param {NodeList, Array, Element, or String} elems
*/
Outlayer.prototype.stamp = function( elems ) {
elems = this._find( elems );
if ( !elems ) {
return;
}
this.stamps = this.stamps.concat( elems );
// ignore
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
this.ignore( elem );
}
};
/**
* removes elements to stamps
* @param {NodeList, Array, or Element} elems
*/
Outlayer.prototype.unstamp = function( elems ) {
elems = this._find( elems );
if ( !elems ){
return;
}
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
// filter out removed stamp elements
utils.removeFrom( this.stamps, elem );
this.unignore( elem );
}
};
/**
* finds child elements
* @param {NodeList, Array, Element, or String} elems
* @returns {Array} elems
*/
Outlayer.prototype._find = function( elems ) {
if ( !elems ) {
return;
}
// if string, use argument as selector string
if ( typeof elems === 'string' ) {
elems = this.element.querySelectorAll( elems );
}
elems = utils.makeArray( elems );
return elems;
};
Outlayer.prototype._manageStamps = function() {
if ( !this.stamps || !this.stamps.length ) {
return;
}
this._getBoundingRect();
for ( var i=0, len = this.stamps.length; i < len; i++ ) {
var stamp = this.stamps[i];
this._manageStamp( stamp );
}
};
// update boundingLeft / Top
Outlayer.prototype._getBoundingRect = function() {
// get bounding rect for container element
var boundingRect = this.element.getBoundingClientRect();
var size = this.size;
this._boundingRect = {
left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
top: boundingRect.top + size.paddingTop + size.borderTopWidth,
right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
};
};
/**
* @param {Element} stamp
**/
Outlayer.prototype._manageStamp = noop;
/**
* get x/y position of element relative to container element
* @param {Element} elem
* @returns {Object} offset - has left, top, right, bottom
*/
Outlayer.prototype._getElementOffset = function( elem ) {
var boundingRect = elem.getBoundingClientRect();
var thisRect = this._boundingRect;
var size = getSize( elem );
var offset = {
left: boundingRect.left - thisRect.left - size.marginLeft,
top: boundingRect.top - thisRect.top - size.marginTop,
right: thisRect.right - boundingRect.right - size.marginRight,
bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
};
return offset;
};
// -------------------------- resize -------------------------- //
// enable event handlers for listeners
// i.e. resize -> onresize
Outlayer.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
/**
* Bind layout to window resizing
*/
Outlayer.prototype.bindResize = function() {
// bind just one listener
if ( this.isResizeBound ) {
return;
}
eventie.bind( window, 'resize', this );
this.isResizeBound = true;
};
/**
* Unbind layout to window resizing
*/
Outlayer.prototype.unbindResize = function() {
if ( this.isResizeBound ) {
eventie.unbind( window, 'resize', this );
}
this.isResizeBound = false;
};
// original debounce by John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
// this fires every resize
Outlayer.prototype.onresize = function() {
if ( this.resizeTimeout ) {
clearTimeout( this.resizeTimeout );
}
var _this = this;
function delayed() {
_this.resize();
delete _this.resizeTimeout;
}
this.resizeTimeout = setTimeout( delayed, 100 );
};
// debounced, layout on resize
Outlayer.prototype.resize = function() {
// don't trigger if size did not change
// or if resize was unbound. See #9
if ( !this.isResizeBound || !this.needsResizeLayout() ) {
return;
}
this.layout();
};
/**
* check if layout is needed post layout
* @returns Boolean
*/
Outlayer.prototype.needsResizeLayout = function() {
var size = getSize( this.element );
// check that this.size and size are there
// IE8 triggers resize on body size change, so they might not be
var hasSizes = this.size && size;
return hasSizes && size.innerWidth !== this.size.innerWidth;
};
// -------------------------- methods -------------------------- //
/**
* add items to Outlayer instance
* @param {Array or NodeList or Element} elems
* @returns {Array} items - Outlayer.Items
**/
Outlayer.prototype.addItems = function( elems ) {
var items = this._itemize( elems );
// add items to collection
if ( items.length ) {
this.items = this.items.concat( items );
}
return items;
};
/**
* Layout newly-appended item elements
* @param {Array or NodeList or Element} elems
*/
Outlayer.prototype.appended = function( elems ) {
var items = this.addItems( elems );
if ( !items.length ) {
return;
}
// layout and reveal just the new items
this.layoutItems( items, true );
this.reveal( items );
};
/**
* Layout prepended elements
* @param {Array or NodeList or Element} elems
*/
Outlayer.prototype.prepended = function( elems ) {
var items = this._itemize( elems );
if ( !items.length ) {
return;
}
// add items to beginning of collection
var previousItems = this.items.slice(0);
this.items = items.concat( previousItems );
// start new layout
this._resetLayout();
this._manageStamps();
// layout new stuff without transition
this.layoutItems( items, true );
this.reveal( items );
// layout previous items
this.layoutItems( previousItems );
};
/**
* reveal a collection of items
* @param {Array of Outlayer.Items} items
*/
Outlayer.prototype.reveal = function( items ) {
this._emitCompleteOnItems( 'reveal', items );
var len = items && items.length;
for ( var i=0; len && i < len; i++ ) {
var item = items[i];
item.reveal();
}
};
/**
* hide a collection of items
* @param {Array of Outlayer.Items} items
*/
Outlayer.prototype.hide = function( items ) {
this._emitCompleteOnItems( 'hide', items );
var len = items && items.length;
for ( var i=0; len && i < len; i++ ) {
var item = items[i];
item.hide();
}
};
/**
* reveal item elements
* @param {Array}, {Element}, {NodeList} items
*/
Outlayer.prototype.revealItemElements = function( elems ) {
var items = this.getItems( elems );
this.reveal( items );
};
/**
* hide item elements
* @param {Array}, {Element}, {NodeList} items
*/
Outlayer.prototype.hideItemElements = function( elems ) {
var items = this.getItems( elems );
this.hide( items );
};
/**
* get Outlayer.Item, given an Element
* @param {Element} elem
* @param {Function} callback
* @returns {Outlayer.Item} item
*/
Outlayer.prototype.getItem = function( elem ) {
// loop through items to get the one that matches
for ( var i=0, len = this.items.length; i < len; i++ ) {
var item = this.items[i];
if ( item.element === elem ) {
// return item
return item;
}
}
};
/**
* get collection of Outlayer.Items, given Elements
* @param {Array} elems
* @returns {Array} items - Outlayer.Items
*/
Outlayer.prototype.getItems = function( elems ) {
elems = utils.makeArray( elems );
var items = [];
for ( var i=0, len = elems.length; i < len; i++ ) {
var elem = elems[i];
var item = this.getItem( elem );
if ( item ) {
items.push( item );
}
}
return items;
};
/**
* remove element(s) from instance and DOM
* @param {Array or NodeList or Element} elems
*/
Outlayer.prototype.remove = function( elems ) {
var removeItems = this.getItems( elems );
this._emitCompleteOnItems( 'remove', removeItems );
// bail if no items to remove
if ( !removeItems || !removeItems.length ) {
return;
}
for ( var i=0, len = removeItems.length; i < len; i++ ) {
var item = removeItems[i];
item.remove();
// remove item from collection
utils.removeFrom( this.items, item );
}
};
// ----- destroy ----- //
// remove and disable Outlayer instance
Outlayer.prototype.destroy = function() {
// clean up dynamic styles
var style = this.element.style;
style.height = '';
style.position = '';
style.width = '';
// destroy items
for ( var i=0, len = this.items.length; i < len; i++ ) {
var item = this.items[i];
item.destroy();
}
this.unbindResize();
var id = this.element.outlayerGUID;
delete instances[ id ]; // remove reference to instance by id
delete this.element.outlayerGUID;
// remove data for jQuery
if ( jQuery ) {
jQuery.removeData( this.element, this.constructor.namespace );
}
};
// -------------------------- data -------------------------- //
/**
* get Outlayer instance from element
* @param {Element} elem
* @returns {Outlayer}
*/
Outlayer.data = function( elem ) {
elem = utils.getQueryElement( elem );
var id = elem && elem.outlayerGUID;
return id && instances[ id ];
};
// -------------------------- create Outlayer class -------------------------- //
/**
* create a layout class
* @param {String} namespace
*/
Outlayer.create = function( namespace, options ) {
// sub-class Outlayer
function Layout() {
Outlayer.apply( this, arguments );
}
// inherit Outlayer prototype, use Object.create if there
if ( Object.create ) {
Layout.prototype = Object.create( Outlayer.prototype );
} else {
utils.extend( Layout.prototype, Outlayer.prototype );
}
// set contructor, used for namespace and Item
Layout.prototype.constructor = Layout;
Layout.defaults = utils.extend( {}, Outlayer.defaults );
// apply new options
utils.extend( Layout.defaults, options );
// keep prototype.settings for backwards compatibility (Packery v1.2.0)
Layout.prototype.settings = {};
Layout.namespace = namespace;
Layout.data = Outlayer.data;
// sub-class Item
Layout.Item = function LayoutItem() {
Item.apply( this, arguments );
};
Layout.Item.prototype = new Item();
// -------------------------- declarative -------------------------- //
utils.htmlInit( Layout, namespace );
// -------------------------- jQuery bridge -------------------------- //
// make into jQuery plugin
if ( jQuery && jQuery.bridget ) {
jQuery.bridget( namespace, Layout );
}
return Layout;
};
// ----- fin ----- //
// back in global
Outlayer.Item = Item;
return Outlayer;
}));
/**
* Isotope Item
**/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'isotope/js/item',[
'outlayer/outlayer'
],
factory );
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
require('outlayer')
);
} else {
// browser global
window.Isotope = window.Isotope || {};
window.Isotope.Item = factory(
window.Outlayer
);
}
}( window, function factory( Outlayer ) {
'use strict';
// -------------------------- Item -------------------------- //
// sub-class Outlayer Item
function Item() {
Outlayer.Item.apply( this, arguments );
}
Item.prototype = new Outlayer.Item();
Item.prototype._create = function() {
// assign id, used for original-order sorting
this.id = this.layout.itemGUID++;
Outlayer.Item.prototype._create.call( this );
this.sortData = {};
};
Item.prototype.updateSortData = function() {
if ( this.isIgnored ) {
return;
}
// default sorters
this.sortData.id = this.id;
// for backward compatibility
this.sortData['original-order'] = this.id;
this.sortData.random = Math.random();
// go thru getSortData obj and apply the sorters
var getSortData = this.layout.options.getSortData;
var sorters = this.layout._sorters;
for ( var key in getSortData ) {
var sorter = sorters[ key ];
this.sortData[ key ] = sorter( this.element, this );
}
};
var _destroy = Item.prototype.destroy;
Item.prototype.destroy = function() {
// call super
_destroy.apply( this, arguments );
// reset display, #741
this.css({
display: ''
});
};
return Item;
}));
/**
* Isotope LayoutMode
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'isotope/js/layout-mode',[
'get-size/get-size',
'outlayer/outlayer'
],
factory );
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
require('get-size'),
require('outlayer')
);
} else {
// browser global
window.Isotope = window.Isotope || {};
window.Isotope.LayoutMode = factory(
window.getSize,
window.Outlayer
);
}
}( window, function factory( getSize, Outlayer ) {
'use strict';
// layout mode class
function LayoutMode( isotope ) {
this.isotope = isotope;
// link properties
if ( isotope ) {
this.options = isotope.options[ this.namespace ];
this.element = isotope.element;
this.items = isotope.filteredItems;
this.size = isotope.size;
}
}
/**
* some methods should just defer to default Outlayer method
* and reference the Isotope instance as `this`
**/
( function() {
var facadeMethods = [
'_resetLayout',
'_getItemLayoutPosition',
'_manageStamp',
'_getContainerSize',
'_getElementOffset',
'needsResizeLayout'
];
for ( var i=0, len = facadeMethods.length; i < len; i++ ) {
var methodName = facadeMethods[i];
LayoutMode.prototype[ methodName ] = getOutlayerMethod( methodName );
}
function getOutlayerMethod( methodName ) {
return function() {
return Outlayer.prototype[ methodName ].apply( this.isotope, arguments );
};
}
})();
// ----- ----- //
// for horizontal layout modes, check vertical size
LayoutMode.prototype.needsVerticalResizeLayout = function() {
// don't trigger if size did not change
var size = getSize( this.isotope.element );
// check that this.size and size are there
// IE8 triggers resize on body size change, so they might not be
var hasSizes = this.isotope.size && size;
return hasSizes && size.innerHeight != this.isotope.size.innerHeight;
};
// ----- measurements ----- //
LayoutMode.prototype._getMeasurement = function() {
this.isotope._getMeasurement.apply( this, arguments );
};
LayoutMode.prototype.getColumnWidth = function() {
this.getSegmentSize( 'column', 'Width' );
};
LayoutMode.prototype.getRowHeight = function() {
this.getSegmentSize( 'row', 'Height' );
};
/**
* get columnWidth or rowHeight
* segment: 'column' or 'row'
* size 'Width' or 'Height'
**/
LayoutMode.prototype.getSegmentSize = function( segment, size ) {
var segmentName = segment + size;
var outerSize = 'outer' + size;
// columnWidth / outerWidth // rowHeight / outerHeight
this._getMeasurement( segmentName, outerSize );
// got rowHeight or columnWidth, we can chill
if ( this[ segmentName ] ) {
return;
}
// fall back to item of first element
var firstItemSize = this.getFirstItemSize();
this[ segmentName ] = firstItemSize && firstItemSize[ outerSize ] ||
// or size of container
this.isotope.size[ 'inner' + size ];
};
LayoutMode.prototype.getFirstItemSize = function() {
var firstItem = this.isotope.filteredItems[0];
return firstItem && firstItem.element && getSize( firstItem.element );
};
// ----- methods that should reference isotope ----- //
LayoutMode.prototype.layout = function() {
this.isotope.layout.apply( this.isotope, arguments );
};
LayoutMode.prototype.getSize = function() {
this.isotope.getSize();
this.size = this.isotope.size;
};
// -------------------------- create -------------------------- //
LayoutMode.modes = {};
LayoutMode.create = function( namespace, options ) {
function Mode() {
LayoutMode.apply( this, arguments );
}
Mode.prototype = new LayoutMode();
// default options
if ( options ) {
Mode.options = options;
}
Mode.prototype.namespace = namespace;
// register in Isotope
LayoutMode.modes[ namespace ] = Mode;
return Mode;
};
return LayoutMode;
}));
/*!
* Masonry v3.3.1
* Cascading grid layout library
* http://masonry.desandro.com
* MIT License
* by David DeSandro
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define === 'function' && define.amd ) {
// AMD
define( 'masonry/masonry',[
'outlayer/outlayer',
'get-size/get-size',
'fizzy-ui-utils/utils'
],
factory );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = factory(
require('outlayer'),
require('get-size'),
require('fizzy-ui-utils')
);
} else {
// browser global
window.Masonry = factory(
window.Outlayer,
window.getSize,
window.fizzyUIUtils
);
}
}( window, function factory( Outlayer, getSize, utils ) {
// -------------------------- masonryDefinition -------------------------- //
// create an Outlayer layout class
var Masonry = Outlayer.create('masonry');
Masonry.prototype._resetLayout = function() {
this.getSize();
this._getMeasurement( 'columnWidth', 'outerWidth' );
this._getMeasurement( 'gutter', 'outerWidth' );
this.measureColumns();
// reset column Y
var i = this.cols;
this.colYs = [];
while (i--) {
this.colYs.push( 0 );
}
this.maxY = 0;
};
Masonry.prototype.measureColumns = function() {
this.getContainerWidth();
// if columnWidth is 0, default to outerWidth of first item
if ( !this.columnWidth ) {
var firstItem = this.items[0];
var firstItemElem = firstItem && firstItem.element;
// columnWidth fall back to item of first element
this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth ||
// if first elem has no width, default to size of container
this.containerWidth;
}
var columnWidth = this.columnWidth += this.gutter;
// calculate columns
var containerWidth = this.containerWidth + this.gutter;
var cols = containerWidth / columnWidth;
// fix rounding errors, typically with gutters
var excess = columnWidth - containerWidth % columnWidth;
// if overshoot is less than a pixel, round up, otherwise floor it
var mathMethod = excess && excess < 1 ? 'round' : 'floor';
cols = Math[ mathMethod ]( cols );
this.cols = Math.max( cols, 1 );
};
Masonry.prototype.getContainerWidth = function() {
// container is parent if fit width
var container = this.options.isFitWidth ? this.element.parentNode : this.element;
// check that this.size and size are there
// IE8 triggers resize on body size change, so they might not be
var size = getSize( container );
this.containerWidth = size && size.innerWidth;
};
Masonry.prototype._getItemLayoutPosition = function( item ) {
item.getSize();
// how many columns does this brick span
var remainder = item.size.outerWidth % this.columnWidth;
var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
// round if off by 1 pixel, otherwise use ceil
var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth );
colSpan = Math.min( colSpan, this.cols );
var colGroup = this._getColGroup( colSpan );
// get the minimum Y value from the columns
var minimumY = Math.min.apply( Math, colGroup );
var shortColIndex = utils.indexOf( colGroup, minimumY );
// position the brick
var position = {
x: this.columnWidth * shortColIndex,
y: minimumY
};
// apply setHeight to necessary columns
var setHeight = minimumY + item.size.outerHeight;
var setSpan = this.cols + 1 - colGroup.length;
for ( var i = 0; i < setSpan; i++ ) {
this.colYs[ shortColIndex + i ] = setHeight;
}
return position;
};
/**
* @param {Number} colSpan - number of columns the element spans
* @returns {Array} colGroup
*/
Masonry.prototype._getColGroup = function( colSpan ) {
if ( colSpan < 2 ) {
// if brick spans only one column, use all the column Ys
return this.colYs;
}
var colGroup = [];
// how many different places could this brick fit horizontally
var groupCount = this.cols + 1 - colSpan;
// for each group potential horizontal position
for ( var i = 0; i < groupCount; i++ ) {
// make an array of colY values for that one group
var groupColYs = this.colYs.slice( i, i + colSpan );
// and get the max value of the array
colGroup[i] = Math.max.apply( Math, groupColYs );
}
return colGroup;
};
Masonry.prototype._manageStamp = function( stamp ) {
var stampSize = getSize( stamp );
var offset = this._getElementOffset( stamp );
// get the columns that this stamp affects
var firstX = this.options.isOriginLeft ? offset.left : offset.right;
var lastX = firstX + stampSize.outerWidth;
var firstCol = Math.floor( firstX / this.columnWidth );
firstCol = Math.max( 0, firstCol );
var lastCol = Math.floor( lastX / this.columnWidth );
// lastCol should not go over if multiple of columnWidth #425
lastCol -= lastX % this.columnWidth ? 0 : 1;
lastCol = Math.min( this.cols - 1, lastCol );
// set colYs to bottom of the stamp
var stampMaxY = ( this.options.isOriginTop ? offset.top : offset.bottom ) +
stampSize.outerHeight;
for ( var i = firstCol; i <= lastCol; i++ ) {
this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
}
};
Masonry.prototype._getContainerSize = function() {
this.maxY = Math.max.apply( Math, this.colYs );
var size = {
height: this.maxY
};
if ( this.options.isFitWidth ) {
size.width = this._getContainerFitWidth();
}
return size;
};
Masonry.prototype._getContainerFitWidth = function() {
var unusedCols = 0;
// count unused columns
var i = this.cols;
while ( --i ) {
if ( this.colYs[i] !== 0 ) {
break;
}
unusedCols++;
}
// fit container to columns that have been used
return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
};
Masonry.prototype.needsResizeLayout = function() {
var previousWidth = this.containerWidth;
this.getContainerWidth();
return previousWidth !== this.containerWidth;
};
return Masonry;
}));
/*!
* Masonry layout mode
* sub-classes Masonry
* http://masonry.desandro.com
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'isotope/js/layout-modes/masonry',[
'../layout-mode',
'masonry/masonry'
],
factory );
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
require('../layout-mode'),
require('masonry-layout')
);
} else {
// browser global
factory(
window.Isotope.LayoutMode,
window.Masonry
);
}
}( window, function factory( LayoutMode, Masonry ) {
'use strict';
// -------------------------- helpers -------------------------- //
// extend objects
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
// -------------------------- masonryDefinition -------------------------- //
// create an Outlayer layout class
var MasonryMode = LayoutMode.create('masonry');
// save on to these methods
var _getElementOffset = MasonryMode.prototype._getElementOffset;
var layout = MasonryMode.prototype.layout;
var _getMeasurement = MasonryMode.prototype._getMeasurement;
// sub-class Masonry
extend( MasonryMode.prototype, Masonry.prototype );
// set back, as it was overwritten by Masonry
MasonryMode.prototype._getElementOffset = _getElementOffset;
MasonryMode.prototype.layout = layout;
MasonryMode.prototype._getMeasurement = _getMeasurement;
var measureColumns = MasonryMode.prototype.measureColumns;
MasonryMode.prototype.measureColumns = function() {
// set items, used if measuring first item
this.items = this.isotope.filteredItems;
measureColumns.call( this );
};
// HACK copy over isOriginLeft/Top options
var _manageStamp = MasonryMode.prototype._manageStamp;
MasonryMode.prototype._manageStamp = function() {
this.options.isOriginLeft = this.isotope.options.isOriginLeft;
this.options.isOriginTop = this.isotope.options.isOriginTop;
_manageStamp.apply( this, arguments );
};
return MasonryMode;
}));
/**
* fitRows layout mode
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'isotope/js/layout-modes/fit-rows',[
'../layout-mode'
],
factory );
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
require('../layout-mode')
);
} else {
// browser global
factory(
window.Isotope.LayoutMode
);
}
}( window, function factory( LayoutMode ) {
'use strict';
var FitRows = LayoutMode.create('fitRows');
FitRows.prototype._resetLayout = function() {
this.x = 0;
this.y = 0;
this.maxY = 0;
this._getMeasurement( 'gutter', 'outerWidth' );
};
FitRows.prototype._getItemLayoutPosition = function( item ) {
item.getSize();
var itemWidth = item.size.outerWidth + this.gutter;
// if this element cannot fit in the current row
var containerWidth = this.isotope.size.innerWidth + this.gutter;
if ( this.x !== 0 && itemWidth + this.x > containerWidth ) {
this.x = 0;
this.y = this.maxY;
}
var position = {
x: this.x,
y: this.y
};
this.maxY = Math.max( this.maxY, this.y + item.size.outerHeight );
this.x += itemWidth;
return position;
};
FitRows.prototype._getContainerSize = function() {
return { height: this.maxY };
};
return FitRows;
}));
/**
* vertical layout mode
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'isotope/js/layout-modes/vertical',[
'../layout-mode'
],
factory );
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
require('../layout-mode')
);
} else {
// browser global
factory(
window.Isotope.LayoutMode
);
}
}( window, function factory( LayoutMode ) {
'use strict';
var Vertical = LayoutMode.create( 'vertical', {
horizontalAlignment: 0
});
Vertical.prototype._resetLayout = function() {
this.y = 0;
};
Vertical.prototype._getItemLayoutPosition = function( item ) {
item.getSize();
var x = ( this.isotope.size.innerWidth - item.size.outerWidth ) *
this.options.horizontalAlignment;
var y = this.y;
this.y += item.size.outerHeight;
return { x: x, y: y };
};
Vertical.prototype._getContainerSize = function() {
return { height: this.y };
};
return Vertical;
}));
/*!
* Isotope v2.2.2
*
* Licensed GPLv3 for open source use
* or Isotope Commercial License for commercial use
*
* http://isotope.metafizzy.co
* Copyright 2015 Metafizzy
*/
( function( window, factory ) {
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( [
'outlayer/outlayer',
'get-size/get-size',
'matches-selector/matches-selector',
'fizzy-ui-utils/utils',
'isotope/js/item',
'isotope/js/layout-mode',
// include default layout modes
'isotope/js/layout-modes/masonry',
'isotope/js/layout-modes/fit-rows',
'isotope/js/layout-modes/vertical'
],
function( Outlayer, getSize, matchesSelector, utils, Item, LayoutMode ) {
return factory( window, Outlayer, getSize, matchesSelector, utils, Item, LayoutMode );
});
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
window,
require('outlayer'),
require('get-size'),
require('desandro-matches-selector'),
require('fizzy-ui-utils'),
require('./item'),
require('./layout-mode'),
// include default layout modes
require('./layout-modes/masonry'),
require('./layout-modes/fit-rows'),
require('./layout-modes/vertical')
);
} else {
// browser global
window.Isotope = factory(
window,
window.Outlayer,
window.getSize,
window.matchesSelector,
window.fizzyUIUtils,
window.Isotope.Item,
window.Isotope.LayoutMode
);
}
}( window, function factory( window, Outlayer, getSize, matchesSelector, utils,
Item, LayoutMode ) {
// -------------------------- vars -------------------------- //
var jQuery = window.jQuery;
// -------------------------- helpers -------------------------- //
var trim = String.prototype.trim ?
function( str ) {
return str.trim();
} :
function( str ) {
return str.replace( /^\s+|\s+$/g, '' );
};
var docElem = document.documentElement;
var getText = docElem.textContent ?
function( elem ) {
return elem.textContent;
} :
function( elem ) {
return elem.innerText;
};
// -------------------------- isotopeDefinition -------------------------- //
// create an Outlayer layout class
var Isotope = Outlayer.create( 'isotope', {
layoutMode: "masonry",
isJQueryFiltering: true,
sortAscending: true
});
Isotope.Item = Item;
Isotope.LayoutMode = LayoutMode;
Isotope.prototype._create = function() {
this.itemGUID = 0;
// functions that sort items
this._sorters = {};
this._getSorters();
// call super
Outlayer.prototype._create.call( this );
// create layout modes
this.modes = {};
// start filteredItems with all items
this.filteredItems = this.items;
// keep of track of sortBys
this.sortHistory = [ 'original-order' ];
// create from registered layout modes
for ( var name in LayoutMode.modes ) {
this._initLayoutMode( name );
}
};
Isotope.prototype.reloadItems = function() {
// reset item ID counter
this.itemGUID = 0;
// call super
Outlayer.prototype.reloadItems.call( this );
};
Isotope.prototype._itemize = function() {
var items = Outlayer.prototype._itemize.apply( this, arguments );
// assign ID for original-order
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
item.id = this.itemGUID++;
}
this._updateItemsSortData( items );
return items;
};
// -------------------------- layout -------------------------- //
Isotope.prototype._initLayoutMode = function( name ) {
var Mode = LayoutMode.modes[ name ];
// set mode options
// HACK extend initial options, back-fill in default options
var initialOpts = this.options[ name ] || {};
this.options[ name ] = Mode.options ?
utils.extend( Mode.options, initialOpts ) : initialOpts;
// init layout mode instance
this.modes[ name ] = new Mode( this );
};
Isotope.prototype.layout = function() {
// if first time doing layout, do all magic
if ( !this._isLayoutInited && this.options.isInitLayout ) {
this.arrange();
return;
}
this._layout();
};
// private method to be used in layout() & magic()
Isotope.prototype._layout = function() {
// don't animate first layout
var isInstant = this._getIsInstant();
// layout flow
this._resetLayout();
this._manageStamps();
this.layoutItems( this.filteredItems, isInstant );
// flag for initalized
this._isLayoutInited = true;
};
// filter + sort + layout
Isotope.prototype.arrange = function( opts ) {
// set any options pass
this.option( opts );
this._getIsInstant();
// filter, sort, and layout
// filter
var filtered = this._filter( this.items );
this.filteredItems = filtered.matches;
var _this = this;
function hideReveal() {
_this.reveal( filtered.needReveal );
_this.hide( filtered.needHide );
}
this._bindArrangeComplete();
if ( this._isInstant ) {
this._noTransition( hideReveal );
} else {
hideReveal();
}
this._sort();
this._layout();
};
// alias to _init for main plugin method
Isotope.prototype._init = Isotope.prototype.arrange;
// HACK
// Don't animate/transition first layout
// Or don't animate/transition other layouts
Isotope.prototype._getIsInstant = function() {
var isInstant = this.options.isLayoutInstant !== undefined ?
this.options.isLayoutInstant : !this._isLayoutInited;
this._isInstant = isInstant;
return isInstant;
};
// listen for layoutComplete, hideComplete and revealComplete
// to trigger arrangeComplete
Isotope.prototype._bindArrangeComplete = function() {
// listen for 3 events to trigger arrangeComplete
var isLayoutComplete, isHideComplete, isRevealComplete;
var _this = this;
function arrangeParallelCallback() {
if ( isLayoutComplete && isHideComplete && isRevealComplete ) {
_this.dispatchEvent( 'arrangeComplete', null, [ _this.filteredItems ] );
}
}
this.once( 'layoutComplete', function() {
isLayoutComplete = true;
arrangeParallelCallback();
});
this.once( 'hideComplete', function() {
isHideComplete = true;
arrangeParallelCallback();
});
this.once( 'revealComplete', function() {
isRevealComplete = true;
arrangeParallelCallback();
});
};
// -------------------------- filter -------------------------- //
Isotope.prototype._filter = function( items ) {
var filter = this.options.filter;
filter = filter || '*';
var matches = [];
var hiddenMatched = [];
var visibleUnmatched = [];
var test = this._getFilterTest( filter );
// test each item
for ( var i=0, len = items.length; i < len; i++ ) {
var item = items[i];
if ( item.isIgnored ) {
continue;
}
// add item to either matched or unmatched group
var isMatched = test( item );
// item.isFilterMatched = isMatched;
// add to matches if its a match
if ( isMatched ) {
matches.push( item );
}
// add to additional group if item needs to be hidden or revealed
if ( isMatched && item.isHidden ) {
hiddenMatched.push( item );
} else if ( !isMatched && !item.isHidden ) {
visibleUnmatched.push( item );
}
}
// return collections of items to be manipulated
return {
matches: matches,
needReveal: hiddenMatched,
needHide: visibleUnmatched
};
};
// get a jQuery, function, or a matchesSelector test given the filter
Isotope.prototype._getFilterTest = function( filter ) {
if ( jQuery && this.options.isJQueryFiltering ) {
// use jQuery
return function( item ) {
return jQuery( item.element ).is( filter );
};
}
if ( typeof filter == 'function' ) {
// use filter as function
return function( item ) {
return filter( item.element );
};
}
// default, use filter as selector string
return function( item ) {
return matchesSelector( item.element, filter );
};
};
// -------------------------- sorting -------------------------- //
/**
* @params {Array} elems
* @public
*/
Isotope.prototype.updateSortData = function( elems ) {
// get items
var items;
if ( elems ) {
elems = utils.makeArray( elems );
items = this.getItems( elems );
} else {
// update all items if no elems provided
items = this.items;
}
this._getSorters();
this._updateItemsSortData( items );
};
Isotope.prototype._getSorters = function() {
var getSortData = this.options.getSortData;
for ( var key in getSortData ) {
var sorter = getSortData[ key ];
this._sorters[ key ] = mungeSorter( sorter );
}
};
/**
* @params {Array} items - of Isotope.Items
* @private
*/
Isotope.prototype._updateItemsSortData = function( items ) {
// do not update if no items
var len = items && items.length;
for ( var i=0; len && i < len; i++ ) {
var item = items[i];
item.updateSortData();
}
};
// ----- munge sorter ----- //
// encapsulate this, as we just need mungeSorter
// other functions in here are just for munging
var mungeSorter = ( function() {
// add a magic layer to sorters for convienent shorthands
// `.foo-bar` will use the text of .foo-bar querySelector
// `[foo-bar]` will use attribute
// you can also add parser
// `.foo-bar parseInt` will parse that as a number
function mungeSorter( sorter ) {
// if not a string, return function or whatever it is
if ( typeof sorter != 'string' ) {
return sorter;
}
// parse the sorter string
var args = trim( sorter ).split(' ');
var query = args[0];
// check if query looks like [an-attribute]
var attrMatch = query.match( /^\[(.+)\]$/ );
var attr = attrMatch && attrMatch[1];
var getValue = getValueGetter( attr, query );
// use second argument as a parser
var parser = Isotope.sortDataParsers[ args[1] ];
// parse the value, if there was a parser
sorter = parser ? function( elem ) {
return elem && parser( getValue( elem ) );
} :
// otherwise just return value
function( elem ) {
return elem && getValue( elem );
};
return sorter;
}
// get an attribute getter, or get text of the querySelector
function getValueGetter( attr, query ) {
var getValue;
// if query looks like [foo-bar], get attribute
if ( attr ) {
getValue = function( elem ) {
return elem.getAttribute( attr );
};
} else {
// otherwise, assume its a querySelector, and get its text
getValue = function( elem ) {
var child = elem.querySelector( query );
return child && getText( child );
};
}
return getValue;
}
return mungeSorter;
})();
// parsers used in getSortData shortcut strings
Isotope.sortDataParsers = {
'parseInt': function( val ) {
return parseInt( val, 10 );
},
'parseFloat': function( val ) {
return parseFloat( val );
}
};
// ----- sort method ----- //
// sort filteredItem order
Isotope.prototype._sort = function() {
var sortByOpt = this.options.sortBy;
if ( !sortByOpt ) {
return;
}
// concat all sortBy and sortHistory
var sortBys = [].concat.apply( sortByOpt, this.sortHistory );
// sort magic
var itemSorter = getItemSorter( sortBys, this.options.sortAscending );
this.filteredItems.sort( itemSorter );
// keep track of sortBy History
if ( sortByOpt != this.sortHistory[0] ) {
// add to front, oldest goes in last
this.sortHistory.unshift( sortByOpt );
}
};
// returns a function used for sorting
function getItemSorter( sortBys, sortAsc ) {
return function sorter( itemA, itemB ) {
// cycle through all sortKeys
for ( var i = 0, len = sortBys.length; i < len; i++ ) {
var sortBy = sortBys[i];
var a = itemA.sortData[ sortBy ];
var b = itemB.sortData[ sortBy ];
if ( a > b || a < b ) {
// if sortAsc is an object, use the value given the sortBy key
var isAscending = sortAsc[ sortBy ] !== undefined ? sortAsc[ sortBy ] : sortAsc;
var direction = isAscending ? 1 : -1;
return ( a > b ? 1 : -1 ) * direction;
}
}
return 0;
};
}
// -------------------------- methods -------------------------- //
// get layout mode
Isotope.prototype._mode = function() {
var layoutMode = this.options.layoutMode;
var mode = this.modes[ layoutMode ];
if ( !mode ) {
// TODO console.error
throw new Error( 'No layout mode: ' + layoutMode );
}
// HACK sync mode's options
// any options set after init for layout mode need to be synced
mode.options = this.options[ layoutMode ];
return mode;
};
Isotope.prototype._resetLayout = function() {
// trigger original reset layout
Outlayer.prototype._resetLayout.call( this );
this._mode()._resetLayout();
};
Isotope.prototype._getItemLayoutPosition = function( item ) {
return this._mode()._getItemLayoutPosition( item );
};
Isotope.prototype._manageStamp = function( stamp ) {
this._mode()._manageStamp( stamp );
};
Isotope.prototype._getContainerSize = function() {
return this._mode()._getContainerSize();
};
Isotope.prototype.needsResizeLayout = function() {
return this._mode().needsResizeLayout();
};
// -------------------------- adding & removing -------------------------- //
// HEADS UP overwrites default Outlayer appended
Isotope.prototype.appended = function( elems ) {
var items = this.addItems( elems );
if ( !items.length ) {
return;
}
// filter, layout, reveal new items
var filteredItems = this._filterRevealAdded( items );
// add to filteredItems
this.filteredItems = this.filteredItems.concat( filteredItems );
};
// HEADS UP overwrites default Outlayer prepended
Isotope.prototype.prepended = function( elems ) {
var items = this._itemize( elems );
if ( !items.length ) {
return;
}
// start new layout
this._resetLayout();
this._manageStamps();
// filter, layout, reveal new items
var filteredItems = this._filterRevealAdded( items );
// layout previous items
this.layoutItems( this.filteredItems );
// add to items and filteredItems
this.filteredItems = filteredItems.concat( this.filteredItems );
this.items = items.concat( this.items );
};
Isotope.prototype._filterRevealAdded = function( items ) {
var filtered = this._filter( items );
this.hide( filtered.needHide );
// reveal all new items
this.reveal( filtered.matches );
// layout new items, no transition
this.layoutItems( filtered.matches, true );
return filtered.matches;
};
/**
* Filter, sort, and layout newly-appended item elements
* @param {Array or NodeList or Element} elems
*/
Isotope.prototype.insert = function( elems ) {
var items = this.addItems( elems );
if ( !items.length ) {
return;
}
// append item elements
var i, item;
var len = items.length;
for ( i=0; i < len; i++ ) {
item = items[i];
this.element.appendChild( item.element );
}
// filter new stuff
var filteredInsertItems = this._filter( items ).matches;
// set flag
for ( i=0; i < len; i++ ) {
items[i].isLayoutInstant = true;
}
this.arrange();
// reset flag
for ( i=0; i < len; i++ ) {
delete items[i].isLayoutInstant;
}
this.reveal( filteredInsertItems );
};
var _remove = Isotope.prototype.remove;
Isotope.prototype.remove = function( elems ) {
elems = utils.makeArray( elems );
var removeItems = this.getItems( elems );
// do regular thing
_remove.call( this, elems );
// bail if no items to remove
var len = removeItems && removeItems.length;
if ( !len ) {
return;
}
// remove elems from filteredItems
for ( var i=0; i < len; i++ ) {
var item = removeItems[i];
// remove item from collection
utils.removeFrom( this.filteredItems, item );
}
};
Isotope.prototype.shuffle = function() {
// update random sortData
for ( var i=0, len = this.items.length; i < len; i++ ) {
var item = this.items[i];
item.sortData.random = Math.random();
}
this.options.sortBy = 'random';
this._sort();
this._layout();
};
/**
* trigger fn without transition
* kind of hacky to have this in the first place
* @param {Function} fn
* @returns ret
* @private
*/
Isotope.prototype._noTransition = function( fn ) {
// save transitionDuration before disabling
var transitionDuration = this.options.transitionDuration;
// disable transition
this.options.transitionDuration = 0;
// do it
var returnValue = fn.call( this );
// re-enable transition for reveal
this.options.transitionDuration = transitionDuration;
return returnValue;
};
// ----- helper methods ----- //
/**
* getter method for getting filtered item elements
* @returns {Array} elems - collection of item elements
*/
Isotope.prototype.getFilteredItemElements = function() {
var elems = [];
for ( var i=0, len = this.filteredItems.length; i < len; i++ ) {
elems.push( this.filteredItems[i].element );
}
return elems;
};
// ----- ----- //
return Isotope;
}));
/*!
* Packery layout mode PACKAGED v1.1.1
* sub-classes Packery
* http://packery.metafizzy.co
*/
!function (a) { function b(a) { return new RegExp("(^|\\s+)" + a + "(\\s+|$)") } function c(a, b) { var c = d(a, b) ? f : e; c(a, b) } var d, e, f; "classList" in document.documentElement ? (d = function (a, b) { return a.classList.contains(b) }, e = function (a, b) { a.classList.add(b) }, f = function (a, b) { a.classList.remove(b) }) : (d = function (a, c) { return b(c).test(a.className) }, e = function (a, b) { d(a, b) || (a.className = a.className + " " + b) }, f = function (a, c) { a.className = a.className.replace(b(c), " ") }); var g = { hasClass: d, addClass: e, removeClass: f, toggleClass: c, has: d, add: e, remove: f, toggle: c }; "function" == typeof define && define.amd ? define("classie/classie", g) : "object" == typeof exports ? module.exports = g : a.classie = g }(window), function (a) { function b() { function a(b) { for (var c in a.defaults) this[c] = a.defaults[c]; for (c in b) this[c] = b[c] } return c.Rect = a, a.defaults = { x: 0, y: 0, width: 0, height: 0 }, a.prototype.contains = function (a) { var b = a.width || 0, c = a.height || 0; return this.x <= a.x && this.y <= a.y && this.x + this.width >= a.x + b && this.y + this.height >= a.y + c }, a.prototype.overlaps = function (a) { var b = this.x + this.width, c = this.y + this.height, d = a.x + a.width, e = a.y + a.height; return this.x < d && b > a.x && this.y < e && c > a.y }, a.prototype.getMaximalFreeRects = function (b) { if (!this.overlaps(b)) return !1; var c, d = [], e = this.x + this.width, f = this.y + this.height, g = b.x + b.width, h = b.y + b.height; return this.y < b.y && (c = new a({ x: this.x, y: this.y, width: this.width, height: b.y - this.y }), d.push(c)), e > g && (c = new a({ x: g, y: this.y, width: e - g, height: this.height }), d.push(c)), f > h && (c = new a({ x: this.x, y: h, width: this.width, height: f - h }), d.push(c)), this.x < b.x && (c = new a({ x: this.x, y: this.y, width: b.x - this.x, height: this.height }), d.push(c)), d }, a.prototype.canFit = function (a) { return this.width >= a.width && this.height >= a.height }, a } var c = a.Packery = function () { }; "function" == typeof define && define.amd ? define("packery/js/rect", b) : "object" == typeof exports ? module.exports = b() : (a.Packery = a.Packery || {}, a.Packery.Rect = b()) }(window), function (a) { function b(a) { function b(a, b, c) { this.width = a || 0, this.height = b || 0, this.sortDirection = c || "downwardLeftToRight", this.reset() } b.prototype.reset = function () { this.spaces = [], this.newSpaces = []; var b = new a({ x: 0, y: 0, width: this.width, height: this.height }); this.spaces.push(b), this.sorter = c[this.sortDirection] || c.downwardLeftToRight }, b.prototype.pack = function (a) { for (var b = 0, c = this.spaces.length; c > b; b++) { var d = this.spaces[b]; if (d.canFit(a)) { this.placeInSpace(a, d); break } } }, b.prototype.placeInSpace = function (a, b) { a.x = b.x, a.y = b.y, this.placed(a) }, b.prototype.placed = function (a) { for (var b = [], c = 0, d = this.spaces.length; d > c; c++) { var e = this.spaces[c], f = e.getMaximalFreeRects(a); f ? b.push.apply(b, f) : b.push(e) } this.spaces = b, this.mergeSortSpaces() }, b.prototype.mergeSortSpaces = function () { b.mergeRects(this.spaces), this.spaces.sort(this.sorter) }, b.prototype.addSpace = function (a) { this.spaces.push(a), this.mergeSortSpaces() }, b.mergeRects = function (a) { for (var b = 0, c = a.length; c > b; b++) { var d = a[b]; if (d) { var e = a.slice(0); e.splice(b, 1); for (var f = 0, g = 0, h = e.length; h > g; g++) { var i = e[g], j = b > g ? 0 : 1; d.contains(i) && (a.splice(g + j - f, 1), f++) } } } return a }; var c = { downwardLeftToRight: function (a, b) { return a.y - b.y || a.x - b.x }, rightwardTopToBottom: function (a, b) { return a.x - b.x || a.y - b.y } }; return b } if ("function" == typeof define && define.amd) define("packery/js/packer", ["./rect"], b); else if ("object" == typeof exports) module.exports = b(require("./rect")); else { var c = a.Packery = a.Packery || {}; c.Packer = b(c.Rect) } }(window), function (a) { function b(a, b, c) { var d = a("transform"), e = function () { b.Item.apply(this, arguments) }; e.prototype = new b.Item; var f = e.prototype._create; return e.prototype._create = function () { f.call(this), this.rect = new c, this.placeRect = new c }, e.prototype.dragStart = function () { this.getPosition(), this.removeTransitionStyles(), this.isTransitioning && d && (this.element.style[d] = "none"), this.getSize(), this.isPlacing = !0, this.needsPositioning = !1, this.positionPlaceRect(this.position.x, this.position.y), this.isTransitioning = !1, this.didDrag = !1 }, e.prototype.dragMove = function (a, b) { this.didDrag = !0; var c = this.layout.size; a -= c.paddingLeft, b -= c.paddingTop, this.positionPlaceRect(a, b) }, e.prototype.dragStop = function () { this.getPosition(); var a = this.position.x !== this.placeRect.x, b = this.position.y !== this.placeRect.y; this.needsPositioning = a || b, this.didDrag = !1 }, e.prototype.positionPlaceRect = function (a, b, c) { this.placeRect.x = this.getPlaceRectCoord(a, !0), this.placeRect.y = this.getPlaceRectCoord(b, !1, c) }, e.prototype.getPlaceRectCoord = function (a, b, c) { var d = b ? "Width" : "Height", e = this.size["outer" + d], f = this.layout[b ? "columnWidth" : "rowHeight"], g = this.layout.size["inner" + d]; b || (g = Math.max(g, this.layout.maxY), this.layout.rowHeight || (g -= this.layout.gutter)); var h; if (f) { f += this.layout.gutter, g += b ? this.layout.gutter : 0, a = Math.round(a / f); var i; i = this.layout.options.isHorizontal ? b ? "ceil" : "floor" : b ? "floor" : "ceil"; var j = Math[i](g / f); j -= Math.ceil(e / f), h = j } else h = g - e; return a = c ? a : Math.min(a, h), a *= f || 1, Math.max(0, a) }, e.prototype.copyPlaceRectPosition = function () { this.rect.x = this.placeRect.x, this.rect.y = this.placeRect.y }, e.prototype.removeElem = function () { this.element.parentNode.removeChild(this.element), this.layout.packer.addSpace(this.rect), this.emitEvent("remove", [this]) }, e } "function" == typeof define && define.amd ? define("packery/js/item", ["get-style-property/get-style-property", "outlayer/outlayer", "./rect"], b) : "object" == typeof exports ? module.exports = b(require("desandro-get-style-property"), require("outlayer"), require("./rect")) : a.Packery.Item = b(a.getStyleProperty, a.Outlayer, a.Packery.Rect) }(window), function (a) { function b(a, b, c, d, e, f) { function g(a, b) { return a.position.y - b.position.y || a.position.x - b.position.x } function h(a, b) { return a.position.x - b.position.x || a.position.y - b.position.y } d.prototype.canFit = function (a) { return this.width >= a.width - 1 && this.height >= a.height - 1 }; var i = c.create("packery"); return i.Item = f, i.prototype._create = function () { c.prototype._create.call(this), this.packer = new e, this.stamp(this.options.stamped); var a = this; this.handleDraggabilly = { dragStart: function (b) { a.itemDragStart(b.element) }, dragMove: function (b) { a.itemDragMove(b.element, b.position.x, b.position.y) }, dragEnd: function (b) { a.itemDragEnd(b.element) } }, this.handleUIDraggable = { start: function (b) { a.itemDragStart(b.currentTarget) }, drag: function (b, c) { a.itemDragMove(b.currentTarget, c.position.left, c.position.top) }, stop: function (b) { a.itemDragEnd(b.currentTarget) } } }, i.prototype._resetLayout = function () { this.getSize(), this._getMeasurements(); var a = this.packer; this.options.isHorizontal ? (a.width = Number.POSITIVE_INFINITY, a.height = this.size.innerHeight + this.gutter, a.sortDirection = "rightwardTopToBottom") : (a.width = this.size.innerWidth + this.gutter, a.height = Number.POSITIVE_INFINITY, a.sortDirection = "downwardLeftToRight"), a.reset(), this.maxY = 0, this.maxX = 0 }, i.prototype._getMeasurements = function () { this._getMeasurement("columnWidth", "width"), this._getMeasurement("rowHeight", "height"), this._getMeasurement("gutter", "width") }, i.prototype._getItemLayoutPosition = function (a) { return this._packItem(a), a.rect }, i.prototype._packItem = function (a) { this._setRectSize(a.element, a.rect), this.packer.pack(a.rect), this._setMaxXY(a.rect) }, i.prototype._setMaxXY = function (a) { this.maxX = Math.max(a.x + a.width, this.maxX), this.maxY = Math.max(a.y + a.height, this.maxY) }, i.prototype._setRectSize = function (a, c) { var d = b(a), e = d.outerWidth, f = d.outerHeight; (e || f) && (e = this._applyGridGutter(e, this.columnWidth), f = this._applyGridGutter(f, this.rowHeight)), c.width = Math.min(e, this.packer.width), c.height = Math.min(f, this.packer.height) }, i.prototype._applyGridGutter = function (a, b) { if (!b) return a + this.gutter; b += this.gutter; var c = a % b, d = c && 1 > c ? "round" : "ceil"; return a = Math[d](a / b) * b }, i.prototype._getContainerSize = function () { return this.options.isHorizontal ? { width: this.maxX - this.gutter } : { height: this.maxY - this.gutter } }, i.prototype._manageStamp = function (a) { var b, c = this.getItem(a); if (c && c.isPlacing) b = c.placeRect; else { var e = this._getElementOffset(a); b = new d({ x: this.options.isOriginLeft ? e.left : e.right, y: this.options.isOriginTop ? e.top : e.bottom }) } this._setRectSize(a, b), this.packer.placed(b), this._setMaxXY(b) }, i.prototype.sortItemsByPosition = function () { var a = this.options.isHorizontal ? h : g; this.items.sort(a) }, i.prototype.fit = function (a, b, c) { var d = this.getItem(a); d && (this._getMeasurements(), this.stamp(d.element), d.getSize(), d.isPlacing = !0, b = void 0 === b ? d.rect.x : b, c = void 0 === c ? d.rect.y : c, d.positionPlaceRect(b, c, !0), this._bindFitEvents(d), d.moveTo(d.placeRect.x, d.placeRect.y), this.layout(), this.unstamp(d.element), this.sortItemsByPosition(), d.isPlacing = !1, d.copyPlaceRectPosition()) }, i.prototype._bindFitEvents = function (a) { function b() { d++ , 2 === d && c.emitEvent("fitComplete", [c, a]) } var c = this, d = 0; a.on("layout", function () { return b(), !0 }), this.on("layoutComplete", function () { return b(), !0 }) }, i.prototype.resize = function () { var a = b(this.element), c = this.size && a, d = this.options.isHorizontal ? "innerHeight" : "innerWidth"; c && a[d] === this.size[d] || this.layout() }, i.prototype.itemDragStart = function (a) { this.stamp(a); var b = this.getItem(a); b && b.dragStart() }, i.prototype.itemDragMove = function (a, b, c) { function d() { f.layout(), delete f.dragTimeout } var e = this.getItem(a); e && e.dragMove(b, c); var f = this; this.clearDragTimeout(), this.dragTimeout = setTimeout(d, 40) }, i.prototype.clearDragTimeout = function () { this.dragTimeout && clearTimeout(this.dragTimeout) }, i.prototype.itemDragEnd = function (b) { var c, d = this.getItem(b); if (d && (c = d.didDrag, d.dragStop()), !d || !c && !d.needsPositioning) return void this.unstamp(b); a.add(d.element, "is-positioning-post-drag"); var e = this._getDragEndLayoutComplete(b, d); d.needsPositioning ? (d.on("layout", e), d.moveTo(d.placeRect.x, d.placeRect.y)) : d && d.copyPlaceRectPosition(), this.clearDragTimeout(), this.on("layoutComplete", e), this.layout() }, i.prototype._getDragEndLayoutComplete = function (b, c) { var d = c && c.needsPositioning, e = 0, f = d ? 2 : 1, g = this; return function () { return e++ , e !== f ? !0 : (c && (a.remove(c.element, "is-positioning-post-drag"), c.isPlacing = !1, c.copyPlaceRectPosition()), g.unstamp(b), g.sortItemsByPosition(), d && g.emitEvent("dragItemPositioned", [g, c]), !0) } }, i.prototype.bindDraggabillyEvents = function (a) { a.on("dragStart", this.handleDraggabilly.dragStart), a.on("dragMove", this.handleDraggabilly.dragMove), a.on("dragEnd", this.handleDraggabilly.dragEnd) }, i.prototype.bindUIDraggableEvents = function (a) { a.on("dragstart", this.handleUIDraggable.start).on("drag", this.handleUIDraggable.drag).on("dragstop", this.handleUIDraggable.stop) }, i.Rect = d, i.Packer = e, i } "function" == typeof define && define.amd ? define("packery/js/packery", ["classie/classie", "get-size/get-size", "outlayer/outlayer", "./rect", "./packer", "./item"], b) : "object" == typeof exports ? module.exports = b(require("desandro-classie"), require("get-size"), require("outlayer"), require("./rect"), require("./packer"), require("./item")) : a.Packery = b(a.classie, a.getSize, a.Outlayer, a.Packery.Rect, a.Packery.Packer, a.Packery.Item) }(window), function (a) { function b(a, b) { for (var c in b) a[c] = b[c]; return a } function c(a, c, d) { var e = a.create("packery"), f = e.prototype._getElementOffset, g = e.prototype._getMeasurement; b(e.prototype, c.prototype), e.prototype._getElementOffset = f, e.prototype._getMeasurement = g; var h = e.prototype._resetLayout; e.prototype._resetLayout = function () { this.packer = this.packer || new c.Packer, h.apply(this, arguments) }; var i = e.prototype._getItemLayoutPosition; e.prototype._getItemLayoutPosition = function (a) { return a.rect = a.rect || new c.Rect, i.call(this, a) }; var j = e.prototype._manageStamp; return e.prototype._manageStamp = function () { this.options.isOriginLeft = this.isotope.options.isOriginLeft, this.options.isOriginTop = this.isotope.options.isOriginTop, j.apply(this, arguments) }, e.prototype.needsResizeLayout = function () { var a = d(this.element), b = this.size && a, c = this.options.isHorizontal ? "innerHeight" : "innerWidth"; return b && a[c] !== this.size[c] }, e } "function" == typeof define && define.amd ? define(["isotope/js/layout-mode", "packery/js/packery", "get-size/get-size"], c) : "object" == typeof exports ? module.exports = c(require("isotope-layout/js/layout-mode"), require("packery"), require("get-size")) : c(a.Isotope.LayoutMode, a.Packery, a.getSize) }(window);
/*!
* Justified Gallery - v3.5.4
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
*/
(function($) {
/* Events
jg.complete : called when all the gallery has been created
jg.resize : called when the gallery has been resized
*/
$.fn.justifiedGallery = function (arg) {
// Default options
var defaults = {
sizeRangeSuffixes : {
'lt100': '', // e.g. Flickr uses '_t'
'lt240': '', // e.g. Flickr uses '_m'
'lt320': '', // e.g. Flickr uses '_n'
'lt500': '', // e.g. Flickr uses ''
'lt640': '', // e.g. Flickr uses '_z'
'lt1024': '', // e.g. Flickr uses '_b'
},
rowHeight : 120,
maxRowHeight : 0, // negative value = no limits, 0 = 1.5 * rowHeight
margins : 1,
border: -1, // negative value = same as margins, 0 = disabled
lastRow : 'nojustify', // or can be 'justify' or 'hide'
justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified
(i.e. lastRow setting is not considered) */
fixedHeight : false,
waitThumbnailsLoad : true,
captions : true,
cssAnimation: false,
imagesAnimationDuration : 500, // ignored with css animations
captionSettings : { // ignored with css animations
animationDuration : 500,
visibleOpacity : 0.7,
nonVisibleOpacity : 0.0
},
rel : null, // rewrite the rel of each analyzed links
target : null, // rewrite the target of all links
extension : /\.[^.\\/]+$/,
refreshTime : 100,
randomize : false
};
function getSuffix(width, height, context) {
var longestSide;
longestSide = (width > height) ? width : height;
if (longestSide <= 100) {
return context.settings.sizeRangeSuffixes.lt100;
} else if (longestSide <= 240) {
return context.settings.sizeRangeSuffixes.lt240;
} else if (longestSide <= 320) {
return context.settings.sizeRangeSuffixes.lt320;
} else if (longestSide <= 500) {
return context.settings.sizeRangeSuffixes.lt500;
} else if (longestSide <= 640) {
return context.settings.sizeRangeSuffixes.lt640;
} else {
return context.settings.sizeRangeSuffixes.lt1024;
}
}
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function removeSuffix(str, suffix) {
return str.substring(0, str.length - suffix.length);
}
function getUsedSuffix(str, context) {
var voidSuffix = false;
for (var si in context.settings.sizeRangeSuffixes) {
if (context.settings.sizeRangeSuffixes[si].length === 0) {
voidSuffix = true;
continue;
}
if (endsWith(str, context.settings.sizeRangeSuffixes[si])) {
return context.settings.sizeRangeSuffixes[si];
}
}
if (voidSuffix) return "";
else throw 'unknown suffix for ' + str;
}
/* Given an image src, with the width and the height, returns the new image src with the
best suffix to show the best quality thumbnail. */
function newSrc(imageSrc, imgWidth, imgHeight, context) {
var matchRes = imageSrc.match(context.settings.extension);
var ext = (matchRes != null) ? matchRes[0] : '';
var newImageSrc = imageSrc.replace(context.settings.extension, '');
newImageSrc = removeSuffix(newImageSrc, getUsedSuffix(newImageSrc, context));
newImageSrc += getSuffix(imgWidth, imgHeight, context) + ext;
return newImageSrc;
}
function onEntryMouseEnterForCaption (ev) {
var $caption = $(ev.currentTarget).find('.caption');
if (ev.data.settings.cssAnimation) {
$caption.addClass('caption-visible').removeClass('caption-hidden');
} else {
$caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration,
ev.data.settings.captionSettings.visibleOpacity);
}
}
function onEntryMouseLeaveForCaption (ev) {
var $caption = $(ev.currentTarget).find('.caption');
if (ev.data.settings.cssAnimation) {
$caption.removeClass('caption-visible').removeClass('caption-hidden');
} else {
$caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration,
ev.data.settings.captionSettings.nonVisibleOpacity);
}
}
function showImg($entry, callback, context) {
if (context.settings.cssAnimation) {
$entry.addClass('entry-visible');
callback();
} else {
$entry.stop().fadeTo(context.settings.imagesAnimationDuration, 1.0, callback);
}
}
function hideImgImmediately($entry, context) {
if (context.settings.cssAnimation) {
$entry.removeClass('entry-visible');
} else {
$entry.stop().fadeTo(0, 0);
}
}
function imgFromEntry($entry) {
var $img = $entry.find('> img');
if ($img.length === 0) $img = $entry.find('> a > img');
return $img;
}
function displayEntry($entry, x, y, imgWidth, imgHeight, rowHeight, context) {
var $image = imgFromEntry($entry);
$image.css('width', imgWidth);
$image.css('height', imgHeight);
//if ($entry.get(0) === $image.parent().get(0)) { // this creates an error in link_around_img test
$image.css('margin-left', - imgWidth / 2);
$image.css('margin-top', - imgHeight / 2);
//}
$entry.width(imgWidth);
$entry.height(rowHeight);
$entry.css('top', y);
$entry.css('left', x);
//DEBUG// console.log('displayEntry (w: ' + $image.width() + ' h: ' + $image.height());
// Image reloading for an high quality of thumbnails
var imageSrc = $image.attr('src');
var newImageSrc = newSrc(imageSrc, imgWidth, imgHeight, context);
$image.one('error', function () {
//DEBUG// console.log('revert the original image');
$image.attr('src', $image.data('jg.originalSrc')); //revert to the original thumbnail, we got it.
});
function loadNewImage() {
if (imageSrc !== newImageSrc) { //load the new image after the fadeIn
$image.attr('src', newImageSrc);
}
}
if ($image.data('jg.loaded') === 'skipped') {
onImageEvent(imageSrc, function() {
showImg($entry, loadNewImage, context);
$image.data('jg.loaded', true);
});
} else {
showImg($entry, loadNewImage, context);
}
// Captions ------------------------------
var captionMouseEvents = $entry.data('jg.captionMouseEvents');
if (context.settings.captions === true) {
var $imgCaption = $entry.find('.caption');
if ($imgCaption.length === 0) { // Create it if it doesn't exists
var caption = $image.attr('alt');
if (typeof caption === 'undefined') caption = $entry.attr('title');
if (typeof caption !== 'undefined') { // Create only we found something
$imgCaption = $('<div class="caption">' + caption + '</div>');
$entry.append($imgCaption);
}
}
// Create events (we check again the $imgCaption because it can be still inexistent)
if ($imgCaption.length !== 0) {
if (!context.settings.cssAnimation) {
$imgCaption.stop().fadeTo(context.settings.imagesAnimationDuration,
context.settings.captionSettings.nonVisibleOpacity);
}
if (typeof captionMouseEvents === 'undefined') {
captionMouseEvents = {
mouseenter: onEntryMouseEnterForCaption,
mouseleave: onEntryMouseLeaveForCaption
};
$entry.on('mouseenter', undefined, context, captionMouseEvents.mouseenter);
$entry.on('mouseleave', undefined, context, captionMouseEvents.mouseleave);
$entry.data('jg.captionMouseEvents', captionMouseEvents);
}
}
} else {
if (typeof captionMouseEvents !== 'undefined') {
$entry.off('mouseenter', undefined, context, captionMouseEvents.mouseenter);
$entry.off('mouseleave', undefined, context, captionMouseEvents.mouseleave);
$entry.removeData('jg.captionMouseEvents');
}
}
}
function prepareBuildingRow(context, isLastRow) {
var settings = context.settings;
var i, $entry, $image, imgAspectRatio, newImgW, newImgH, justify = true;
var minHeight = 0;
var availableWidth = context.galleryWidth - 2 * context.border - (
(context.buildingRow.entriesBuff.length - 1) * settings.margins);
var rowHeight = availableWidth / context.buildingRow.aspectRatio;
var justificable = context.buildingRow.width / availableWidth > settings.justifyThreshold;
//Skip the last row if we can't justify it and the lastRow == 'hide'
if (isLastRow && settings.lastRow === 'hide' && !justificable) {
for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
$entry = context.buildingRow.entriesBuff[i];
if (settings.cssAnimation)
$entry.removeClass('entry-visible');
else
$entry.stop().fadeTo(0, 0);
}
return -1;
}
// With lastRow = nojustify, justify if is justificable (the images will not become too big)
if (isLastRow && !justificable && settings.lastRow === 'nojustify') justify = false;
for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
$image = imgFromEntry(context.buildingRow.entriesBuff[i]);
imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
if (justify) {
newImgW = (i === context.buildingRow.entriesBuff.length - 1) ? availableWidth
: rowHeight * imgAspectRatio;
newImgH = rowHeight;
/* With fixedHeight the newImgH must be greater than rowHeight.
In some cases here this is not satisfied (due to the justification).
But we comment it, because is better to have a shorter but justified row instead
to have a cropped image at the end. */
/*if (settings.fixedHeight && newImgH < settings.rowHeight) {
newImgW = settings.rowHeight * imgAspectRatio;
newImgH = settings.rowHeight;
}*/
} else {
newImgW = settings.rowHeight * imgAspectRatio;
newImgH = settings.rowHeight;
}
availableWidth -= Math.round(newImgW);
$image.data('jg.jimgw', Math.round(newImgW));
$image.data('jg.jimgh', Math.ceil(newImgH));
if (i === 0 || minHeight > newImgH) minHeight = newImgH;
}
if (settings.fixedHeight && minHeight > settings.rowHeight)
minHeight = settings.rowHeight;
return {minHeight: minHeight, justify: justify};
}
function rewind(context) {
context.lastAnalyzedIndex = -1;
context.buildingRow.entriesBuff = [];
context.buildingRow.aspectRatio = 0;
context.buildingRow.width = 0;
context.offY = context.border;
}
function flushRow(context, isLastRow) {
var settings = context.settings;
var $entry, $image, minHeight, buildingRowRes, offX = context.border;
//DEBUG// console.log('flush (isLastRow: ' + isLastRow + ')');
buildingRowRes = prepareBuildingRow(context, isLastRow);
minHeight = buildingRowRes.minHeight;
if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) {
context.buildingRow.entriesBuff = [];
context.buildingRow.aspectRatio = 0;
context.buildingRow.width = 0;
return;
}
if (settings.maxRowHeight > 0 && settings.maxRowHeight < minHeight)
minHeight = settings.maxRowHeight;
else if (settings.maxRowHeight === 0 && (1.5 * settings.rowHeight) < minHeight)
minHeight = 1.5 * settings.rowHeight;
for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
$entry = context.buildingRow.entriesBuff[i];
$image = imgFromEntry($entry);
displayEntry($entry, offX, context.offY, $image.data('jg.jimgw'),
$image.data('jg.jimgh'), minHeight, context);
offX += $image.data('jg.jimgw') + settings.margins;
}
//Gallery Height
context.$gallery.height(context.offY + minHeight + context.border +
(context.spinner.active ? context.spinner.$el.innerHeight() : 0)
);
if (!isLastRow || (minHeight <= context.settings.rowHeight && buildingRowRes.justify)) {
//Ready for a new row
context.offY += minHeight + context.settings.margins;
//DEBUG// console.log('minHeight: ' + minHeight + ' offY: ' + context.offY);
context.buildingRow.entriesBuff = []; //clear the array creating a new one
context.buildingRow.aspectRatio = 0;
context.buildingRow.width = 0;
context.$gallery.trigger('jg.rowflush');
}
}
function checkWidth(context) {
context.checkWidthIntervalId = setInterval(function () {
var galleryWidth = parseInt(context.$gallery.width(), 10);
if (context.galleryWidth !== galleryWidth) {
//DEBUG// console.log("resize. old: " + context.galleryWidth + " new: " + galleryWidth);
context.galleryWidth = galleryWidth;
rewind(context);
// Restart to analyze
startImgAnalyzer(context, true);
}
}, context.settings.refreshTime);
}
function startLoadingSpinnerAnimation(spinnerContext) {
clearInterval(spinnerContext.intervalId);
spinnerContext.intervalId = setInterval(function () {
if (spinnerContext.phase < spinnerContext.$points.length)
spinnerContext.$points.eq(spinnerContext.phase).fadeTo(spinnerContext.timeslot, 1);
else
spinnerContext.$points.eq(spinnerContext.phase - spinnerContext.$points.length)
.fadeTo(spinnerContext.timeslot, 0);
spinnerContext.phase = (spinnerContext.phase + 1) % (spinnerContext.$points.length * 2);
}, spinnerContext.timeslot);
}
function stopLoadingSpinnerAnimation(spinnerContext) {
clearInterval(spinnerContext.intervalId);
spinnerContext.intervalId = null;
}
function stopImgAnalyzerStarter(context) {
context.yield.flushed = 0;
if (context.imgAnalyzerTimeout !== null) clearTimeout(context.imgAnalyzerTimeout);
}
function startImgAnalyzer(context, isForResize) {
stopImgAnalyzerStarter(context);
context.imgAnalyzerTimeout = setTimeout(function () {
analyzeImages(context, isForResize);
}, 0.001);
analyzeImages(context, isForResize);
}
function analyzeImages(context, isForResize) {
/* //DEBUG//
var rnd = parseInt(Math.random() * 10000, 10);
console.log('analyzeImages ' + rnd + ' start');
console.log('images status: ');
for (var i = 0; i < context.entries.length; i++) {
var $entry = $(context.entries[i]);
var $image = imgFromEntry($entry);
console.log(i + ' (alt: ' + $image.attr('alt') + 'loaded: ' + $image.data('jg.loaded') + ')');
}*/
/* The first row */
var settings = context.settings;
var isLastRow;
for (var i = context.lastAnalyzedIndex + 1; i < context.entries.length; i++) {
var $entry = $(context.entries[i]);
var $image = imgFromEntry($entry);
if ($image.data('jg.loaded') === true || $image.data('jg.loaded') === 'skipped') {
isLastRow = i >= context.entries.length - 1;
var availableWidth = context.galleryWidth - 2 * context.border - (
(context.buildingRow.entriesBuff.length - 1) * settings.margins);
var imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
if (availableWidth / (context.buildingRow.aspectRatio + imgAspectRatio) < settings.rowHeight) {
flushRow(context, isLastRow);
if(++context.yield.flushed >= context.yield.every) {
//DEBUG// console.log("yield");
startImgAnalyzer(context, isForResize);
return;
}
}
context.buildingRow.entriesBuff.push($entry);
context.buildingRow.aspectRatio += imgAspectRatio;
context.buildingRow.width += imgAspectRatio * settings.rowHeight;
context.lastAnalyzedIndex = i;
} else if ($image.data('jg.loaded') !== 'error') {
return;
}
}
// Last row flush (the row is not full)
if (context.buildingRow.entriesBuff.length > 0) flushRow(context, true);
if (context.spinner.active) {
context.spinner.active = false;
context.$gallery.height(context.$gallery.height() - context.spinner.$el.innerHeight());
context.spinner.$el.detach();
stopLoadingSpinnerAnimation(context.spinner);
}
/* Stop, if there is, the timeout to start the analyzeImages.
This is because an image can be set loaded, and the timeout can be set,
but this image can be analyzed yet.
*/
stopImgAnalyzerStarter(context);
//On complete callback
if (!isForResize)
context.$gallery.trigger('jg.complete');
else
context.$gallery.trigger('jg.resize');
//DEBUG// console.log('analyzeImages ' + rnd + ' end');
}
function checkSettings (context) {
var settings = context.settings;
function checkSuffixesRange(range) {
if (typeof settings.sizeRangeSuffixes[range] !== 'string')
throw 'sizeRangeSuffixes.' + range + ' must be a string';
}
function checkOrConvertNumber(parent, settingName) {
if (typeof parent[settingName] === 'string') {
parent[settingName] = parseFloat(parent[settingName], 10);
if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
} else if (typeof parent[settingName] === 'number') {
if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
} else {
throw settingName + ' must be a number';
}
}
if (typeof settings.sizeRangeSuffixes !== 'object')
throw 'sizeRangeSuffixes must be defined and must be an object';
checkSuffixesRange('lt100');
checkSuffixesRange('lt240');
checkSuffixesRange('lt320');
checkSuffixesRange('lt500');
checkSuffixesRange('lt640');
checkSuffixesRange('lt1024');
checkOrConvertNumber(settings, 'rowHeight');
checkOrConvertNumber(settings, 'maxRowHeight');
if (settings.maxRowHeight > 0 &&
settings.maxRowHeight < settings.rowHeight) {
settings.maxRowHeight = settings.rowHeight;
}
checkOrConvertNumber(settings, 'margins');
checkOrConvertNumber(settings, 'border');
if (settings.lastRow !== 'nojustify' &&
settings.lastRow !== 'justify' &&
settings.lastRow !== 'hide') {
throw 'lastRow must be "nojustify", "justify" or "hide"';
}
checkOrConvertNumber(settings, 'justifyThreshold');
if (settings.justifyThreshold < 0 || settings.justifyThreshold > 1)
throw 'justifyThreshold must be in the interval [0,1]';
if (typeof settings.cssAnimation !== 'boolean') {
throw 'cssAnimation must be a boolean';
}
checkOrConvertNumber(settings.captionSettings, 'animationDuration');
checkOrConvertNumber(settings, 'imagesAnimationDuration');
checkOrConvertNumber(settings.captionSettings, 'visibleOpacity');
if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
throw 'captionSettings.visibleOpacity must be in the interval [0, 1]';
checkOrConvertNumber(settings.captionSettings, 'nonVisibleOpacity');
if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]';
if (typeof settings.fixedHeight !== 'boolean') {
throw 'fixedHeight must be a boolean';
}
if (typeof settings.captions !== 'boolean') {
throw 'captions must be a boolean';
}
checkOrConvertNumber(settings, 'refreshTime');
if (typeof settings.randomize !== 'boolean') {
throw 'randomize must be a boolean';
}
}
function onImageEvent(imageSrc, onLoad, onError) {
if (!onLoad && !onError) {
return;
}
/* Check if the image is loaded or not using another image object.
We cannot use the 'complete' image property, because some browsers,
with a 404 set complete = true */
var memImage = new Image();
var $memImage = $(memImage);
if (onLoad) {
$memImage.one('load', function () {
$memImage.off('load error');
onLoad(memImage);
});
}
if (onError) {
$memImage.one('error', function() {
$memImage.off('load error');
onError(memImage);
});
}
memImage.src = imageSrc;
}
return this.each(function (index, gallery) {
var $gallery = $(gallery);
$gallery.addClass('justified-gallery');
var context = $gallery.data('jg.context');
if (typeof context === 'undefined') {
if (typeof arg !== 'undefined' && arg !== null && typeof arg !== 'object')
throw 'The argument must be an object';
// Spinner init
var $spinner = $('<div class="spinner"><span></span><span></span><span></span></div>');
var extendedSettings = $.extend({}, defaults, arg);
var border = extendedSettings.border >= 0 ? extendedSettings.border : extendedSettings.margins;
//Context init
context = {
settings : extendedSettings,
imgAnalyzerTimeout : null,
entries : null,
buildingRow : {
entriesBuff : [],
width : 0,
aspectRatio : 0
},
lastAnalyzedIndex : -1,
yield : {
every : 2, /* do a flush every context.yield.every flushes (
* must be greater than 1, else the analyzeImages will loop */
flushed : 0 //flushed rows without a yield
},
border : border,
offY : border,
spinner : {
active : false,
phase : 0,
timeslot : 150,
$el : $spinner,
$points : $spinner.find('span'),
intervalId : null
},
checkWidthIntervalId : null,
galleryWidth : $gallery.width(),
$gallery : $gallery
};
$gallery.data('jg.context', context);
} else if (arg === 'norewind') {
/* Hide the image of the buildingRow to prevent strange effects when the row will be
re-justified again */
for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
hideImgImmediately(context.buildingRow.entriesBuff[i], context);
}
// In this case we don't rewind, and analyze all the images
} else {
context.settings = $.extend({}, context.settings, arg);
context.border = context.settings.border >= 0 ? context.settings.border : context.settings.margins;
rewind(context);
}
checkSettings(context);
context.entries = $gallery.find('> a, > div:not(.spinner)').toArray();
if (context.entries.length === 0) return;
// Randomize
if (context.settings.randomize) {
context.entries.sort(function () { return Math.random() * 2 - 1; });
$.each(context.entries, function () {
$(this).appendTo($gallery);
});
}
var imagesToLoad = false;
var skippedImages = false;
$.each(context.entries, function (index, entry) {
var $entry = $(entry);
var $image = imgFromEntry($entry);
$entry.addClass('jg-entry');
if ($image.data('jg.loaded') !== true && $image.data('jg.loaded') !== 'skipped') {
// Link Rel global overwrite
if (context.settings.rel !== null) $entry.attr('rel', context.settings.rel);
// Link Target global overwrite
if (context.settings.target !== null) $entry.attr('target', context.settings.target);
// Image src
var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ?
$image.data('safe-src') : $image.attr('src');
$image.data('jg.originalSrc', imageSrc);
$image.attr('src', imageSrc);
var width = parseInt($image.attr('width'), 10);
var height = parseInt($image.attr('height'), 10);
if(context.settings.waitThumbnailsLoad !== true && !isNaN(width) && !isNaN(height)) {
$image.data('jg.imgw', width);
$image.data('jg.imgh', height);
$image.data('jg.loaded', 'skipped');
skippedImages = true;
startImgAnalyzer(context, false);
return true;
}
$image.data('jg.loaded', false);
imagesToLoad = true;
// Spinner start
if (context.spinner.active === false) {
context.spinner.active = true;
$gallery.append(context.spinner.$el);
$gallery.height(context.offY + context.spinner.$el.innerHeight());
startLoadingSpinnerAnimation(context.spinner);
}
onImageEvent(imageSrc, function imgLoaded (loadImg) {
//DEBUG// console.log('img load (alt: ' + $image.attr('alt') + ')');
$image.data('jg.imgw', loadImg.width);
$image.data('jg.imgh', loadImg.height);
$image.data('jg.loaded', true);
startImgAnalyzer(context, false);
}, function imgLoadError () {
//DEBUG// console.log('img error (alt: ' + $image.attr('alt') + ')');
$image.data('jg.loaded', 'error');
startImgAnalyzer(context, false);
});
}
});
if (!imagesToLoad && !skippedImages) startImgAnalyzer(context, false);
checkWidth(context);
});
};
}(jQuery));
/*! Magnific Popup - v1.0.0 - 2015-01-03
* http://dimsemenov.com/plugins/magnific-popup/
* Copyright (c) 2015 Dmitry Semenov; */
;(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(window.jQuery || window.Zepto);
}
}(function($) {
/*>>core*/
/**
*
* Magnific Popup Core JS file
*
*/
/**
* Private static constants
*/
var CLOSE_EVENT = 'Close',
BEFORE_CLOSE_EVENT = 'BeforeClose',
AFTER_CLOSE_EVENT = 'AfterClose',
BEFORE_APPEND_EVENT = 'BeforeAppend',
MARKUP_PARSE_EVENT = 'MarkupParse',
OPEN_EVENT = 'Open',
CHANGE_EVENT = 'Change',
NS = 'mfp',
EVENT_NS = '.' + NS,
READY_CLASS = 'mfp-ready',
REMOVING_CLASS = 'mfp-removing',
PREVENT_CLOSE_CLASS = 'mfp-prevent-close';
/**
* Private vars
*/
/*jshint -W079 */
var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this'
MagnificPopup = function(){},
_isJQ = !!(window.jQuery),
_prevStatus,
_window = $(window),
_document,
_prevContentType,
_wrapClasses,
_currPopupType;
/**
* Private functions
*/
var _mfpOn = function(name, f) {
mfp.ev.on(NS + name + EVENT_NS, f);
},
_getEl = function(className, appendTo, html, raw) {
var el = document.createElement('div');
el.className = 'mfp-'+className;
if(html) {
el.innerHTML = html;
}
if(!raw) {
el = $(el);
if(appendTo) {
el.appendTo(appendTo);
}
} else if(appendTo) {
appendTo.appendChild(el);
}
return el;
},
_mfpTrigger = function(e, data) {
mfp.ev.triggerHandler(NS + e, data);
if(mfp.st.callbacks) {
// converts "mfpEventName" to "eventName" callback and triggers it if it's present
e = e.charAt(0).toLowerCase() + e.slice(1);
if(mfp.st.callbacks[e]) {
mfp.st.callbacks[e].apply(mfp, $.isArray(data) ? data : [data]);
}
}
},
_getCloseBtn = function(type) {
if(type !== _currPopupType || !mfp.currTemplate.closeBtn) {
mfp.currTemplate.closeBtn = $( mfp.st.closeMarkup.replace('%title%', mfp.st.tClose ) );
_currPopupType = type;
}
return mfp.currTemplate.closeBtn;
},
// Initialize Magnific Popup only when called at least once
_checkInstance = function() {
if(!$.magnificPopup.instance) {
/*jshint -W020 */
mfp = new MagnificPopup();
mfp.init();
$.magnificPopup.instance = mfp;
}
},
// CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
supportsTransitions = function() {
var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist
v = ['ms','O','Moz','Webkit']; // 'v' for vendor
if( s['transition'] !== undefined ) {
return true;
}
while( v.length ) {
if( v.pop() + 'Transition' in s ) {
return true;
}
}
return false;
};
/**
* Public functions
*/
MagnificPopup.prototype = {
constructor: MagnificPopup,
/**
* Initializes Magnific Popup plugin.
* This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed
*/
init: function() {
var appVersion = navigator.appVersion;
mfp.isIE7 = appVersion.indexOf("MSIE 7.") !== -1;
mfp.isIE8 = appVersion.indexOf("MSIE 8.") !== -1;
mfp.isLowIE = mfp.isIE7 || mfp.isIE8;
mfp.isAndroid = (/android/gi).test(appVersion);
mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion);
mfp.supportsTransition = supportsTransitions();
// We disable fixed positioned lightbox on devices that don't handle it nicely.
// If you know a better way of detecting this - let me know.
mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) );
_document = $(document);
mfp.popupsCache = {};
},
/**
* Opens popup
* @param data [description]
*/
open: function(data) {
var i;
if(data.isObj === false) {
// convert jQuery collection to array to avoid conflicts later
mfp.items = data.items.toArray();
mfp.index = 0;
var items = data.items,
item;
for(i = 0; i < items.length; i++) {
item = items[i];
if(item.parsed) {
item = item.el[0];
}
if(item === data.el[0]) {
mfp.index = i;
break;
}
}
} else {
mfp.items = $.isArray(data.items) ? data.items : [data.items];
mfp.index = data.index || 0;
}
// if popup is already opened - we just update the content
if(mfp.isOpen) {
mfp.updateItemHTML();
return;
}
mfp.types = [];
_wrapClasses = '';
if(data.mainEl && data.mainEl.length) {
mfp.ev = data.mainEl.eq(0);
} else {
mfp.ev = _document;
}
if(data.key) {
if(!mfp.popupsCache[data.key]) {
mfp.popupsCache[data.key] = {};
}
mfp.currTemplate = mfp.popupsCache[data.key];
} else {
mfp.currTemplate = {};
}
mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data );
mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos;
if(mfp.st.modal) {
mfp.st.closeOnContentClick = false;
mfp.st.closeOnBgClick = false;
mfp.st.showCloseBtn = false;
mfp.st.enableEscapeKey = false;
}
// Building markup
// main containers are created only once
if(!mfp.bgOverlay) {
// Dark overlay
mfp.bgOverlay = _getEl('bg').on('click'+EVENT_NS, function() {
mfp.close();
});
mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click'+EVENT_NS, function(e) {
if(mfp._checkIfClose(e.target)) {
mfp.close();
}
});
mfp.container = _getEl('container', mfp.wrap);
}
mfp.contentContainer = _getEl('content');
if(mfp.st.preloader) {
mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading);
}
// Initializing modules
var modules = $.magnificPopup.modules;
for(i = 0; i < modules.length; i++) {
var n = modules[i];
n = n.charAt(0).toUpperCase() + n.slice(1);
mfp['init'+n].call(mfp);
}
_mfpTrigger('BeforeOpen');
if(mfp.st.showCloseBtn) {
// Close button
if(!mfp.st.closeBtnInside) {
mfp.wrap.append( _getCloseBtn() );
} else {
_mfpOn(MARKUP_PARSE_EVENT, function(e, template, values, item) {
values.close_replaceWith = _getCloseBtn(item.type);
});
_wrapClasses += ' mfp-close-btn-in';
}
}
if(mfp.st.alignTop) {
_wrapClasses += ' mfp-align-top';
}
if(mfp.fixedContentPos) {
mfp.wrap.css({
overflow: mfp.st.overflowY,
overflowX: 'hidden',
overflowY: mfp.st.overflowY
});
} else {
mfp.wrap.css({
top: _window.scrollTop(),
position: 'absolute'
});
}
if( mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos) ) {
mfp.bgOverlay.css({
height: _document.height(),
position: 'absolute'
});
}
if(mfp.st.enableEscapeKey) {
// Close on ESC key
_document.on('keyup' + EVENT_NS, function(e) {
if(e.keyCode === 27) {
mfp.close();
}
});
}
_window.on('resize' + EVENT_NS, function() {
mfp.updateSize();
});
if(!mfp.st.closeOnContentClick) {
_wrapClasses += ' mfp-auto-cursor';
}
if(_wrapClasses)
mfp.wrap.addClass(_wrapClasses);
// this triggers recalculation of layout, so we get it once to not to trigger twice
var windowHeight = mfp.wH = _window.height();
var windowStyles = {};
if( mfp.fixedContentPos ) {
if(mfp._hasScrollBar(windowHeight)){
var s = mfp._getScrollbarSize();
if(s) {
windowStyles.marginRight = s;
}
}
}
if(mfp.fixedContentPos) {
if(!mfp.isIE7) {
windowStyles.overflow = 'hidden';
} else {
// ie7 double-scroll bug
$('body, html').css('overflow', 'hidden');
}
}
var classesToadd = mfp.st.mainClass;
if(mfp.isIE7) {
classesToadd += ' mfp-ie7';
}
if(classesToadd) {
mfp._addClassToMFP( classesToadd );
}
// add content
mfp.updateItemHTML();
_mfpTrigger('BuildControls');
// remove scrollbar, add margin e.t.c
$('html').css(windowStyles);
// add everything to DOM
mfp.bgOverlay.add(mfp.wrap).prependTo( mfp.st.prependTo || $(document.body) );
// Save last focused element
mfp._lastFocusedEl = document.activeElement;
// Wait for next cycle to allow CSS transition
setTimeout(function() {
if(mfp.content) {
mfp._addClassToMFP(READY_CLASS);
mfp._setFocus();
} else {
// if content is not defined (not loaded e.t.c) we add class only for BG
mfp.bgOverlay.addClass(READY_CLASS);
}
// Trap the focus in popup
_document.on('focusin' + EVENT_NS, mfp._onFocusIn);
}, 16);
mfp.isOpen = true;
mfp.updateSize(windowHeight);
_mfpTrigger(OPEN_EVENT);
return data;
},
/**
* Closes the popup
*/
close: function() {
if(!mfp.isOpen) return;
_mfpTrigger(BEFORE_CLOSE_EVENT);
mfp.isOpen = false;
// for CSS3 animation
if(mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition ) {
mfp._addClassToMFP(REMOVING_CLASS);
setTimeout(function() {
mfp._close();
}, mfp.st.removalDelay);
} else {
mfp._close();
}
},
/**
* Helper for close() function
*/
_close: function() {
_mfpTrigger(CLOSE_EVENT);
var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' ';
mfp.bgOverlay.detach();
mfp.wrap.detach();
mfp.container.empty();
if(mfp.st.mainClass) {
classesToRemove += mfp.st.mainClass + ' ';
}
mfp._removeClassFromMFP(classesToRemove);
if(mfp.fixedContentPos) {
var windowStyles = {marginRight: ''};
if(mfp.isIE7) {
$('body, html').css('overflow', '');
} else {
windowStyles.overflow = '';
}
$('html').css(windowStyles);
}
_document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS);
mfp.ev.off(EVENT_NS);
// clean up DOM elements that aren't removed
mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style');
mfp.bgOverlay.attr('class', 'mfp-bg');
mfp.container.attr('class', 'mfp-container');
// remove close button from target element
if(mfp.st.showCloseBtn &&
(!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) {
if(mfp.currTemplate.closeBtn)
mfp.currTemplate.closeBtn.detach();
}
// if(mfp._lastFocusedEl) {
// $(mfp._lastFocusedEl).focus(); // put tab focus back
// }
mfp.currItem = null;
mfp.content = null;
mfp.currTemplate = null;
mfp.prevHeight = 0;
_mfpTrigger(AFTER_CLOSE_EVENT);
},
updateSize: function(winHeight) {
if(mfp.isIOS) {
// fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2
var zoomLevel = document.documentElement.clientWidth / window.innerWidth;
var height = window.innerHeight * zoomLevel;
mfp.wrap.css('height', height);
mfp.wH = height;
} else {
mfp.wH = winHeight || _window.height();
}
// Fixes #84: popup incorrectly positioned with position:relative on body
if(!mfp.fixedContentPos) {
mfp.wrap.css('height', mfp.wH);
}
_mfpTrigger('Resize');
},
/**
* Set content of popup based on current index
*/
updateItemHTML: function() {
var item = mfp.items[mfp.index];
// Detach and perform modifications
mfp.contentContainer.detach();
if(mfp.content)
mfp.content.detach();
if(!item.parsed) {
item = mfp.parseEl( mfp.index );
}
var type = item.type;
_mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]);
// BeforeChange event works like so:
// _mfpOn('BeforeChange', function(e, prevType, newType) { });
mfp.currItem = item;
if(!mfp.currTemplate[type]) {
var markup = mfp.st[type] ? mfp.st[type].markup : false;
// allows to modify markup
_mfpTrigger('FirstMarkupParse', markup);
if(markup) {
mfp.currTemplate[type] = $(markup);
} else {
// if there is no markup found we just define that template is parsed
mfp.currTemplate[type] = true;
}
}
if(_prevContentType && _prevContentType !== item.type) {
mfp.container.removeClass('mfp-'+_prevContentType+'-holder');
}
var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]);
mfp.appendContent(newContent, type);
item.preloaded = true;
_mfpTrigger(CHANGE_EVENT, item);
_prevContentType = item.type;
// Append container back after its content changed
mfp.container.prepend(mfp.contentContainer);
_mfpTrigger('AfterChange');
},
/**
* Set HTML content of popup
*/
appendContent: function(newContent, type) {
mfp.content = newContent;
if(newContent) {
if(mfp.st.showCloseBtn && mfp.st.closeBtnInside &&
mfp.currTemplate[type] === true) {
// if there is no markup, we just append close button element inside
if(!mfp.content.find('.mfp-close').length) {
mfp.content.append(_getCloseBtn());
}
} else {
mfp.content = newContent;
}
} else {
mfp.content = '';
}
_mfpTrigger(BEFORE_APPEND_EVENT);
mfp.container.addClass('mfp-'+type+'-holder');
mfp.contentContainer.append(mfp.content);
},
/**
* Creates Magnific Popup data object based on given data
* @param {int} index Index of item to parse
*/
parseEl: function(index) {
var item = mfp.items[index],
type;
if(item.tagName) {
item = { el: $(item) };
} else {
type = item.type;
item = { data: item, src: item.src };
}
if(item.el) {
var types = mfp.types;
// check for 'mfp-TYPE' class
for(var i = 0; i < types.length; i++) {
if( item.el.hasClass('mfp-'+types[i]) ) {
type = types[i];
break;
}
}
item.src = item.el.attr('data-mfp-src');
if(!item.src) {
item.src = item.el.attr('href');
}
}
item.type = type || mfp.st.type || 'inline';
item.index = index;
item.parsed = true;
mfp.items[index] = item;
_mfpTrigger('ElementParse', item);
return mfp.items[index];
},
/**
* Initializes single popup or a group of popups
*/
addGroup: function(el, options) {
var eHandler = function(e) {
e.mfpEl = this;
mfp._openClick(e, el, options);
};
if(!options) {
options = {};
}
var eName = 'click.magnificPopup';
options.mainEl = el;
if(options.items) {
options.isObj = true;
el.off(eName).on(eName, eHandler);
} else {
options.isObj = false;
if(options.delegate) {
el.off(eName).on(eName, options.delegate , eHandler);
} else {
options.items = el;
el.off(eName).on(eName, eHandler);
}
}
},
_openClick: function(e, el, options) {
var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick;
if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey ) ) {
return;
}
var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn;
if(disableOn) {
if(typeof disableOn === "function") {
if( !disableOn.call(mfp) ) {
return true;
}
} else { // else it's number
if( _window.width() < disableOn ) {
return true;
}
}
}
if(e.type) {
e.preventDefault();
// This will prevent popup from closing if element is inside and popup is already opened
if(mfp.isOpen) {
e.stopPropagation();
}
}
options.el = $(e.mfpEl);
if(options.delegate) {
options.items = el.find(options.delegate);
}
mfp.open(options);
},
/**
* Updates text on preloader
*/
updateStatus: function(status, text) {
if(mfp.preloader) {
if(_prevStatus !== status) {
mfp.container.removeClass('mfp-s-'+_prevStatus);
}
if(!text && status === 'loading') {
text = mfp.st.tLoading;
}
var data = {
status: status,
text: text
};
// allows to modify status
_mfpTrigger('UpdateStatus', data);
status = data.status;
text = data.text;
mfp.preloader.html(text);
mfp.preloader.find('a').on('click', function(e) {
e.stopImmediatePropagation();
});
mfp.container.addClass('mfp-s-'+status);
_prevStatus = status;
}
},
/*
"Private" helpers that aren't private at all
*/
// Check to close popup or not
// "target" is an element that was clicked
_checkIfClose: function(target) {
if($(target).hasClass(PREVENT_CLOSE_CLASS)) {
return;
}
var closeOnContent = mfp.st.closeOnContentClick;
var closeOnBg = mfp.st.closeOnBgClick;
if(closeOnContent && closeOnBg) {
return true;
} else {
// We close the popup if click is on close button or on preloader. Or if there is no content.
if(!mfp.content || $(target).hasClass('mfp-close') || (mfp.preloader && target === mfp.preloader[0]) ) {
return true;
}
// if click is outside the content
if( (target !== mfp.content[0] && !$.contains(mfp.content[0], target)) ) {
if(closeOnBg) {
// last check, if the clicked element is in DOM, (in case it's removed onclick)
if( $.contains(document, target) ) {
return true;
}
}
} else if(closeOnContent) {
return true;
}
}
return false;
},
_addClassToMFP: function(cName) {
mfp.bgOverlay.addClass(cName);
mfp.wrap.addClass(cName);
},
_removeClassFromMFP: function(cName) {
this.bgOverlay.removeClass(cName);
mfp.wrap.removeClass(cName);
},
_hasScrollBar: function(winHeight) {
return ( (mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height()) );
},
_setFocus: function() {
(mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).focus();
},
_onFocusIn: function(e) {
if( e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target) ) {
mfp._setFocus();
return false;
}
},
_parseMarkup: function(template, values, item) {
var arr;
if(item.data) {
values = $.extend(item.data, values);
}
_mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] );
$.each(values, function(key, value) {
if(value === undefined || value === false) {
return true;
}
arr = key.split('_');
if(arr.length > 1) {
var el = template.find(EVENT_NS + '-'+arr[0]);
if(el.length > 0) {
var attr = arr[1];
if(attr === 'replaceWith') {
if(el[0] !== value[0]) {
el.replaceWith(value);
}
} else if(attr === 'img') {
if(el.is('img')) {
el.attr('src', value);
} else {
el.replaceWith( '<img src="'+value+'" class="' + el.attr('class') + '" />' );
}
} else {
el.attr(arr[1], value);
}
}
} else {
template.find(EVENT_NS + '-'+key).html(value);
}
});
},
_getScrollbarSize: function() {
// thx David
if(mfp.scrollbarSize === undefined) {
var scrollDiv = document.createElement("div");
scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
document.body.appendChild(scrollDiv);
mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
document.body.removeChild(scrollDiv);
}
return mfp.scrollbarSize;
}
}; /* MagnificPopup core prototype end */
/**
* Public static functions
*/
$.magnificPopup = {
instance: null,
proto: MagnificPopup.prototype,
modules: [],
open: function(options, index) {
_checkInstance();
if(!options) {
options = {};
} else {
options = $.extend(true, {}, options);
}
options.isObj = true;
options.index = index || 0;
return this.instance.open(options);
},
close: function() {
return $.magnificPopup.instance && $.magnificPopup.instance.close();
},
registerModule: function(name, module) {
if(module.options) {
$.magnificPopup.defaults[name] = module.options;
}
$.extend(this.proto, module.proto);
this.modules.push(name);
},
defaults: {
// Info about options is in docs:
// http://dimsemenov.com/plugins/magnific-popup/documentation.html#options
disableOn: 0,
key: null,
midClick: false,
mainClass: '',
preloader: true,
focus: '', // CSS selector of input to focus after popup is opened
closeOnContentClick: false,
closeOnBgClick: true,
closeBtnInside: true,
showCloseBtn: true,
enableEscapeKey: true,
modal: false,
alignTop: false,
removalDelay: 0,
prependTo: null,
fixedContentPos: 'auto',
fixedBgPos: 'auto',
overflowY: 'auto',
closeMarkup: '<button title="%title%" type="button" class="mfp-close">&times;</button>',
tClose: 'Close (Esc)',
tLoading: 'Loading...'
}
};
$.fn.magnificPopup = function(options) {
_checkInstance();
var jqEl = $(this);
// We call some API method of first param is a string
if (typeof options === "string" ) {
if(options === 'open') {
var items,
itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup,
index = parseInt(arguments[1], 10) || 0;
if(itemOpts.items) {
items = itemOpts.items[index];
} else {
items = jqEl;
if(itemOpts.delegate) {
items = items.find(itemOpts.delegate);
}
items = items.eq( index );
}
mfp._openClick({mfpEl:items}, jqEl, itemOpts);
} else {
if(mfp.isOpen)
mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1));
}
} else {
// clone options obj
options = $.extend(true, {}, options);
/*
* As Zepto doesn't support .data() method for objects
* and it works only in normal browsers
* we assign "options" object directly to the DOM element. FTW!
*/
if(_isJQ) {
jqEl.data('magnificPopup', options);
} else {
jqEl[0].magnificPopup = options;
}
mfp.addGroup(jqEl, options);
}
return jqEl;
};
//Quick benchmark
/*
var start = performance.now(),
i,
rounds = 1000;
for(i = 0; i < rounds; i++) {
}
console.log('Test #1:', performance.now() - start);
start = performance.now();
for(i = 0; i < rounds; i++) {
}
console.log('Test #2:', performance.now() - start);
*/
/*>>core*/
/*>>inline*/
var INLINE_NS = 'inline',
_hiddenClass,
_inlinePlaceholder,
_lastInlineElement,
_putInlineElementsBack = function() {
if(_lastInlineElement) {
_inlinePlaceholder.after( _lastInlineElement.addClass(_hiddenClass) ).detach();
_lastInlineElement = null;
}
};
$.magnificPopup.registerModule(INLINE_NS, {
options: {
hiddenClass: 'hide', // will be appended with `mfp-` prefix
markup: '',
tNotFound: 'Content not found'
},
proto: {
initInline: function() {
mfp.types.push(INLINE_NS);
_mfpOn(CLOSE_EVENT+'.'+INLINE_NS, function() {
_putInlineElementsBack();
});
},
getInline: function(item, template) {
_putInlineElementsBack();
if(item.src) {
var inlineSt = mfp.st.inline,
el = $(item.src);
if(el.length) {
// If target element has parent - we replace it with placeholder and put it back after popup is closed
var parent = el[0].parentNode;
if(parent && parent.tagName) {
if(!_inlinePlaceholder) {
_hiddenClass = inlineSt.hiddenClass;
_inlinePlaceholder = _getEl(_hiddenClass);
_hiddenClass = 'mfp-'+_hiddenClass;
}
// replace target inline element with placeholder
_lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass);
}
mfp.updateStatus('ready');
} else {
mfp.updateStatus('error', inlineSt.tNotFound);
el = $('<div>');
}
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: '<a href="%url%">The content</a> 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: '<div class="mfp-figure">'+
'<div class="mfp-close"></div>'+
'<figure>'+
'<div class="mfp-img"></div>'+
'<figcaption>'+
'<div class="mfp-bottom-bar">'+
'<div class="mfp-title"></div>'+
'<div class="mfp-counter"></div>'+
'</div>'+
'</figcaption>'+
'</figure>'+
'</div>',
cursor: 'mfp-zoom-out-cur',
titleSrc: 'title',
verticalFit: true,
tError: '<a href="%url%">The image</a> 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: '<div class="mfp-iframe-scaler">'+
'<div class="mfp-close"></div>'+
'<iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe>'+
'</div>',
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: '<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',
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 = $('<img class="mfp-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('<div class="' + this.settings.stageOuterClass + '"/>');
// 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.<String>} - 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.<jQuery>} - 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.<jQuery>} - 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.<Number>} - 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.<Number>} - 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.<String>} 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.<String>} 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.<jQuery>}
*/
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 = '<div class="owl-video-play-icon"></div>';
if (settings.lazyLoad) {
tnLink = '<div class="owl-video-tn ' + lazyClass + '" ' + srcType + '="' + path + '"></div>';
} else {
tnLink = '<div class="owl-video-tn" style="opacity:1;background-image:url(' + path + ')"></div>';
}
target.after(tnLink);
target.after(icon);
};
// wrap video content into owl-video-wrapper div
target.wrap('<div class="owl-video-wrapper"' + dimensions + '></div>');
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 = '<iframe width="' + width + '" height="' + height + '" src="//www.youtube.com/embed/' +
video.id + '?autoplay=1&v=' + video.id + '" frameborder="0" allowfullscreen></iframe>';
} else if (video.type === 'vimeo') {
html = '<iframe src="//player.vimeo.com/video/' + video.id +
'?autoplay=1" width="' + width + '" height="' + height +
'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
} else if (video.type === 'vzaar') {
html = '<iframe frameborder="0"' + 'height="' + height + '"' + 'width="' + width +
'" allowfullscreen mozallowfullscreen webkitAllowFullScreen ' +
'src="//view.vzaar.com/' + video.id + '/player?autoplay=true"></iframe>';
}
$('<div class="owl-video-frame">' + html + '</div>').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.<String>}
*/
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('<div class="' + this._core.settings.dotClass + '">' +
$(e.content).find('[data-dot]').addBack('[data-dot]').attr('data-dot') + '</div>');
}
}, 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)
: $('<div>').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 = [ $('<div>')
.addClass(settings.dotClass)
.append($('<span>'))
.prop('outerHTML') ];
}
this._controls.$absolute = (settings.dotsContainer ? $(settings.dotsContainer)
: $('<div>').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 = $('<support>').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);
/*!
* VERSION: 1.18.4
* DATE: 2016-04-26
* UPDATES AND DOCS AT: http://greensock.com
*
* Includes all of the following: TweenLite, TweenMax, TimelineLite, TimelineMax, EasePack, CSSPlugin, RoundPropsPlugin, BezierPlugin, AttrPlugin, DirectionalRotationPlugin
*
* @license Copyright (c) 2008-2016, GreenSock. All rights reserved.
* This work is subject to the terms at http://greensock.com/standard-license or for
* Club GreenSock members, the software agreement that was issued with your membership.
*
* @author: Jack Doyle, jack@greensock.com
**/
var _gsScope = (typeof (module) !== "undefined" && module.exports && typeof (global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node
(_gsScope._gsQueue || (_gsScope._gsQueue = [])).push(function () {
"use strict";
_gsScope._gsDefine("TweenMax", ["core.Animation", "core.SimpleTimeline", "TweenLite"], function (Animation, SimpleTimeline, TweenLite) {
var _slice = function (a) { //don't use [].slice because that doesn't work in IE8 with a NodeList that's returned by querySelectorAll()
var b = [],
l = a.length,
i;
for (i = 0; i !== l; b.push(a[i++]));
return b;
},
_applyCycle = function (vars, targets, i) {
var alt = vars.cycle,
p, val;
for (p in alt) {
val = alt[p];
vars[p] = (typeof (val) === "function") ? val.call(targets[i], i) : val[i % val.length];
}
delete vars.cycle;
},
TweenMax = function (target, duration, vars) {
TweenLite.call(this, target, duration, vars);
this._cycle = 0;
this._yoyo = (this.vars.yoyo === true);
this._repeat = this.vars.repeat || 0;
this._repeatDelay = this.vars.repeatDelay || 0;
this._dirty = true; //ensures that if there is any repeat, the totalDuration will get recalculated to accurately report it.
this.render = TweenMax.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
},
_tinyNum = 0.0000000001,
TweenLiteInternals = TweenLite._internals,
_isSelector = TweenLiteInternals.isSelector,
_isArray = TweenLiteInternals.isArray,
p = TweenMax.prototype = TweenLite.to({}, 0.1, {}),
_blankArray = [];
TweenMax.version = "1.18.4";
p.constructor = TweenMax;
p.kill()._gc = false;
TweenMax.killTweensOf = TweenMax.killDelayedCallsTo = TweenLite.killTweensOf;
TweenMax.getTweensOf = TweenLite.getTweensOf;
TweenMax.lagSmoothing = TweenLite.lagSmoothing;
TweenMax.ticker = TweenLite.ticker;
TweenMax.render = TweenLite.render;
p.invalidate = function () {
this._yoyo = (this.vars.yoyo === true);
this._repeat = this.vars.repeat || 0;
this._repeatDelay = this.vars.repeatDelay || 0;
this._uncache(true);
return TweenLite.prototype.invalidate.call(this);
};
p.updateTo = function (vars, resetDuration) {
var curRatio = this.ratio,
immediate = this.vars.immediateRender || vars.immediateRender,
p;
if (resetDuration && this._startTime < this._timeline._time) {
this._startTime = this._timeline._time;
this._uncache(false);
if (this._gc) {
this._enabled(true, false);
} else {
this._timeline.insert(this, this._startTime - this._delay); //ensures that any necessary re-sequencing of Animations in the timeline occurs to make sure the rendering order is correct.
}
}
for (p in vars) {
this.vars[p] = vars[p];
}
if (this._initted || immediate) {
if (resetDuration) {
this._initted = false;
if (immediate) {
this.render(0, true, true);
}
} else {
if (this._gc) {
this._enabled(true, false);
}
if (this._notifyPluginsOfEnabled && this._firstPT) {
TweenLite._onPluginEvent("_onDisable", this); //in case a plugin like MotionBlur must perform some cleanup tasks
}
if (this._time / this._duration > 0.998) { //if the tween has finished (or come extremely close to finishing), we just need to rewind it to 0 and then render it again at the end which forces it to re-initialize (parsing the new vars). We allow tweens that are close to finishing (but haven't quite finished) to work this way too because otherwise, the values are so small when determining where to project the starting values that binary math issues creep in and can make the tween appear to render incorrectly when run backwards.
var prevTime = this._totalTime;
this.render(0, true, false);
this._initted = false;
this.render(prevTime, true, false);
} else {
this._initted = false;
this._init();
if (this._time > 0 || immediate) {
var inv = 1 / (1 - curRatio),
pt = this._firstPT, endValue;
while (pt) {
endValue = pt.s + pt.c;
pt.c *= inv;
pt.s = endValue - pt.c;
pt = pt._next;
}
}
}
}
}
return this;
};
p.render = function (time, suppressEvents, force) {
if (!this._initted) if (this._duration === 0 && this.vars.repeat) { //zero duration tweens that render immediately have render() called from TweenLite's constructor, before TweenMax's constructor has finished setting _repeat, _repeatDelay, and _yoyo which are critical in determining totalDuration() so we need to call invalidate() which is a low-kb way to get those set properly.
this.invalidate();
}
var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
prevTime = this._time,
prevTotalTime = this._totalTime,
prevCycle = this._cycle,
duration = this._duration,
prevRawPrevTime = this._rawPrevTime,
isComplete, callback, pt, cycleDuration, r, type, pow, rawPrevTime;
if (time >= totalDur - 0.0000001) { //to work around occasional floating point math artifacts.
this._totalTime = totalDur;
this._cycle = this._repeat;
if (this._yoyo && (this._cycle & 1) !== 0) {
this._time = 0;
this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
} else {
this._time = duration;
this.ratio = this._ease._calcEnd ? this._ease.getRatio(1) : 1;
}
if (!this._reversed) {
isComplete = true;
callback = "onComplete";
force = (force || this._timeline.autoRemoveChildren); //otherwise, if the animation is unpaused/activated after it's already finished, it doesn't get removed from the parent timeline.
}
if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
if (this._startTime === this._timeline._duration) { //if a zero-duration tween is at the VERY end of a timeline and that timeline renders at its end, it will typically add a tiny bit of cushion to the render time to prevent rounding errors from getting in the way of tweens rendering their VERY end. If we then reverse() that timeline, the zero-duration tween will trigger its onReverseComplete even though technically the playhead didn't pass over it again. It's a very specific edge case we must accommodate.
time = 0;
}
if (prevRawPrevTime < 0 || (time <= 0 && time >= -0.0000001) || (prevRawPrevTime === _tinyNum && this.data !== "isPause")) if (prevRawPrevTime !== time) { //note: when this.data is "isPause", it's a callback added by addPause() on a timeline that we should not be triggered when LEAVING its exact start time. In other words, tl.addPause(1).play(1) shouldn't pause.
force = true;
if (prevRawPrevTime > _tinyNum) {
callback = "onReverseComplete";
}
}
this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
}
} else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
this._totalTime = this._time = this._cycle = 0;
this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
if (prevTotalTime !== 0 || (duration === 0 && prevRawPrevTime > 0)) {
callback = "onReverseComplete";
isComplete = this._reversed;
}
if (time < 0) {
this._active = false;
if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
if (prevRawPrevTime >= 0) {
force = true;
}
this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
}
}
if (!this._initted) { //if we render the very beginning (time == 0) of a fromTo(), we must force the render (normal tweens wouldn't need to render at a time of 0 when the prevTime was also 0). This is also mandatory to make sure overwriting kicks in immediately.
force = true;
}
} else {
this._totalTime = this._time = time;
if (this._repeat !== 0) {
cycleDuration = duration + this._repeatDelay;
this._cycle = (this._totalTime / cycleDuration) >> 0; //originally _totalTime % cycleDuration but floating point errors caused problems, so I normalized it. (4 % 0.8 should be 0 but some browsers report it as 0.79999999!)
if (this._cycle !== 0) if (this._cycle === this._totalTime / cycleDuration && prevTotalTime <= time) {
this._cycle--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
}
this._time = this._totalTime - (this._cycle * cycleDuration);
if (this._yoyo) if ((this._cycle & 1) !== 0) {
this._time = duration - this._time;
}
if (this._time > duration) {
this._time = duration;
} else if (this._time < 0) {
this._time = 0;
}
}
if (this._easeType) {
r = this._time / duration;
type = this._easeType;
pow = this._easePower;
if (type === 1 || (type === 3 && r >= 0.5)) {
r = 1 - r;
}
if (type === 3) {
r *= 2;
}
if (pow === 1) {
r *= r;
} else if (pow === 2) {
r *= r * r;
} else if (pow === 3) {
r *= r * r * r;
} else if (pow === 4) {
r *= r * r * r * r;
}
if (type === 1) {
this.ratio = 1 - r;
} else if (type === 2) {
this.ratio = r;
} else if (this._time / duration < 0.5) {
this.ratio = r / 2;
} else {
this.ratio = 1 - (r / 2);
}
} else {
this.ratio = this._ease.getRatio(this._time / duration);
}
}
if (prevTime === this._time && !force && prevCycle === this._cycle) {
if (prevTotalTime !== this._totalTime) if (this._onUpdate) if (!suppressEvents) { //so that onUpdate fires even during the repeatDelay - as long as the totalTime changed, we should trigger onUpdate.
this._callback("onUpdate");
}
return;
} else if (!this._initted) {
this._init();
if (!this._initted || this._gc) { //immediateRender tweens typically won't initialize until the playhead advances (_time is greater than 0) in order to ensure that overwriting occurs properly. Also, if all of the tweening properties have been overwritten (which would cause _gc to be true, as set in _init()), we shouldn't continue otherwise an onStart callback could be called for example.
return;
} else if (!force && this._firstPT && ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration))) { //we stick it in the queue for rendering at the very end of the tick - this is a performance optimization because browsers invalidate styles and force a recalculation if you read, write, and then read style data (so it's better to read/read/read/write/write/write than read/write/read/write/read/write). The down side, of course, is that usually you WANT things to render immediately because you may have code running right after that which depends on the change. Like imagine running TweenLite.set(...) and then immediately after that, creating a nother tween that animates the same property to another value; the starting values of that 2nd tween wouldn't be accurate if lazy is true.
this._time = prevTime;
this._totalTime = prevTotalTime;
this._rawPrevTime = prevRawPrevTime;
this._cycle = prevCycle;
TweenLiteInternals.lazyTweens.push(this);
this._lazy = [time, suppressEvents];
return;
}
//_ease is initially set to defaultEase, so now that init() has run, _ease is set properly and we need to recalculate the ratio. Overall this is faster than using conditional logic earlier in the method to avoid having to set ratio twice because we only init() once but renderTime() gets called VERY frequently.
if (this._time && !isComplete) {
this.ratio = this._ease.getRatio(this._time / duration);
} else if (isComplete && this._ease._calcEnd) {
this.ratio = this._ease.getRatio((this._time === 0) ? 0 : 1);
}
}
if (this._lazy !== false) {
this._lazy = false;
}
if (!this._active) if (!this._paused && this._time !== prevTime && time >= 0) {
this._active = true; //so that if the user renders a tween (as opposed to the timeline rendering it), the timeline is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the tween already finished but the user manually re-renders it as halfway done.
}
if (prevTotalTime === 0) {
if (this._initted === 2 && time > 0) {
//this.invalidate();
this._init(); //will just apply overwriting since _initted of (2) means it was a from() tween that had immediateRender:true
}
if (this._startAt) {
if (time >= 0) {
this._startAt.render(time, suppressEvents, force);
} else if (!callback) {
callback = "_dummyGS"; //if no callback is defined, use a dummy value just so that the condition at the end evaluates as true because _startAt should render AFTER the normal render loop when the time is negative. We could handle this in a more intuitive way, of course, but the render loop is the MOST important thing to optimize, so this technique allows us to avoid adding extra conditional logic in a high-frequency area.
}
}
if (this.vars.onStart) if (this._totalTime !== 0 || duration === 0) if (!suppressEvents) {
this._callback("onStart");
}
}
pt = this._firstPT;
while (pt) {
if (pt.f) {
pt.t[pt.p](pt.c * this.ratio + pt.s);
} else {
pt.t[pt.p] = pt.c * this.ratio + pt.s;
}
pt = pt._next;
}
if (this._onUpdate) {
if (time < 0) if (this._startAt && this._startTime) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
this._startAt.render(time, suppressEvents, force); //note: for performance reasons, we tuck this conditional logic inside less traveled areas (most tweens don't have an onUpdate). We'd just have it at the end before the onComplete, but the values should be updated before any onUpdate is called, so we ALSO put it here and then if it's not called, we do so later near the onComplete.
}
if (!suppressEvents) if (this._totalTime !== prevTotalTime || callback) {
this._callback("onUpdate");
}
}
if (this._cycle !== prevCycle) if (!suppressEvents) if (!this._gc) if (this.vars.onRepeat) {
this._callback("onRepeat");
}
if (callback) if (!this._gc || force) { //check gc because there's a chance that kill() could be called in an onUpdate
if (time < 0 && this._startAt && !this._onUpdate && this._startTime) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
this._startAt.render(time, suppressEvents, force);
}
if (isComplete) {
if (this._timeline.autoRemoveChildren) {
this._enabled(false, false);
}
this._active = false;
}
if (!suppressEvents && this.vars[callback]) {
this._callback(callback);
}
if (duration === 0 && this._rawPrevTime === _tinyNum && rawPrevTime !== _tinyNum) { //the onComplete or onReverseComplete could trigger movement of the playhead and for zero-duration tweens (which must discern direction) that land directly back on their start time, we don't want to fire again on the next render. Think of several addPause()'s in a timeline that forces the playhead to a certain spot, but what if it's already paused and another tween is tweening the "time" of the timeline? Each time it moves [forward] past that spot, it would move back, and since suppressEvents is true, it'd reset _rawPrevTime to _tinyNum so that when it begins again, the callback would fire (so ultimately it could bounce back and forth during that tween). Again, this is a very uncommon scenario, but possible nonetheless.
this._rawPrevTime = 0;
}
}
};
//---- STATIC FUNCTIONS -----------------------------------------------------------------------------------------------------------
TweenMax.to = function (target, duration, vars) {
return new TweenMax(target, duration, vars);
};
TweenMax.from = function (target, duration, vars) {
vars.runBackwards = true;
vars.immediateRender = (vars.immediateRender != false);
return new TweenMax(target, duration, vars);
};
TweenMax.fromTo = function (target, duration, fromVars, toVars) {
toVars.startAt = fromVars;
toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
return new TweenMax(target, duration, toVars);
};
TweenMax.staggerTo = TweenMax.allTo = function (targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
stagger = stagger || 0;
var delay = 0,
a = [],
finalComplete = function () {
if (vars.onComplete) {
vars.onComplete.apply(vars.onCompleteScope || this, arguments);
}
onCompleteAll.apply(onCompleteAllScope || vars.callbackScope || this, onCompleteAllParams || _blankArray);
},
cycle = vars.cycle,
fromCycle = (vars.startAt && vars.startAt.cycle),
l, copy, i, p;
if (!_isArray(targets)) {
if (typeof (targets) === "string") {
targets = TweenLite.selector(targets) || targets;
}
if (_isSelector(targets)) {
targets = _slice(targets);
}
}
targets = targets || [];
if (stagger < 0) {
targets = _slice(targets);
targets.reverse();
stagger *= -1;
}
l = targets.length - 1;
for (i = 0; i <= l; i++) {
copy = {};
for (p in vars) {
copy[p] = vars[p];
}
if (cycle) {
_applyCycle(copy, targets, i);
}
if (fromCycle) {
fromCycle = copy.startAt = {};
for (p in vars.startAt) {
fromCycle[p] = vars.startAt[p];
}
_applyCycle(copy.startAt, targets, i);
}
copy.delay = delay + (copy.delay || 0);
if (i === l && onCompleteAll) {
copy.onComplete = finalComplete;
}
a[i] = new TweenMax(targets[i], duration, copy);
delay += stagger;
}
return a;
};
TweenMax.staggerFrom = TweenMax.allFrom = function (targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
vars.runBackwards = true;
vars.immediateRender = (vars.immediateRender != false);
return TweenMax.staggerTo(targets, duration, vars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
};
TweenMax.staggerFromTo = TweenMax.allFromTo = function (targets, duration, fromVars, toVars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
toVars.startAt = fromVars;
toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
return TweenMax.staggerTo(targets, duration, toVars, stagger, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
};
TweenMax.delayedCall = function (delay, callback, params, scope, useFrames) {
return new TweenMax(callback, 0, { delay: delay, onComplete: callback, onCompleteParams: params, callbackScope: scope, onReverseComplete: callback, onReverseCompleteParams: params, immediateRender: false, useFrames: useFrames, overwrite: 0 });
};
TweenMax.set = function (target, vars) {
return new TweenMax(target, 0, vars);
};
TweenMax.isTweening = function (target) {
return (TweenLite.getTweensOf(target, true).length > 0);
};
var _getChildrenOf = function (timeline, includeTimelines) {
var a = [],
cnt = 0,
tween = timeline._first;
while (tween) {
if (tween instanceof TweenLite) {
a[cnt++] = tween;
} else {
if (includeTimelines) {
a[cnt++] = tween;
}
a = a.concat(_getChildrenOf(tween, includeTimelines));
cnt = a.length;
}
tween = tween._next;
}
return a;
},
getAllTweens = TweenMax.getAllTweens = function (includeTimelines) {
return _getChildrenOf(Animation._rootTimeline, includeTimelines).concat(_getChildrenOf(Animation._rootFramesTimeline, includeTimelines));
};
TweenMax.killAll = function (complete, tweens, delayedCalls, timelines) {
if (tweens == null) {
tweens = true;
}
if (delayedCalls == null) {
delayedCalls = true;
}
var a = getAllTweens((timelines != false)),
l = a.length,
allTrue = (tweens && delayedCalls && timelines),
isDC, tween, i;
for (i = 0; i < l; i++) {
tween = a[i];
if (allTrue || (tween instanceof SimpleTimeline) || ((isDC = (tween.target === tween.vars.onComplete)) && delayedCalls) || (tweens && !isDC)) {
if (complete) {
tween.totalTime(tween._reversed ? 0 : tween.totalDuration());
} else {
tween._enabled(false, false);
}
}
}
};
TweenMax.killChildTweensOf = function (parent, complete) {
if (parent == null) {
return;
}
var tl = TweenLiteInternals.tweenLookup,
a, curParent, p, i, l;
if (typeof (parent) === "string") {
parent = TweenLite.selector(parent) || parent;
}
if (_isSelector(parent)) {
parent = _slice(parent);
}
if (_isArray(parent)) {
i = parent.length;
while (--i > -1) {
TweenMax.killChildTweensOf(parent[i], complete);
}
return;
}
a = [];
for (p in tl) {
curParent = tl[p].target.parentNode;
while (curParent) {
if (curParent === parent) {
a = a.concat(tl[p].tweens);
}
curParent = curParent.parentNode;
}
}
l = a.length;
for (i = 0; i < l; i++) {
if (complete) {
a[i].totalTime(a[i].totalDuration());
}
a[i]._enabled(false, false);
}
};
var _changePause = function (pause, tweens, delayedCalls, timelines) {
tweens = (tweens !== false);
delayedCalls = (delayedCalls !== false);
timelines = (timelines !== false);
var a = getAllTweens(timelines),
allTrue = (tweens && delayedCalls && timelines),
i = a.length,
isDC, tween;
while (--i > -1) {
tween = a[i];
if (allTrue || (tween instanceof SimpleTimeline) || ((isDC = (tween.target === tween.vars.onComplete)) && delayedCalls) || (tweens && !isDC)) {
tween.paused(pause);
}
}
};
TweenMax.pauseAll = function (tweens, delayedCalls, timelines) {
_changePause(true, tweens, delayedCalls, timelines);
};
TweenMax.resumeAll = function (tweens, delayedCalls, timelines) {
_changePause(false, tweens, delayedCalls, timelines);
};
TweenMax.globalTimeScale = function (value) {
var tl = Animation._rootTimeline,
t = TweenLite.ticker.time;
if (!arguments.length) {
return tl._timeScale;
}
value = value || _tinyNum; //can't allow zero because it'll throw the math off
tl._startTime = t - ((t - tl._startTime) * tl._timeScale / value);
tl = Animation._rootFramesTimeline;
t = TweenLite.ticker.frame;
tl._startTime = t - ((t - tl._startTime) * tl._timeScale / value);
tl._timeScale = Animation._rootTimeline._timeScale = value;
return value;
};
//---- GETTERS / SETTERS ----------------------------------------------------------------------------------------------------------
p.progress = function (value, suppressEvents) {
return (!arguments.length) ? this._time / this.duration() : this.totalTime(this.duration() * ((this._yoyo && (this._cycle & 1) !== 0) ? 1 - value : value) + (this._cycle * (this._duration + this._repeatDelay)), suppressEvents);
};
p.totalProgress = function (value, suppressEvents) {
return (!arguments.length) ? this._totalTime / this.totalDuration() : this.totalTime(this.totalDuration() * value, suppressEvents);
};
p.time = function (value, suppressEvents) {
if (!arguments.length) {
return this._time;
}
if (this._dirty) {
this.totalDuration();
}
if (value > this._duration) {
value = this._duration;
}
if (this._yoyo && (this._cycle & 1) !== 0) {
value = (this._duration - value) + (this._cycle * (this._duration + this._repeatDelay));
} else if (this._repeat !== 0) {
value += this._cycle * (this._duration + this._repeatDelay);
}
return this.totalTime(value, suppressEvents);
};
p.duration = function (value) {
if (!arguments.length) {
return this._duration; //don't set _dirty = false because there could be repeats that haven't been factored into the _totalDuration yet. Otherwise, if you create a repeated TweenMax and then immediately check its duration(), it would cache the value and the totalDuration would not be correct, thus repeats wouldn't take effect.
}
return Animation.prototype.duration.call(this, value);
};
p.totalDuration = function (value) {
if (!arguments.length) {
if (this._dirty) {
//instead of Infinity, we use 999999999999 so that we can accommodate reverses
this._totalDuration = (this._repeat === -1) ? 999999999999 : this._duration * (this._repeat + 1) + (this._repeatDelay * this._repeat);
this._dirty = false;
}
return this._totalDuration;
}
return (this._repeat === -1) ? this : this.duration((value - (this._repeat * this._repeatDelay)) / (this._repeat + 1));
};
p.repeat = function (value) {
if (!arguments.length) {
return this._repeat;
}
this._repeat = value;
return this._uncache(true);
};
p.repeatDelay = function (value) {
if (!arguments.length) {
return this._repeatDelay;
}
this._repeatDelay = value;
return this._uncache(true);
};
p.yoyo = function (value) {
if (!arguments.length) {
return this._yoyo;
}
this._yoyo = value;
return this;
};
return TweenMax;
}, true);
/*
* ----------------------------------------------------------------
* TimelineLite
* ----------------------------------------------------------------
*/
_gsScope._gsDefine("TimelineLite", ["core.Animation", "core.SimpleTimeline", "TweenLite"], function (Animation, SimpleTimeline, TweenLite) {
var TimelineLite = function (vars) {
SimpleTimeline.call(this, vars);
this._labels = {};
this.autoRemoveChildren = (this.vars.autoRemoveChildren === true);
this.smoothChildTiming = (this.vars.smoothChildTiming === true);
this._sortChildren = true;
this._onUpdate = this.vars.onUpdate;
var v = this.vars,
val, p;
for (p in v) {
val = v[p];
if (_isArray(val)) if (val.join("").indexOf("{self}") !== -1) {
v[p] = this._swapSelfInParams(val);
}
}
if (_isArray(v.tweens)) {
this.add(v.tweens, 0, v.align, v.stagger);
}
},
_tinyNum = 0.0000000001,
TweenLiteInternals = TweenLite._internals,
_internals = TimelineLite._internals = {},
_isSelector = TweenLiteInternals.isSelector,
_isArray = TweenLiteInternals.isArray,
_lazyTweens = TweenLiteInternals.lazyTweens,
_lazyRender = TweenLiteInternals.lazyRender,
_globals = _gsScope._gsDefine.globals,
_copy = function (vars) {
var copy = {}, p;
for (p in vars) {
copy[p] = vars[p];
}
return copy;
},
_applyCycle = function (vars, targets, i) {
var alt = vars.cycle,
p, val;
for (p in alt) {
val = alt[p];
vars[p] = (typeof (val) === "function") ? val.call(targets[i], i) : val[i % val.length];
}
delete vars.cycle;
},
_pauseCallback = _internals.pauseCallback = function () { },
_slice = function (a) { //don't use [].slice because that doesn't work in IE8 with a NodeList that's returned by querySelectorAll()
var b = [],
l = a.length,
i;
for (i = 0; i !== l; b.push(a[i++]));
return b;
},
p = TimelineLite.prototype = new SimpleTimeline();
TimelineLite.version = "1.18.4";
p.constructor = TimelineLite;
p.kill()._gc = p._forcingPlayhead = p._hasPause = false;
/* might use later...
//translates a local time inside an animation to the corresponding time on the root/global timeline, factoring in all nesting and timeScales.
function localToGlobal(time, animation) {
while (animation) {
time = (time / animation._timeScale) + animation._startTime;
animation = animation.timeline;
}
return time;
}
//translates the supplied time on the root/global timeline into the corresponding local time inside a particular animation, factoring in all nesting and timeScales
function globalToLocal(time, animation) {
var scale = 1;
time -= localToGlobal(0, animation);
while (animation) {
scale *= animation._timeScale;
animation = animation.timeline;
}
return time * scale;
}
*/
p.to = function (target, duration, vars, position) {
var Engine = (vars.repeat && _globals.TweenMax) || TweenLite;
return duration ? this.add(new Engine(target, duration, vars), position) : this.set(target, vars, position);
};
p.from = function (target, duration, vars, position) {
return this.add(((vars.repeat && _globals.TweenMax) || TweenLite).from(target, duration, vars), position);
};
p.fromTo = function (target, duration, fromVars, toVars, position) {
var Engine = (toVars.repeat && _globals.TweenMax) || TweenLite;
return duration ? this.add(Engine.fromTo(target, duration, fromVars, toVars), position) : this.set(target, toVars, position);
};
p.staggerTo = function (targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
var tl = new TimelineLite({ onComplete: onCompleteAll, onCompleteParams: onCompleteAllParams, callbackScope: onCompleteAllScope, smoothChildTiming: this.smoothChildTiming }),
cycle = vars.cycle,
copy, i;
if (typeof (targets) === "string") {
targets = TweenLite.selector(targets) || targets;
}
targets = targets || [];
if (_isSelector(targets)) { //senses if the targets object is a selector. If it is, we should translate it into an array.
targets = _slice(targets);
}
stagger = stagger || 0;
if (stagger < 0) {
targets = _slice(targets);
targets.reverse();
stagger *= -1;
}
for (i = 0; i < targets.length; i++) {
copy = _copy(vars);
if (copy.startAt) {
copy.startAt = _copy(copy.startAt);
if (copy.startAt.cycle) {
_applyCycle(copy.startAt, targets, i);
}
}
if (cycle) {
_applyCycle(copy, targets, i);
}
tl.to(targets[i], duration, copy, i * stagger);
}
return this.add(tl, position);
};
p.staggerFrom = function (targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
vars.immediateRender = (vars.immediateRender != false);
vars.runBackwards = true;
return this.staggerTo(targets, duration, vars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
};
p.staggerFromTo = function (targets, duration, fromVars, toVars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope) {
toVars.startAt = fromVars;
toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
return this.staggerTo(targets, duration, toVars, stagger, position, onCompleteAll, onCompleteAllParams, onCompleteAllScope);
};
p.call = function (callback, params, scope, position) {
return this.add(TweenLite.delayedCall(0, callback, params, scope), position);
};
p.set = function (target, vars, position) {
position = this._parseTimeOrLabel(position, 0, true);
if (vars.immediateRender == null) {
vars.immediateRender = (position === this._time && !this._paused);
}
return this.add(new TweenLite(target, 0, vars), position);
};
TimelineLite.exportRoot = function (vars, ignoreDelayedCalls) {
vars = vars || {};
if (vars.smoothChildTiming == null) {
vars.smoothChildTiming = true;
}
var tl = new TimelineLite(vars),
root = tl._timeline,
tween, next;
if (ignoreDelayedCalls == null) {
ignoreDelayedCalls = true;
}
root._remove(tl, true);
tl._startTime = 0;
tl._rawPrevTime = tl._time = tl._totalTime = root._time;
tween = root._first;
while (tween) {
next = tween._next;
if (!ignoreDelayedCalls || !(tween instanceof TweenLite && tween.target === tween.vars.onComplete)) {
tl.add(tween, tween._startTime - tween._delay);
}
tween = next;
}
root.add(tl, 0);
return tl;
};
p.add = function (value, position, align, stagger) {
var curTime, l, i, child, tl, beforeRawTime;
if (typeof (position) !== "number") {
position = this._parseTimeOrLabel(position, 0, true, value);
}
if (!(value instanceof Animation)) {
if ((value instanceof Array) || (value && value.push && _isArray(value))) {
align = align || "normal";
stagger = stagger || 0;
curTime = position;
l = value.length;
for (i = 0; i < l; i++) {
if (_isArray(child = value[i])) {
child = new TimelineLite({ tweens: child });
}
this.add(child, curTime);
if (typeof (child) !== "string" && typeof (child) !== "function") {
if (align === "sequence") {
curTime = child._startTime + (child.totalDuration() / child._timeScale);
} else if (align === "start") {
child._startTime -= child.delay();
}
}
curTime += stagger;
}
return this._uncache(true);
} else if (typeof (value) === "string") {
return this.addLabel(value, position);
} else if (typeof (value) === "function") {
value = TweenLite.delayedCall(0, value);
} else {
throw ("Cannot add " + value + " into the timeline; it is not a tween, timeline, function, or string.");
}
}
SimpleTimeline.prototype.add.call(this, value, position);
//if the timeline has already ended but the inserted tween/timeline extends the duration, we should enable this timeline again so that it renders properly. We should also align the playhead with the parent timeline's when appropriate.
if (this._gc || this._time === this._duration) if (!this._paused) if (this._duration < this.duration()) {
//in case any of the ancestors had completed but should now be enabled...
tl = this;
beforeRawTime = (tl.rawTime() > value._startTime); //if the tween is placed on the timeline so that it starts BEFORE the current rawTime, we should align the playhead (move the timeline). This is because sometimes users will create a timeline, let it finish, and much later append a tween and expect it to run instead of jumping to its end state. While technically one could argue that it should jump to its end state, that's not what users intuitively expect.
while (tl._timeline) {
if (beforeRawTime && tl._timeline.smoothChildTiming) {
tl.totalTime(tl._totalTime, true); //moves the timeline (shifts its startTime) if necessary, and also enables it.
} else if (tl._gc) {
tl._enabled(true, false);
}
tl = tl._timeline;
}
}
return this;
};
p.remove = function (value) {
if (value instanceof Animation) {
this._remove(value, false);
var tl = value._timeline = value.vars.useFrames ? Animation._rootFramesTimeline : Animation._rootTimeline; //now that it's removed, default it to the root timeline so that if it gets played again, it doesn't jump back into this timeline.
value._startTime = (value._paused ? value._pauseTime : tl._time) - ((!value._reversed ? value._totalTime : value.totalDuration() - value._totalTime) / value._timeScale); //ensure that if it gets played again, the timing is correct.
return this;
} else if (value instanceof Array || (value && value.push && _isArray(value))) {
var i = value.length;
while (--i > -1) {
this.remove(value[i]);
}
return this;
} else if (typeof (value) === "string") {
return this.removeLabel(value);
}
return this.kill(null, value);
};
p._remove = function (tween, skipDisable) {
SimpleTimeline.prototype._remove.call(this, tween, skipDisable);
var last = this._last;
if (!last) {
this._time = this._totalTime = this._duration = this._totalDuration = 0;
} else if (this._time > last._startTime + last._totalDuration / last._timeScale) {
this._time = this.duration();
this._totalTime = this._totalDuration;
}
return this;
};
p.append = function (value, offsetOrLabel) {
return this.add(value, this._parseTimeOrLabel(null, offsetOrLabel, true, value));
};
p.insert = p.insertMultiple = function (value, position, align, stagger) {
return this.add(value, position || 0, align, stagger);
};
p.appendMultiple = function (tweens, offsetOrLabel, align, stagger) {
return this.add(tweens, this._parseTimeOrLabel(null, offsetOrLabel, true, tweens), align, stagger);
};
p.addLabel = function (label, position) {
this._labels[label] = this._parseTimeOrLabel(position);
return this;
};
p.addPause = function (position, callback, params, scope) {
var t = TweenLite.delayedCall(0, _pauseCallback, params, scope || this);
t.vars.onComplete = t.vars.onReverseComplete = callback;
t.data = "isPause";
this._hasPause = true;
return this.add(t, position);
};
p.removeLabel = function (label) {
delete this._labels[label];
return this;
};
p.getLabelTime = function (label) {
return (this._labels[label] != null) ? this._labels[label] : -1;
};
p._parseTimeOrLabel = function (timeOrLabel, offsetOrLabel, appendIfAbsent, ignore) {
var i;
//if we're about to add a tween/timeline (or an array of them) that's already a child of this timeline, we should remove it first so that it doesn't contaminate the duration().
if (ignore instanceof Animation && ignore.timeline === this) {
this.remove(ignore);
} else if (ignore && ((ignore instanceof Array) || (ignore.push && _isArray(ignore)))) {
i = ignore.length;
while (--i > -1) {
if (ignore[i] instanceof Animation && ignore[i].timeline === this) {
this.remove(ignore[i]);
}
}
}
if (typeof (offsetOrLabel) === "string") {
return this._parseTimeOrLabel(offsetOrLabel, (appendIfAbsent && typeof (timeOrLabel) === "number" && this._labels[offsetOrLabel] == null) ? timeOrLabel - this.duration() : 0, appendIfAbsent);
}
offsetOrLabel = offsetOrLabel || 0;
if (typeof (timeOrLabel) === "string" && (isNaN(timeOrLabel) || this._labels[timeOrLabel] != null)) { //if the string is a number like "1", check to see if there's a label with that name, otherwise interpret it as a number (absolute value).
i = timeOrLabel.indexOf("=");
if (i === -1) {
if (this._labels[timeOrLabel] == null) {
return appendIfAbsent ? (this._labels[timeOrLabel] = this.duration() + offsetOrLabel) : offsetOrLabel;
}
return this._labels[timeOrLabel] + offsetOrLabel;
}
offsetOrLabel = parseInt(timeOrLabel.charAt(i - 1) + "1", 10) * Number(timeOrLabel.substr(i + 1));
timeOrLabel = (i > 1) ? this._parseTimeOrLabel(timeOrLabel.substr(0, i - 1), 0, appendIfAbsent) : this.duration();
} else if (timeOrLabel == null) {
timeOrLabel = this.duration();
}
return Number(timeOrLabel) + offsetOrLabel;
};
p.seek = function (position, suppressEvents) {
return this.totalTime((typeof (position) === "number") ? position : this._parseTimeOrLabel(position), (suppressEvents !== false));
};
p.stop = function () {
return this.paused(true);
};
p.gotoAndPlay = function (position, suppressEvents) {
return this.play(position, suppressEvents);
};
p.gotoAndStop = function (position, suppressEvents) {
return this.pause(position, suppressEvents);
};
p.render = function (time, suppressEvents, force) {
if (this._gc) {
this._enabled(true, false);
}
var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
prevTime = this._time,
prevStart = this._startTime,
prevTimeScale = this._timeScale,
prevPaused = this._paused,
tween, isComplete, next, callback, internalForce, pauseTween, curTime;
if (time >= totalDur - 0.0000001) { //to work around occasional floating point math artifacts.
this._totalTime = this._time = totalDur;
if (!this._reversed) if (!this._hasPausedChild()) {
isComplete = true;
callback = "onComplete";
internalForce = !!this._timeline.autoRemoveChildren; //otherwise, if the animation is unpaused/activated after it's already finished, it doesn't get removed from the parent timeline.
if (this._duration === 0) if ((time <= 0 && time >= -0.0000001) || this._rawPrevTime < 0 || this._rawPrevTime === _tinyNum) if (this._rawPrevTime !== time && this._first) {
internalForce = true;
if (this._rawPrevTime > _tinyNum) {
callback = "onReverseComplete";
}
}
}
this._rawPrevTime = (this._duration || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
time = totalDur + 0.0001; //to avoid occasional floating point rounding errors - sometimes child tweens/timelines were not being fully completed (their progress might be 0.999999999999998 instead of 1 because when _time - tween._startTime is performed, floating point errors would return a value that was SLIGHTLY off). Try (999999999999.7 - 999999999999) * 1 = 0.699951171875 instead of 0.7.
} else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
this._totalTime = this._time = 0;
if (prevTime !== 0 || (this._duration === 0 && this._rawPrevTime !== _tinyNum && (this._rawPrevTime > 0 || (time < 0 && this._rawPrevTime >= 0)))) {
callback = "onReverseComplete";
isComplete = this._reversed;
}
if (time < 0) {
this._active = false;
if (this._timeline.autoRemoveChildren && this._reversed) { //ensures proper GC if a timeline is resumed after it's finished reversing.
internalForce = isComplete = true;
callback = "onReverseComplete";
} else if (this._rawPrevTime >= 0 && this._first) { //when going back beyond the start, force a render so that zero-duration tweens that sit at the very beginning render their start values properly. Otherwise, if the parent timeline's playhead lands exactly at this timeline's startTime, and then moves backwards, the zero-duration tweens at the beginning would still be at their end state.
internalForce = true;
}
this._rawPrevTime = time;
} else {
this._rawPrevTime = (this._duration || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
if (time === 0 && isComplete) { //if there's a zero-duration tween at the very beginning of a timeline and the playhead lands EXACTLY at time 0, that tween will correctly render its end values, but we need to keep the timeline alive for one more render so that the beginning values render properly as the parent's playhead keeps moving beyond the begining. Imagine obj.x starts at 0 and then we do tl.set(obj, {x:100}).to(obj, 1, {x:200}) and then later we tl.reverse()...the goal is to have obj.x revert to 0. If the playhead happens to land on exactly 0, without this chunk of code, it'd complete the timeline and remove it from the rendering queue (not good).
tween = this._first;
while (tween && tween._startTime === 0) {
if (!tween._duration) {
isComplete = false;
}
tween = tween._next;
}
}
time = 0; //to avoid occasional floating point rounding errors (could cause problems especially with zero-duration tweens at the very beginning of the timeline)
if (!this._initted) {
internalForce = true;
}
}
} else {
if (this._hasPause && !this._forcingPlayhead && !suppressEvents) {
if (time >= prevTime) {
tween = this._first;
while (tween && tween._startTime <= time && !pauseTween) {
if (!tween._duration) if (tween.data === "isPause" && !tween.ratio && !(tween._startTime === 0 && this._rawPrevTime === 0)) {
pauseTween = tween;
}
tween = tween._next;
}
} else {
tween = this._last;
while (tween && tween._startTime >= time && !pauseTween) {
if (!tween._duration) if (tween.data === "isPause" && tween._rawPrevTime > 0) {
pauseTween = tween;
}
tween = tween._prev;
}
}
if (pauseTween) {
this._time = time = pauseTween._startTime;
this._totalTime = time + (this._cycle * (this._totalDuration + this._repeatDelay));
}
}
this._totalTime = this._time = this._rawPrevTime = time;
}
if ((this._time === prevTime || !this._first) && !force && !internalForce && !pauseTween) {
return;
} else if (!this._initted) {
this._initted = true;
}
if (!this._active) if (!this._paused && this._time !== prevTime && time > 0) {
this._active = true; //so that if the user renders the timeline (as opposed to the parent timeline rendering it), it is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the timeline already finished but the user manually re-renders it as halfway done, for example.
}
if (prevTime === 0) if (this.vars.onStart) if (this._time !== 0) if (!suppressEvents) {
this._callback("onStart");
}
curTime = this._time;
if (curTime >= prevTime) {
tween = this._first;
while (tween) {
next = tween._next; //record it here because the value could change after rendering...
if (curTime !== this._time || (this._paused && !prevPaused)) { //in case a tween pauses or seeks the timeline when rendering, like inside of an onUpdate/onComplete
break;
} else if (tween._active || (tween._startTime <= curTime && !tween._paused && !tween._gc)) {
if (pauseTween === tween) {
this.pause();
}
if (!tween._reversed) {
tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
} else {
tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
}
}
tween = next;
}
} else {
tween = this._last;
while (tween) {
next = tween._prev; //record it here because the value could change after rendering...
if (curTime !== this._time || (this._paused && !prevPaused)) { //in case a tween pauses or seeks the timeline when rendering, like inside of an onUpdate/onComplete
break;
} else if (tween._active || (tween._startTime <= prevTime && !tween._paused && !tween._gc)) {
if (pauseTween === tween) {
pauseTween = tween._prev; //the linked list is organized by _startTime, thus it's possible that a tween could start BEFORE the pause and end after it, in which case it would be positioned before the pause tween in the linked list, but we should render it before we pause() the timeline and cease rendering. This is only a concern when going in reverse.
while (pauseTween && pauseTween.endTime() > this._time) {
pauseTween.render((pauseTween._reversed ? pauseTween.totalDuration() - ((time - pauseTween._startTime) * pauseTween._timeScale) : (time - pauseTween._startTime) * pauseTween._timeScale), suppressEvents, force);
pauseTween = pauseTween._prev;
}
pauseTween = null;
this.pause();
}
if (!tween._reversed) {
tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
} else {
tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
}
}
tween = next;
}
}
if (this._onUpdate) if (!suppressEvents) {
if (_lazyTweens.length) { //in case rendering caused any tweens to lazy-init, we should render them because typically when a timeline finishes, users expect things to have rendered fully. Imagine an onUpdate on a timeline that reports/checks tweened values.
_lazyRender();
}
this._callback("onUpdate");
}
if (callback) if (!this._gc) if (prevStart === this._startTime || prevTimeScale !== this._timeScale) if (this._time === 0 || totalDur >= this.totalDuration()) { //if one of the tweens that was rendered altered this timeline's startTime (like if an onComplete reversed the timeline), it probably isn't complete. If it is, don't worry, because whatever call altered the startTime would complete if it was necessary at the new time. The only exception is the timeScale property. Also check _gc because there's a chance that kill() could be called in an onUpdate
if (isComplete) {
if (_lazyTweens.length) { //in case rendering caused any tweens to lazy-init, we should render them because typically when a timeline finishes, users expect things to have rendered fully. Imagine an onComplete on a timeline that reports/checks tweened values.
_lazyRender();
}
if (this._timeline.autoRemoveChildren) {
this._enabled(false, false);
}
this._active = false;
}
if (!suppressEvents && this.vars[callback]) {
this._callback(callback);
}
}
};
p._hasPausedChild = function () {
var tween = this._first;
while (tween) {
if (tween._paused || ((tween instanceof TimelineLite) && tween._hasPausedChild())) {
return true;
}
tween = tween._next;
}
return false;
};
p.getChildren = function (nested, tweens, timelines, ignoreBeforeTime) {
ignoreBeforeTime = ignoreBeforeTime || -9999999999;
var a = [],
tween = this._first,
cnt = 0;
while (tween) {
if (tween._startTime < ignoreBeforeTime) {
//do nothing
} else if (tween instanceof TweenLite) {
if (tweens !== false) {
a[cnt++] = tween;
}
} else {
if (timelines !== false) {
a[cnt++] = tween;
}
if (nested !== false) {
a = a.concat(tween.getChildren(true, tweens, timelines));
cnt = a.length;
}
}
tween = tween._next;
}
return a;
};
p.getTweensOf = function (target, nested) {
var disabled = this._gc,
a = [],
cnt = 0,
tweens, i;
if (disabled) {
this._enabled(true, true); //getTweensOf() filters out disabled tweens, and we have to mark them as _gc = true when the timeline completes in order to allow clean garbage collection, so temporarily re-enable the timeline here.
}
tweens = TweenLite.getTweensOf(target);
i = tweens.length;
while (--i > -1) {
if (tweens[i].timeline === this || (nested && this._contains(tweens[i]))) {
a[cnt++] = tweens[i];
}
}
if (disabled) {
this._enabled(false, true);
}
return a;
};
p.recent = function () {
return this._recent;
};
p._contains = function (tween) {
var tl = tween.timeline;
while (tl) {
if (tl === this) {
return true;
}
tl = tl.timeline;
}
return false;
};
p.shiftChildren = function (amount, adjustLabels, ignoreBeforeTime) {
ignoreBeforeTime = ignoreBeforeTime || 0;
var tween = this._first,
labels = this._labels,
p;
while (tween) {
if (tween._startTime >= ignoreBeforeTime) {
tween._startTime += amount;
}
tween = tween._next;
}
if (adjustLabels) {
for (p in labels) {
if (labels[p] >= ignoreBeforeTime) {
labels[p] += amount;
}
}
}
return this._uncache(true);
};
p._kill = function (vars, target) {
if (!vars && !target) {
return this._enabled(false, false);
}
var tweens = (!target) ? this.getChildren(true, true, false) : this.getTweensOf(target),
i = tweens.length,
changed = false;
while (--i > -1) {
if (tweens[i]._kill(vars, target)) {
changed = true;
}
}
return changed;
};
p.clear = function (labels) {
var tweens = this.getChildren(false, true, true),
i = tweens.length;
this._time = this._totalTime = 0;
while (--i > -1) {
tweens[i]._enabled(false, false);
}
if (labels !== false) {
this._labels = {};
}
return this._uncache(true);
};
p.invalidate = function () {
var tween = this._first;
while (tween) {
tween.invalidate();
tween = tween._next;
}
return Animation.prototype.invalidate.call(this);;
};
p._enabled = function (enabled, ignoreTimeline) {
if (enabled === this._gc) {
var tween = this._first;
while (tween) {
tween._enabled(enabled, true);
tween = tween._next;
}
}
return SimpleTimeline.prototype._enabled.call(this, enabled, ignoreTimeline);
};
p.totalTime = function (time, suppressEvents, uncapped) {
this._forcingPlayhead = true;
var val = Animation.prototype.totalTime.apply(this, arguments);
this._forcingPlayhead = false;
return val;
};
p.duration = function (value) {
if (!arguments.length) {
if (this._dirty) {
this.totalDuration(); //just triggers recalculation
}
return this._duration;
}
if (this.duration() !== 0 && value !== 0) {
this.timeScale(this._duration / value);
}
return this;
};
p.totalDuration = function (value) {
if (!arguments.length) {
if (this._dirty) {
var max = 0,
tween = this._last,
prevStart = 999999999999,
prev, end;
while (tween) {
prev = tween._prev; //record it here in case the tween changes position in the sequence...
if (tween._dirty) {
tween.totalDuration(); //could change the tween._startTime, so make sure the tween's cache is clean before analyzing it.
}
if (tween._startTime > prevStart && this._sortChildren && !tween._paused) { //in case one of the tweens shifted out of order, it needs to be re-inserted into the correct position in the sequence
this.add(tween, tween._startTime - tween._delay);
} else {
prevStart = tween._startTime;
}
if (tween._startTime < 0 && !tween._paused) { //children aren't allowed to have negative startTimes unless smoothChildTiming is true, so adjust here if one is found.
max -= tween._startTime;
if (this._timeline.smoothChildTiming) {
this._startTime += tween._startTime / this._timeScale;
}
this.shiftChildren(-tween._startTime, false, -9999999999);
prevStart = 0;
}
end = tween._startTime + (tween._totalDuration / tween._timeScale);
if (end > max) {
max = end;
}
tween = prev;
}
this._duration = this._totalDuration = max;
this._dirty = false;
}
return this._totalDuration;
}
return (value && this.totalDuration()) ? this.timeScale(this._totalDuration / value) : this;
};
p.paused = function (value) {
if (!value) { //if there's a pause directly at the spot from where we're unpausing, skip it.
var tween = this._first,
time = this._time;
while (tween) {
if (tween._startTime === time && tween.data === "isPause") {
tween._rawPrevTime = 0; //remember, _rawPrevTime is how zero-duration tweens/callbacks sense directionality and determine whether or not to fire. If _rawPrevTime is the same as _startTime on the next render, it won't fire.
}
tween = tween._next;
}
}
return Animation.prototype.paused.apply(this, arguments);
};
p.usesFrames = function () {
var tl = this._timeline;
while (tl._timeline) {
tl = tl._timeline;
}
return (tl === Animation._rootFramesTimeline);
};
p.rawTime = function () {
return this._paused ? this._totalTime : (this._timeline.rawTime() - this._startTime) * this._timeScale;
};
return TimelineLite;
}, true);
/*
* ----------------------------------------------------------------
* TimelineMax
* ----------------------------------------------------------------
*/
_gsScope._gsDefine("TimelineMax", ["TimelineLite", "TweenLite", "easing.Ease"], function (TimelineLite, TweenLite, Ease) {
var TimelineMax = function (vars) {
TimelineLite.call(this, vars);
this._repeat = this.vars.repeat || 0;
this._repeatDelay = this.vars.repeatDelay || 0;
this._cycle = 0;
this._yoyo = (this.vars.yoyo === true);
this._dirty = true;
},
_tinyNum = 0.0000000001,
TweenLiteInternals = TweenLite._internals,
_lazyTweens = TweenLiteInternals.lazyTweens,
_lazyRender = TweenLiteInternals.lazyRender,
_easeNone = new Ease(null, null, 1, 0),
p = TimelineMax.prototype = new TimelineLite();
p.constructor = TimelineMax;
p.kill()._gc = false;
TimelineMax.version = "1.18.4";
p.invalidate = function () {
this._yoyo = (this.vars.yoyo === true);
this._repeat = this.vars.repeat || 0;
this._repeatDelay = this.vars.repeatDelay || 0;
this._uncache(true);
return TimelineLite.prototype.invalidate.call(this);
};
p.addCallback = function (callback, position, params, scope) {
return this.add(TweenLite.delayedCall(0, callback, params, scope), position);
};
p.removeCallback = function (callback, position) {
if (callback) {
if (position == null) {
this._kill(null, callback);
} else {
var a = this.getTweensOf(callback, false),
i = a.length,
time = this._parseTimeOrLabel(position);
while (--i > -1) {
if (a[i]._startTime === time) {
a[i]._enabled(false, false);
}
}
}
}
return this;
};
p.removePause = function (position) {
return this.removeCallback(TimelineLite._internals.pauseCallback, position);
};
p.tweenTo = function (position, vars) {
vars = vars || {};
var copy = { ease: _easeNone, useFrames: this.usesFrames(), immediateRender: false },
duration, p, t;
for (p in vars) {
copy[p] = vars[p];
}
copy.time = this._parseTimeOrLabel(position);
duration = (Math.abs(Number(copy.time) - this._time) / this._timeScale) || 0.001;
t = new TweenLite(this, duration, copy);
copy.onStart = function () {
t.target.paused(true);
if (t.vars.time !== t.target.time() && duration === t.duration()) { //don't make the duration zero - if it's supposed to be zero, don't worry because it's already initting the tween and will complete immediately, effectively making the duration zero anyway. If we make duration zero, the tween won't run at all.
t.duration(Math.abs(t.vars.time - t.target.time()) / t.target._timeScale);
}
if (vars.onStart) { //in case the user had an onStart in the vars - we don't want to overwrite it.
t._callback("onStart");
}
};
return t;
};
p.tweenFromTo = function (fromPosition, toPosition, vars) {
vars = vars || {};
fromPosition = this._parseTimeOrLabel(fromPosition);
vars.startAt = { onComplete: this.seek, onCompleteParams: [fromPosition], callbackScope: this };
vars.immediateRender = (vars.immediateRender !== false);
var t = this.tweenTo(toPosition, vars);
return t.duration((Math.abs(t.vars.time - fromPosition) / this._timeScale) || 0.001);
};
p.render = function (time, suppressEvents, force) {
if (this._gc) {
this._enabled(true, false);
}
var totalDur = (!this._dirty) ? this._totalDuration : this.totalDuration(),
dur = this._duration,
prevTime = this._time,
prevTotalTime = this._totalTime,
prevStart = this._startTime,
prevTimeScale = this._timeScale,
prevRawPrevTime = this._rawPrevTime,
prevPaused = this._paused,
prevCycle = this._cycle,
tween, isComplete, next, callback, internalForce, cycleDuration, pauseTween, curTime;
if (time >= totalDur - 0.0000001) { //to work around occasional floating point math artifacts.
if (!this._locked) {
this._totalTime = totalDur;
this._cycle = this._repeat;
}
if (!this._reversed) if (!this._hasPausedChild()) {
isComplete = true;
callback = "onComplete";
internalForce = !!this._timeline.autoRemoveChildren; //otherwise, if the animation is unpaused/activated after it's already finished, it doesn't get removed from the parent timeline.
if (this._duration === 0) if ((time <= 0 && time >= -0.0000001) || prevRawPrevTime < 0 || prevRawPrevTime === _tinyNum) if (prevRawPrevTime !== time && this._first) {
internalForce = true;
if (prevRawPrevTime > _tinyNum) {
callback = "onReverseComplete";
}
}
}
this._rawPrevTime = (this._duration || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
if (this._yoyo && (this._cycle & 1) !== 0) {
this._time = time = 0;
} else {
this._time = dur;
time = dur + 0.0001; //to avoid occasional floating point rounding errors - sometimes child tweens/timelines were not being fully completed (their progress might be 0.999999999999998 instead of 1 because when _time - tween._startTime is performed, floating point errors would return a value that was SLIGHTLY off). Try (999999999999.7 - 999999999999) * 1 = 0.699951171875 instead of 0.7. We cannot do less then 0.0001 because the same issue can occur when the duration is extremely large like 999999999999 in which case adding 0.00000001, for example, causes it to act like nothing was added.
}
} else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
if (!this._locked) {
this._totalTime = this._cycle = 0;
}
this._time = 0;
if (prevTime !== 0 || (dur === 0 && prevRawPrevTime !== _tinyNum && (prevRawPrevTime > 0 || (time < 0 && prevRawPrevTime >= 0)) && !this._locked)) { //edge case for checking time < 0 && prevRawPrevTime >= 0: a zero-duration fromTo() tween inside a zero-duration timeline (yeah, very rare)
callback = "onReverseComplete";
isComplete = this._reversed;
}
if (time < 0) {
this._active = false;
if (this._timeline.autoRemoveChildren && this._reversed) {
internalForce = isComplete = true;
callback = "onReverseComplete";
} else if (prevRawPrevTime >= 0 && this._first) { //when going back beyond the start, force a render so that zero-duration tweens that sit at the very beginning render their start values properly. Otherwise, if the parent timeline's playhead lands exactly at this timeline's startTime, and then moves backwards, the zero-duration tweens at the beginning would still be at their end state.
internalForce = true;
}
this._rawPrevTime = time;
} else {
this._rawPrevTime = (dur || !suppressEvents || time || this._rawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration timeline or tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
if (time === 0 && isComplete) { //if there's a zero-duration tween at the very beginning of a timeline and the playhead lands EXACTLY at time 0, that tween will correctly render its end values, but we need to keep the timeline alive for one more render so that the beginning values render properly as the parent's playhead keeps moving beyond the begining. Imagine obj.x starts at 0 and then we do tl.set(obj, {x:100}).to(obj, 1, {x:200}) and then later we tl.reverse()...the goal is to have obj.x revert to 0. If the playhead happens to land on exactly 0, without this chunk of code, it'd complete the timeline and remove it from the rendering queue (not good).
tween = this._first;
while (tween && tween._startTime === 0) {
if (!tween._duration) {
isComplete = false;
}
tween = tween._next;
}
}
time = 0; //to avoid occasional floating point rounding errors (could cause problems especially with zero-duration tweens at the very beginning of the timeline)
if (!this._initted) {
internalForce = true;
}
}
} else {
if (dur === 0 && prevRawPrevTime < 0) { //without this, zero-duration repeating timelines (like with a simple callback nested at the very beginning and a repeatDelay) wouldn't render the first time through.
internalForce = true;
}
this._time = this._rawPrevTime = time;
if (!this._locked) {
this._totalTime = time;
if (this._repeat !== 0) {
cycleDuration = dur + this._repeatDelay;
this._cycle = (this._totalTime / cycleDuration) >> 0; //originally _totalTime % cycleDuration but floating point errors caused problems, so I normalized it. (4 % 0.8 should be 0 but it gets reported as 0.79999999!)
if (this._cycle !== 0) if (this._cycle === this._totalTime / cycleDuration && prevTotalTime <= time) {
this._cycle--; //otherwise when rendered exactly at the end time, it will act as though it is repeating (at the beginning)
}
this._time = this._totalTime - (this._cycle * cycleDuration);
if (this._yoyo) if ((this._cycle & 1) !== 0) {
this._time = dur - this._time;
}
if (this._time > dur) {
this._time = dur;
time = dur + 0.0001; //to avoid occasional floating point rounding error
} else if (this._time < 0) {
this._time = time = 0;
} else {
time = this._time;
}
}
}
if (this._hasPause && !this._forcingPlayhead && !suppressEvents) {
time = this._time;
if (time >= prevTime) {
tween = this._first;
while (tween && tween._startTime <= time && !pauseTween) {
if (!tween._duration) if (tween.data === "isPause" && !tween.ratio && !(tween._startTime === 0 && this._rawPrevTime === 0)) {
pauseTween = tween;
}
tween = tween._next;
}
} else {
tween = this._last;
while (tween && tween._startTime >= time && !pauseTween) {
if (!tween._duration) if (tween.data === "isPause" && tween._rawPrevTime > 0) {
pauseTween = tween;
}
tween = tween._prev;
}
}
if (pauseTween) {
this._time = time = pauseTween._startTime;
this._totalTime = time + (this._cycle * (this._totalDuration + this._repeatDelay));
}
}
}
if (this._cycle !== prevCycle) if (!this._locked) {
/*
make sure children at the end/beginning of the timeline are rendered properly. If, for example,
a 3-second long timeline rendered at 2.9 seconds previously, and now renders at 3.2 seconds (which
would get transated to 2.8 seconds if the timeline yoyos or 0.2 seconds if it just repeats), there
could be a callback or a short tween that's at 2.95 or 3 seconds in which wouldn't render. So
we need to push the timeline to the end (and/or beginning depending on its yoyo value). Also we must
ensure that zero-duration tweens at the very beginning or end of the TimelineMax work.
*/
var backwards = (this._yoyo && (prevCycle & 1) !== 0),
wrap = (backwards === (this._yoyo && (this._cycle & 1) !== 0)),
recTotalTime = this._totalTime,
recCycle = this._cycle,
recRawPrevTime = this._rawPrevTime,
recTime = this._time;
this._totalTime = prevCycle * dur;
if (this._cycle < prevCycle) {
backwards = !backwards;
} else {
this._totalTime += dur;
}
this._time = prevTime; //temporarily revert _time so that render() renders the children in the correct order. Without this, tweens won't rewind correctly. We could arhictect things in a "cleaner" way by splitting out the rendering queue into a separate method but for performance reasons, we kept it all inside this method.
this._rawPrevTime = (dur === 0) ? prevRawPrevTime - 0.0001 : prevRawPrevTime;
this._cycle = prevCycle;
this._locked = true; //prevents changes to totalTime and skips repeat/yoyo behavior when we recursively call render()
prevTime = (backwards) ? 0 : dur;
this.render(prevTime, suppressEvents, (dur === 0));
if (!suppressEvents) if (!this._gc) {
if (this.vars.onRepeat) {
this._callback("onRepeat");
}
}
if (prevTime !== this._time) { //in case there's a callback like onComplete in a nested tween/timeline that changes the playhead position, like via seek(), we should just abort.
return;
}
if (wrap) {
prevTime = (backwards) ? dur + 0.0001 : -0.0001;
this.render(prevTime, true, false);
}
this._locked = false;
if (this._paused && !prevPaused) { //if the render() triggered callback that paused this timeline, we should abort (very rare, but possible)
return;
}
this._time = recTime;
this._totalTime = recTotalTime;
this._cycle = recCycle;
this._rawPrevTime = recRawPrevTime;
}
if ((this._time === prevTime || !this._first) && !force && !internalForce && !pauseTween) {
if (prevTotalTime !== this._totalTime) if (this._onUpdate) if (!suppressEvents) { //so that onUpdate fires even during the repeatDelay - as long as the totalTime changed, we should trigger onUpdate.
this._callback("onUpdate");
}
return;
} else if (!this._initted) {
this._initted = true;
}
if (!this._active) if (!this._paused && this._totalTime !== prevTotalTime && time > 0) {
this._active = true; //so that if the user renders the timeline (as opposed to the parent timeline rendering it), it is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the timeline already finished but the user manually re-renders it as halfway done, for example.
}
if (prevTotalTime === 0) if (this.vars.onStart) if (this._totalTime !== 0) if (!suppressEvents) {
this._callback("onStart");
}
curTime = this._time;
if (curTime >= prevTime) {
tween = this._first;
while (tween) {
next = tween._next; //record it here because the value could change after rendering...
if (curTime !== this._time || (this._paused && !prevPaused)) { //in case a tween pauses or seeks the timeline when rendering, like inside of an onUpdate/onComplete
break;
} else if (tween._active || (tween._startTime <= this._time && !tween._paused && !tween._gc)) {
if (pauseTween === tween) {
this.pause();
}
if (!tween._reversed) {
tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
} else {
tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
}
}
tween = next;
}
} else {
tween = this._last;
while (tween) {
next = tween._prev; //record it here because the value could change after rendering...
if (curTime !== this._time || (this._paused && !prevPaused)) { //in case a tween pauses or seeks the timeline when rendering, like inside of an onUpdate/onComplete
break;
} else if (tween._active || (tween._startTime <= prevTime && !tween._paused && !tween._gc)) {
if (pauseTween === tween) {
pauseTween = tween._prev; //the linked list is organized by _startTime, thus it's possible that a tween could start BEFORE the pause and end after it, in which case it would be positioned before the pause tween in the linked list, but we should render it before we pause() the timeline and cease rendering. This is only a concern when going in reverse.
while (pauseTween && pauseTween.endTime() > this._time) {
pauseTween.render((pauseTween._reversed ? pauseTween.totalDuration() - ((time - pauseTween._startTime) * pauseTween._timeScale) : (time - pauseTween._startTime) * pauseTween._timeScale), suppressEvents, force);
pauseTween = pauseTween._prev;
}
pauseTween = null;
this.pause();
}
if (!tween._reversed) {
tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
} else {
tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
}
}
tween = next;
}
}
if (this._onUpdate) if (!suppressEvents) {
if (_lazyTweens.length) { //in case rendering caused any tweens to lazy-init, we should render them because typically when a timeline finishes, users expect things to have rendered fully. Imagine an onUpdate on a timeline that reports/checks tweened values.
_lazyRender();
}
this._callback("onUpdate");
}
if (callback) if (!this._locked) if (!this._gc) if (prevStart === this._startTime || prevTimeScale !== this._timeScale) if (this._time === 0 || totalDur >= this.totalDuration()) { //if one of the tweens that was rendered altered this timeline's startTime (like if an onComplete reversed the timeline), it probably isn't complete. If it is, don't worry, because whatever call altered the startTime would complete if it was necessary at the new time. The only exception is the timeScale property. Also check _gc because there's a chance that kill() could be called in an onUpdate
if (isComplete) {
if (_lazyTweens.length) { //in case rendering caused any tweens to lazy-init, we should render them because typically when a timeline finishes, users expect things to have rendered fully. Imagine an onComplete on a timeline that reports/checks tweened values.
_lazyRender();
}
if (this._timeline.autoRemoveChildren) {
this._enabled(false, false);
}
this._active = false;
}
if (!suppressEvents && this.vars[callback]) {
this._callback(callback);
}
}
};
p.getActive = function (nested, tweens, timelines) {
if (nested == null) {
nested = true;
}
if (tweens == null) {
tweens = true;
}
if (timelines == null) {
timelines = false;
}
var a = [],
all = this.getChildren(nested, tweens, timelines),
cnt = 0,
l = all.length,
i, tween;
for (i = 0; i < l; i++) {
tween = all[i];
if (tween.isActive()) {
a[cnt++] = tween;
}
}
return a;
};
p.getLabelAfter = function (time) {
if (!time) if (time !== 0) { //faster than isNan()
time = this._time;
}
var labels = this.getLabelsArray(),
l = labels.length,
i;
for (i = 0; i < l; i++) {
if (labels[i].time > time) {
return labels[i].name;
}
}
return null;
};
p.getLabelBefore = function (time) {
if (time == null) {
time = this._time;
}
var labels = this.getLabelsArray(),
i = labels.length;
while (--i > -1) {
if (labels[i].time < time) {
return labels[i].name;
}
}
return null;
};
p.getLabelsArray = function () {
var a = [],
cnt = 0,
p;
for (p in this._labels) {
a[cnt++] = { time: this._labels[p], name: p };
}
a.sort(function (a, b) {
return a.time - b.time;
});
return a;
};
//---- GETTERS / SETTERS -------------------------------------------------------------------------------------------------------
p.progress = function (value, suppressEvents) {
return (!arguments.length) ? this._time / this.duration() : this.totalTime(this.duration() * ((this._yoyo && (this._cycle & 1) !== 0) ? 1 - value : value) + (this._cycle * (this._duration + this._repeatDelay)), suppressEvents);
};
p.totalProgress = function (value, suppressEvents) {
return (!arguments.length) ? this._totalTime / this.totalDuration() : this.totalTime(this.totalDuration() * value, suppressEvents);
};
p.totalDuration = function (value) {
if (!arguments.length) {
if (this._dirty) {
TimelineLite.prototype.totalDuration.call(this); //just forces refresh
//Instead of Infinity, we use 999999999999 so that we can accommodate reverses.
this._totalDuration = (this._repeat === -1) ? 999999999999 : this._duration * (this._repeat + 1) + (this._repeatDelay * this._repeat);
}
return this._totalDuration;
}
return (this._repeat === -1 || !value) ? this : this.timeScale(this.totalDuration() / value);
};
p.time = function (value, suppressEvents) {
if (!arguments.length) {
return this._time;
}
if (this._dirty) {
this.totalDuration();
}
if (value > this._duration) {
value = this._duration;
}
if (this._yoyo && (this._cycle & 1) !== 0) {
value = (this._duration - value) + (this._cycle * (this._duration + this._repeatDelay));
} else if (this._repeat !== 0) {
value += this._cycle * (this._duration + this._repeatDelay);
}
return this.totalTime(value, suppressEvents);
};
p.repeat = function (value) {
if (!arguments.length) {
return this._repeat;
}
this._repeat = value;
return this._uncache(true);
};
p.repeatDelay = function (value) {
if (!arguments.length) {
return this._repeatDelay;
}
this._repeatDelay = value;
return this._uncache(true);
};
p.yoyo = function (value) {
if (!arguments.length) {
return this._yoyo;
}
this._yoyo = value;
return this;
};
p.currentLabel = function (value) {
if (!arguments.length) {
return this.getLabelBefore(this._time + 0.00000001);
}
return this.seek(value, true);
};
return TimelineMax;
}, true);
/*
* ----------------------------------------------------------------
* BezierPlugin
* ----------------------------------------------------------------
*/
(function () {
var _RAD2DEG = 180 / Math.PI,
_r1 = [],
_r2 = [],
_r3 = [],
_corProps = {},
_globals = _gsScope._gsDefine.globals,
Segment = function (a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.da = d - a;
this.ca = c - a;
this.ba = b - a;
},
_correlate = ",x,y,z,left,top,right,bottom,marginTop,marginLeft,marginRight,marginBottom,paddingLeft,paddingTop,paddingRight,paddingBottom,backgroundPosition,backgroundPosition_y,",
cubicToQuadratic = function (a, b, c, d) {
var q1 = { a: a },
q2 = {},
q3 = {},
q4 = { c: d },
mab = (a + b) / 2,
mbc = (b + c) / 2,
mcd = (c + d) / 2,
mabc = (mab + mbc) / 2,
mbcd = (mbc + mcd) / 2,
m8 = (mbcd - mabc) / 8;
q1.b = mab + (a - mab) / 4;
q2.b = mabc + m8;
q1.c = q2.a = (q1.b + q2.b) / 2;
q2.c = q3.a = (mabc + mbcd) / 2;
q3.b = mbcd - m8;
q4.b = mcd + (d - mcd) / 4;
q3.c = q4.a = (q3.b + q4.b) / 2;
return [q1, q2, q3, q4];
},
_calculateControlPoints = function (a, curviness, quad, basic, correlate) {
var l = a.length - 1,
ii = 0,
cp1 = a[0].a,
i, p1, p2, p3, seg, m1, m2, mm, cp2, qb, r1, r2, tl;
for (i = 0; i < l; i++) {
seg = a[ii];
p1 = seg.a;
p2 = seg.d;
p3 = a[ii + 1].d;
if (correlate) {
r1 = _r1[i];
r2 = _r2[i];
tl = ((r2 + r1) * curviness * 0.25) / (basic ? 0.5 : _r3[i] || 0.5);
m1 = p2 - (p2 - p1) * (basic ? curviness * 0.5 : (r1 !== 0 ? tl / r1 : 0));
m2 = p2 + (p3 - p2) * (basic ? curviness * 0.5 : (r2 !== 0 ? tl / r2 : 0));
mm = p2 - (m1 + (((m2 - m1) * ((r1 * 3 / (r1 + r2)) + 0.5) / 4) || 0));
} else {
m1 = p2 - (p2 - p1) * curviness * 0.5;
m2 = p2 + (p3 - p2) * curviness * 0.5;
mm = p2 - (m1 + m2) / 2;
}
m1 += mm;
m2 += mm;
seg.c = cp2 = m1;
if (i !== 0) {
seg.b = cp1;
} else {
seg.b = cp1 = seg.a + (seg.c - seg.a) * 0.6; //instead of placing b on a exactly, we move it inline with c so that if the user specifies an ease like Back.easeIn or Elastic.easeIn which goes BEYOND the beginning, it will do so smoothly.
}
seg.da = p2 - p1;
seg.ca = cp2 - p1;
seg.ba = cp1 - p1;
if (quad) {
qb = cubicToQuadratic(p1, cp1, cp2, p2);
a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]);
ii += 4;
} else {
ii++;
}
cp1 = m2;
}
seg = a[ii];
seg.b = cp1;
seg.c = cp1 + (seg.d - cp1) * 0.4; //instead of placing c on d exactly, we move it inline with b so that if the user specifies an ease like Back.easeOut or Elastic.easeOut which goes BEYOND the end, it will do so smoothly.
seg.da = seg.d - seg.a;
seg.ca = seg.c - seg.a;
seg.ba = cp1 - seg.a;
if (quad) {
qb = cubicToQuadratic(seg.a, cp1, seg.c, seg.d);
a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]);
}
},
_parseAnchors = function (values, p, correlate, prepend) {
var a = [],
l, i, p1, p2, p3, tmp;
if (prepend) {
values = [prepend].concat(values);
i = values.length;
while (--i > -1) {
if (typeof ((tmp = values[i][p])) === "string") if (tmp.charAt(1) === "=") {
values[i][p] = prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)); //accommodate relative values. Do it inline instead of breaking it out into a function for speed reasons
}
}
}
l = values.length - 2;
if (l < 0) {
a[0] = new Segment(values[0][p], 0, 0, values[(l < -1) ? 0 : 1][p]);
return a;
}
for (i = 0; i < l; i++) {
p1 = values[i][p];
p2 = values[i + 1][p];
a[i] = new Segment(p1, 0, 0, p2);
if (correlate) {
p3 = values[i + 2][p];
_r1[i] = (_r1[i] || 0) + (p2 - p1) * (p2 - p1);
_r2[i] = (_r2[i] || 0) + (p3 - p2) * (p3 - p2);
}
}
a[i] = new Segment(values[i][p], 0, 0, values[i + 1][p]);
return a;
},
bezierThrough = function (values, curviness, quadratic, basic, correlate, prepend) {
var obj = {},
props = [],
first = prepend || values[0],
i, p, a, j, r, l, seamless, last;
correlate = (typeof (correlate) === "string") ? "," + correlate + "," : _correlate;
if (curviness == null) {
curviness = 1;
}
for (p in values[0]) {
props.push(p);
}
//check to see if the last and first values are identical (well, within 0.05). If so, make seamless by appending the second element to the very end of the values array and the 2nd-to-last element to the very beginning (we'll remove those segments later)
if (values.length > 1) {
last = values[values.length - 1];
seamless = true;
i = props.length;
while (--i > -1) {
p = props[i];
if (Math.abs(first[p] - last[p]) > 0.05) { //build in a tolerance of +/-0.05 to accommodate rounding errors.
seamless = false;
break;
}
}
if (seamless) {
values = values.concat(); //duplicate the array to avoid contaminating the original which the user may be reusing for other tweens
if (prepend) {
values.unshift(prepend);
}
values.push(values[1]);
prepend = values[values.length - 3];
}
}
_r1.length = _r2.length = _r3.length = 0;
i = props.length;
while (--i > -1) {
p = props[i];
_corProps[p] = (correlate.indexOf("," + p + ",") !== -1);
obj[p] = _parseAnchors(values, p, _corProps[p], prepend);
}
i = _r1.length;
while (--i > -1) {
_r1[i] = Math.sqrt(_r1[i]);
_r2[i] = Math.sqrt(_r2[i]);
}
if (!basic) {
i = props.length;
while (--i > -1) {
if (_corProps[p]) {
a = obj[props[i]];
l = a.length - 1;
for (j = 0; j < l; j++) {
r = (a[j + 1].da / _r2[j] + a[j].da / _r1[j]) || 0;
_r3[j] = (_r3[j] || 0) + r * r;
}
}
}
i = _r3.length;
while (--i > -1) {
_r3[i] = Math.sqrt(_r3[i]);
}
}
i = props.length;
j = quadratic ? 4 : 1;
while (--i > -1) {
p = props[i];
a = obj[p];
_calculateControlPoints(a, curviness, quadratic, basic, _corProps[p]); //this method requires that _parseAnchors() and _setSegmentRatios() ran first so that _r1, _r2, and _r3 values are populated for all properties
if (seamless) {
a.splice(0, j);
a.splice(a.length - j, j);
}
}
return obj;
},
_parseBezierData = function (values, type, prepend) {
type = type || "soft";
var obj = {},
inc = (type === "cubic") ? 3 : 2,
soft = (type === "soft"),
props = [],
a, b, c, d, cur, i, j, l, p, cnt, tmp;
if (soft && prepend) {
values = [prepend].concat(values);
}
if (values == null || values.length < inc + 1) { throw "invalid Bezier data"; }
for (p in values[0]) {
props.push(p);
}
i = props.length;
while (--i > -1) {
p = props[i];
obj[p] = cur = [];
cnt = 0;
l = values.length;
for (j = 0; j < l; j++) {
a = (prepend == null) ? values[j][p] : (typeof ((tmp = values[j][p])) === "string" && tmp.charAt(1) === "=") ? prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)) : Number(tmp);
if (soft) if (j > 1) if (j < l - 1) {
cur[cnt++] = (a + cur[cnt - 2]) / 2;
}
cur[cnt++] = a;
}
l = cnt - inc + 1;
cnt = 0;
for (j = 0; j < l; j += inc) {
a = cur[j];
b = cur[j + 1];
c = cur[j + 2];
d = (inc === 2) ? 0 : cur[j + 3];
cur[cnt++] = tmp = (inc === 3) ? new Segment(a, b, c, d) : new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c);
}
cur.length = cnt;
}
return obj;
},
_addCubicLengths = function (a, steps, resolution) {
var inc = 1 / resolution,
j = a.length,
d, d1, s, da, ca, ba, p, i, inv, bez, index;
while (--j > -1) {
bez = a[j];
s = bez.a;
da = bez.d - s;
ca = bez.c - s;
ba = bez.b - s;
d = d1 = 0;
for (i = 1; i <= resolution; i++) {
p = inc * i;
inv = 1 - p;
d = d1 - (d1 = (p * p * da + 3 * inv * (p * ca + inv * ba)) * p);
index = j * resolution + i - 1;
steps[index] = (steps[index] || 0) + d * d;
}
}
},
_parseLengthData = function (obj, resolution) {
resolution = resolution >> 0 || 6;
var a = [],
lengths = [],
d = 0,
total = 0,
threshold = resolution - 1,
segments = [],
curLS = [], //current length segments array
p, i, l, index;
for (p in obj) {
_addCubicLengths(obj[p], a, resolution);
}
l = a.length;
for (i = 0; i < l; i++) {
d += Math.sqrt(a[i]);
index = i % resolution;
curLS[index] = d;
if (index === threshold) {
total += d;
index = (i / resolution) >> 0;
segments[index] = curLS;
lengths[index] = total;
d = 0;
curLS = [];
}
}
return { length: total, lengths: lengths, segments: segments };
},
BezierPlugin = _gsScope._gsDefine.plugin({
propName: "bezier",
priority: -1,
version: "1.3.5",
API: 2,
global: true,
//gets called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
init: function (target, vars, tween) {
this._target = target;
if (vars instanceof Array) {
vars = { values: vars };
}
this._func = {};
this._round = {};
this._props = [];
this._timeRes = (vars.timeResolution == null) ? 6 : parseInt(vars.timeResolution, 10);
var values = vars.values || [],
first = {},
second = values[0],
autoRotate = vars.autoRotate || tween.vars.orientToBezier,
p, isFunc, i, j, prepend;
this._autoRotate = autoRotate ? (autoRotate instanceof Array) ? autoRotate : [["x", "y", "rotation", ((autoRotate === true) ? 0 : Number(autoRotate) || 0)]] : null;
for (p in second) {
this._props.push(p);
}
i = this._props.length;
while (--i > -1) {
p = this._props[i];
this._overwriteProps.push(p);
isFunc = this._func[p] = (typeof (target[p]) === "function");
first[p] = (!isFunc) ? parseFloat(target[p]) : target[((p.indexOf("set") || typeof (target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3))]();
if (!prepend) if (first[p] !== values[0][p]) {
prepend = first;
}
}
this._beziers = (vars.type !== "cubic" && vars.type !== "quadratic" && vars.type !== "soft") ? bezierThrough(values, isNaN(vars.curviness) ? 1 : vars.curviness, false, (vars.type === "thruBasic"), vars.correlate, prepend) : _parseBezierData(values, vars.type, first);
this._segCount = this._beziers[p].length;
if (this._timeRes) {
var ld = _parseLengthData(this._beziers, this._timeRes);
this._length = ld.length;
this._lengths = ld.lengths;
this._segments = ld.segments;
this._l1 = this._li = this._s1 = this._si = 0;
this._l2 = this._lengths[0];
this._curSeg = this._segments[0];
this._s2 = this._curSeg[0];
this._prec = 1 / this._curSeg.length;
}
if ((autoRotate = this._autoRotate)) {
this._initialRotations = [];
if (!(autoRotate[0] instanceof Array)) {
this._autoRotate = autoRotate = [autoRotate];
}
i = autoRotate.length;
while (--i > -1) {
for (j = 0; j < 3; j++) {
p = autoRotate[i][j];
this._func[p] = (typeof (target[p]) === "function") ? target[((p.indexOf("set") || typeof (target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3))] : false;
}
p = autoRotate[i][2];
this._initialRotations[i] = (this._func[p] ? this._func[p].call(this._target) : this._target[p]) || 0;
}
}
this._startRatio = tween.vars.runBackwards ? 1 : 0; //we determine the starting ratio when the tween inits which is always 0 unless the tween has runBackwards:true (indicating it's a from() tween) in which case it's 1.
return true;
},
//called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.)
set: function (v) {
var segments = this._segCount,
func = this._func,
target = this._target,
notStart = (v !== this._startRatio),
curIndex, inv, i, p, b, t, val, l, lengths, curSeg;
if (!this._timeRes) {
curIndex = (v < 0) ? 0 : (v >= 1) ? segments - 1 : (segments * v) >> 0;
t = (v - (curIndex * (1 / segments))) * segments;
} else {
lengths = this._lengths;
curSeg = this._curSeg;
v *= this._length;
i = this._li;
//find the appropriate segment (if the currently cached one isn't correct)
if (v > this._l2 && i < segments - 1) {
l = segments - 1;
while (i < l && (this._l2 = lengths[++i]) <= v) { }
this._l1 = lengths[i - 1];
this._li = i;
this._curSeg = curSeg = this._segments[i];
this._s2 = curSeg[(this._s1 = this._si = 0)];
} else if (v < this._l1 && i > 0) {
while (i > 0 && (this._l1 = lengths[--i]) >= v) { }
if (i === 0 && v < this._l1) {
this._l1 = 0;
} else {
i++;
}
this._l2 = lengths[i];
this._li = i;
this._curSeg = curSeg = this._segments[i];
this._s1 = curSeg[(this._si = curSeg.length - 1) - 1] || 0;
this._s2 = curSeg[this._si];
}
curIndex = i;
//now find the appropriate sub-segment (we split it into the number of pieces that was defined by "precision" and measured each one)
v -= this._l1;
i = this._si;
if (v > this._s2 && i < curSeg.length - 1) {
l = curSeg.length - 1;
while (i < l && (this._s2 = curSeg[++i]) <= v) { }
this._s1 = curSeg[i - 1];
this._si = i;
} else if (v < this._s1 && i > 0) {
while (i > 0 && (this._s1 = curSeg[--i]) >= v) { }
if (i === 0 && v < this._s1) {
this._s1 = 0;
} else {
i++;
}
this._s2 = curSeg[i];
this._si = i;
}
t = ((i + (v - this._s1) / (this._s2 - this._s1)) * this._prec) || 0;
}
inv = 1 - t;
i = this._props.length;
while (--i > -1) {
p = this._props[i];
b = this._beziers[p][curIndex];
val = (t * t * b.da + 3 * inv * (t * b.ca + inv * b.ba)) * t + b.a;
if (this._round[p]) {
val = Math.round(val);
}
if (func[p]) {
target[p](val);
} else {
target[p] = val;
}
}
if (this._autoRotate) {
var ar = this._autoRotate,
b2, x1, y1, x2, y2, add, conv;
i = ar.length;
while (--i > -1) {
p = ar[i][2];
add = ar[i][3] || 0;
conv = (ar[i][4] === true) ? 1 : _RAD2DEG;
b = this._beziers[ar[i][0]];
b2 = this._beziers[ar[i][1]];
if (b && b2) { //in case one of the properties got overwritten.
b = b[curIndex];
b2 = b2[curIndex];
x1 = b.a + (b.b - b.a) * t;
x2 = b.b + (b.c - b.b) * t;
x1 += (x2 - x1) * t;
x2 += ((b.c + (b.d - b.c) * t) - x2) * t;
y1 = b2.a + (b2.b - b2.a) * t;
y2 = b2.b + (b2.c - b2.b) * t;
y1 += (y2 - y1) * t;
y2 += ((b2.c + (b2.d - b2.c) * t) - y2) * t;
val = notStart ? Math.atan2(y2 - y1, x2 - x1) * conv + add : this._initialRotations[i];
if (func[p]) {
target[p](val);
} else {
target[p] = val;
}
}
}
}
}
}),
p = BezierPlugin.prototype;
BezierPlugin.bezierThrough = bezierThrough;
BezierPlugin.cubicToQuadratic = cubicToQuadratic;
BezierPlugin._autoCSS = true; //indicates that this plugin can be inserted into the "css" object using the autoCSS feature of TweenLite
BezierPlugin.quadraticToCubic = function (a, b, c) {
return new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c);
};
BezierPlugin._cssRegister = function () {
var CSSPlugin = _globals.CSSPlugin;
if (!CSSPlugin) {
return;
}
var _internals = CSSPlugin._internals,
_parseToProxy = _internals._parseToProxy,
_setPluginRatio = _internals._setPluginRatio,
CSSPropTween = _internals.CSSPropTween;
_internals._registerComplexSpecialProp("bezier", {
parser: function (t, e, prop, cssp, pt, plugin) {
if (e instanceof Array) {
e = { values: e };
}
plugin = new BezierPlugin();
var values = e.values,
l = values.length - 1,
pluginValues = [],
v = {},
i, p, data;
if (l < 0) {
return pt;
}
for (i = 0; i <= l; i++) {
data = _parseToProxy(t, values[i], cssp, pt, plugin, (l !== i));
pluginValues[i] = data.end;
}
for (p in e) {
v[p] = e[p]; //duplicate the vars object because we need to alter some things which would cause problems if the user plans to reuse the same vars object for another tween.
}
v.values = pluginValues;
pt = new CSSPropTween(t, "bezier", 0, 0, data.pt, 2);
pt.data = data;
pt.plugin = plugin;
pt.setRatio = _setPluginRatio;
if (v.autoRotate === 0) {
v.autoRotate = true;
}
if (v.autoRotate && !(v.autoRotate instanceof Array)) {
i = (v.autoRotate === true) ? 0 : Number(v.autoRotate);
v.autoRotate = (data.end.left != null) ? [["left", "top", "rotation", i, false]] : (data.end.x != null) ? [["x", "y", "rotation", i, false]] : false;
}
if (v.autoRotate) {
if (!cssp._transform) {
cssp._enableTransforms(false);
}
data.autoRotate = cssp._target._gsTransform;
}
plugin._onInitTween(data.proxy, v, cssp._tween);
return pt;
}
});
};
p._roundProps = function (lookup, value) {
var op = this._overwriteProps,
i = op.length;
while (--i > -1) {
if (lookup[op[i]] || lookup.bezier || lookup.bezierThrough) {
this._round[op[i]] = value;
}
}
};
p._kill = function (lookup) {
var a = this._props,
p, i;
for (p in this._beziers) {
if (p in lookup) {
delete this._beziers[p];
delete this._func[p];
i = a.length;
while (--i > -1) {
if (a[i] === p) {
a.splice(i, 1);
}
}
}
}
return this._super._kill.call(this, lookup);
};
}());
/*
* ----------------------------------------------------------------
* CSSPlugin
* ----------------------------------------------------------------
*/
_gsScope._gsDefine("plugins.CSSPlugin", ["plugins.TweenPlugin", "TweenLite"], function (TweenPlugin, TweenLite) {
/** @constructor **/
var CSSPlugin = function () {
TweenPlugin.call(this, "css");
this._overwriteProps.length = 0;
this.setRatio = CSSPlugin.prototype.setRatio; //speed optimization (avoid prototype lookup on this "hot" method)
},
_globals = _gsScope._gsDefine.globals,
_hasPriority, //turns true whenever a CSSPropTween instance is created that has a priority other than 0. This helps us discern whether or not we should spend the time organizing the linked list or not after a CSSPlugin's _onInitTween() method is called.
_suffixMap, //we set this in _onInitTween() each time as a way to have a persistent variable we can use in other methods like _parse() without having to pass it around as a parameter and we keep _parse() decoupled from a particular CSSPlugin instance
_cs, //computed style (we store this in a shared variable to conserve memory and make minification tighter
_overwriteProps, //alias to the currently instantiating CSSPlugin's _overwriteProps array. We use this closure in order to avoid having to pass a reference around from method to method and aid in minification.
_specialProps = {},
p = CSSPlugin.prototype = new TweenPlugin("css");
p.constructor = CSSPlugin;
CSSPlugin.version = "1.18.4";
CSSPlugin.API = 2;
CSSPlugin.defaultTransformPerspective = 0;
CSSPlugin.defaultSkewType = "compensated";
CSSPlugin.defaultSmoothOrigin = true;
p = "px"; //we'll reuse the "p" variable to keep file size down
CSSPlugin.suffixMap = { top: p, right: p, bottom: p, left: p, width: p, height: p, fontSize: p, padding: p, margin: p, perspective: p, lineHeight: "" };
var _numExp = /(?:\-|\.|\b)(\d|\.|e\-)+/g,
_relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g,
_valuesExp = /(?:\+=|\-=|\-|\b)[\d\-\.]+[a-zA-Z0-9]*(?:%|\b)/gi, //finds all the values that begin with numbers or += or -= and then a number. Includes suffixes. We use this to split complex values apart like "1px 5px 20px rgb(255,102,51)"
_NaNExp = /(?![+-]?\d*\.?\d+|[+-]|e[+-]\d+)[^0-9]/g, //also allows scientific notation and doesn't kill the leading -/+ in -= and +=
_suffixExp = /(?:\d|\-|\+|=|#|\.)*/g,
_opacityExp = /opacity *= *([^)]*)/i,
_opacityValExp = /opacity:([^;]*)/i,
_alphaFilterExp = /alpha\(opacity *=.+?\)/i,
_rgbhslExp = /^(rgb|hsl)/,
_capsExp = /([A-Z])/g,
_camelExp = /-([a-z])/gi,
_urlExp = /(^(?:url\(\"|url\())|(?:(\"\))$|\)$)/gi, //for pulling out urls from url(...) or url("...") strings (some browsers wrap urls in quotes, some don't when reporting things like backgroundImage)
_camelFunc = function (s, g) { return g.toUpperCase(); },
_horizExp = /(?:Left|Right|Width)/i,
_ieGetMatrixExp = /(M11|M12|M21|M22)=[\d\-\.e]+/gi,
_ieSetMatrixExp = /progid\:DXImageTransform\.Microsoft\.Matrix\(.+?\)/i,
_commasOutsideParenExp = /,(?=[^\)]*(?:\(|$))/gi, //finds any commas that are not within parenthesis
_complexExp = /[\s,\(]/i, //for testing a string to find if it has a space, comma, or open parenthesis (clues that it's a complex value)
_DEG2RAD = Math.PI / 180,
_RAD2DEG = 180 / Math.PI,
_forcePT = {},
_doc = document,
_createElement = function (type) {
return _doc.createElementNS ? _doc.createElementNS("http://www.w3.org/1999/xhtml", type) : _doc.createElement(type);
},
_tempDiv = _createElement("div"),
_tempImg = _createElement("img"),
_internals = CSSPlugin._internals = { _specialProps: _specialProps }, //provides a hook to a few internal methods that we need to access from inside other plugins
_agent = navigator.userAgent,
_autoRound,
_reqSafariFix, //we won't apply the Safari transform fix until we actually come across a tween that affects a transform property (to maintain best performance).
_isSafari,
_isFirefox, //Firefox has a bug that causes 3D transformed elements to randomly disappear unless a repaint is forced after each update on each element.
_isSafariLT6, //Safari (and Android 4 which uses a flavor of Safari) has a bug that prevents changes to "top" and "left" properties from rendering properly if changed on the same frame as a transform UNLESS we set the element's WebkitBackfaceVisibility to hidden (weird, I know). Doing this for Android 3 and earlier seems to actually cause other problems, though (fun!)
_ieVers,
_supportsOpacity = (function () { //we set _isSafari, _ieVers, _isFirefox, and _supportsOpacity all in one function here to reduce file size slightly, especially in the minified version.
var i = _agent.indexOf("Android"),
a = _createElement("a");
_isSafari = (_agent.indexOf("Safari") !== -1 && _agent.indexOf("Chrome") === -1 && (i === -1 || Number(_agent.substr(i + 8, 1)) > 3));
_isSafariLT6 = (_isSafari && (Number(_agent.substr(_agent.indexOf("Version/") + 8, 1)) < 6));
_isFirefox = (_agent.indexOf("Firefox") !== -1);
if ((/MSIE ([0-9]{1,}[\.0-9]{0,})/).exec(_agent) || (/Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/).exec(_agent)) {
_ieVers = parseFloat(RegExp.$1);
}
if (!a) {
return false;
}
a.style.cssText = "top:1px;opacity:.55;";
return /^0.55/.test(a.style.opacity);
}()),
_getIEOpacity = function (v) {
return (_opacityExp.test(((typeof (v) === "string") ? v : (v.currentStyle ? v.currentStyle.filter : v.style.filter) || "")) ? (parseFloat(RegExp.$1) / 100) : 1);
},
_log = function (s) {//for logging messages, but in a way that won't throw errors in old versions of IE.
if (window.console) {
console.log(s);
}
},
_prefixCSS = "", //the non-camelCase vendor prefix like "-o-", "-moz-", "-ms-", or "-webkit-"
_prefix = "", //camelCase vendor prefix like "O", "ms", "Webkit", or "Moz".
// @private feed in a camelCase property name like "transform" and it will check to see if it is valid as-is or if it needs a vendor prefix. It returns the corrected camelCase property name (i.e. "WebkitTransform" or "MozTransform" or "transform" or null if no such property is found, like if the browser is IE8 or before, "transform" won't be found at all)
_checkPropPrefix = function (p, e) {
e = e || _tempDiv;
var s = e.style,
a, i;
if (s[p] !== undefined) {
return p;
}
p = p.charAt(0).toUpperCase() + p.substr(1);
a = ["O", "Moz", "ms", "Ms", "Webkit"];
i = 5;
while (--i > -1 && s[a[i] + p] === undefined) { }
if (i >= 0) {
_prefix = (i === 3) ? "ms" : a[i];
_prefixCSS = "-" + _prefix.toLowerCase() + "-";
return _prefix + p;
}
return null;
},
_getComputedStyle = _doc.defaultView ? _doc.defaultView.getComputedStyle : function () { },
/**
* @private Returns the css style for a particular property of an element. For example, to get whatever the current "left" css value for an element with an ID of "myElement", you could do:
* var currentLeft = CSSPlugin.getStyle( document.getElementById("myElement"), "left");
*
* @param {!Object} t Target element whose style property you want to query
* @param {!string} p Property name (like "left" or "top" or "marginTop", etc.)
* @param {Object=} cs Computed style object. This just provides a way to speed processing if you're going to get several properties on the same element in quick succession - you can reuse the result of the getComputedStyle() call.
* @param {boolean=} calc If true, the value will not be read directly from the element's "style" property (if it exists there), but instead the getComputedStyle() result will be used. This can be useful when you want to ensure that the browser itself is interpreting the value.
* @param {string=} dflt Default value that should be returned in the place of null, "none", "auto" or "auto auto".
* @return {?string} The current property value
*/
_getStyle = CSSPlugin.getStyle = function (t, p, cs, calc, dflt) {
var rv;
if (!_supportsOpacity) if (p === "opacity") { //several versions of IE don't use the standard "opacity" property - they use things like filter:alpha(opacity=50), so we parse that here.
return _getIEOpacity(t);
}
if (!calc && t.style[p]) {
rv = t.style[p];
} else if ((cs = cs || _getComputedStyle(t))) {
rv = cs[p] || cs.getPropertyValue(p) || cs.getPropertyValue(p.replace(_capsExp, "-$1").toLowerCase());
} else if (t.currentStyle) {
rv = t.currentStyle[p];
}
return (dflt != null && (!rv || rv === "none" || rv === "auto" || rv === "auto auto")) ? dflt : rv;
},
/**
* @private Pass the target element, the property name, the numeric value, and the suffix (like "%", "em", "px", etc.) and it will spit back the equivalent pixel number.
* @param {!Object} t Target element
* @param {!string} p Property name (like "left", "top", "marginLeft", etc.)
* @param {!number} v Value
* @param {string=} sfx Suffix (like "px" or "%" or "em")
* @param {boolean=} recurse If true, the call is a recursive one. In some browsers (like IE7/8), occasionally the value isn't accurately reported initially, but if we run the function again it will take effect.
* @return {number} value in pixels
*/
_convertToPixels = _internals.convertToPixels = function (t, p, v, sfx, recurse) {
if (sfx === "px" || !sfx) { return v; }
if (sfx === "auto" || !v) { return 0; }
var horiz = _horizExp.test(p),
node = t,
style = _tempDiv.style,
neg = (v < 0),
pix, cache, time;
if (neg) {
v = -v;
}
if (sfx === "%" && p.indexOf("border") !== -1) {
pix = (v / 100) * (horiz ? t.clientWidth : t.clientHeight);
} else {
style.cssText = "border:0 solid red;position:" + _getStyle(t, "position") + ";line-height:0;";
if (sfx === "%" || !node.appendChild || sfx.charAt(0) === "v" || sfx === "rem") {
node = t.parentNode || _doc.body;
cache = node._gsCache;
time = TweenLite.ticker.frame;
if (cache && horiz && cache.time === time) { //performance optimization: we record the width of elements along with the ticker frame so that we can quickly get it again on the same tick (seems relatively safe to assume it wouldn't change on the same tick)
return cache.width * v / 100;
}
style[(horiz ? "width" : "height")] = v + sfx;
} else {
style[(horiz ? "borderLeftWidth" : "borderTopWidth")] = v + sfx;
}
node.appendChild(_tempDiv);
pix = parseFloat(_tempDiv[(horiz ? "offsetWidth" : "offsetHeight")]);
node.removeChild(_tempDiv);
if (horiz && sfx === "%" && CSSPlugin.cacheWidths !== false) {
cache = node._gsCache = node._gsCache || {};
cache.time = time;
cache.width = pix / v * 100;
}
if (pix === 0 && !recurse) {
pix = _convertToPixels(t, p, v, sfx, true);
}
}
return neg ? -pix : pix;
},
_calculateOffset = _internals.calculateOffset = function (t, p, cs) { //for figuring out "top" or "left" in px when it's "auto". We need to factor in margin with the offsetLeft/offsetTop
if (_getStyle(t, "position", cs) !== "absolute") { return 0; }
var dim = ((p === "left") ? "Left" : "Top"),
v = _getStyle(t, "margin" + dim, cs);
return t["offset" + dim] - (_convertToPixels(t, p, parseFloat(v), v.replace(_suffixExp, "")) || 0);
},
// @private returns at object containing ALL of the style properties in camelCase and their associated values.
_getAllStyles = function (t, cs) {
var s = {},
i, tr, p;
if ((cs = cs || _getComputedStyle(t, null))) {
if ((i = cs.length)) {
while (--i > -1) {
p = cs[i];
if (p.indexOf("-transform") === -1 || _transformPropCSS === p) { //Some webkit browsers duplicate transform values, one non-prefixed and one prefixed ("transform" and "WebkitTransform"), so we must weed out the extra one here.
s[p.replace(_camelExp, _camelFunc)] = cs.getPropertyValue(p);
}
}
} else { //some browsers behave differently - cs.length is always 0, so we must do a for...in loop.
for (i in cs) {
if (i.indexOf("Transform") === -1 || _transformProp === i) { //Some webkit browsers duplicate transform values, one non-prefixed and one prefixed ("transform" and "WebkitTransform"), so we must weed out the extra one here.
s[i] = cs[i];
}
}
}
} else if ((cs = t.currentStyle || t.style)) {
for (i in cs) {
if (typeof (i) === "string" && s[i] === undefined) {
s[i.replace(_camelExp, _camelFunc)] = cs[i];
}
}
}
if (!_supportsOpacity) {
s.opacity = _getIEOpacity(t);
}
tr = _getTransform(t, cs, false);
s.rotation = tr.rotation;
s.skewX = tr.skewX;
s.scaleX = tr.scaleX;
s.scaleY = tr.scaleY;
s.x = tr.x;
s.y = tr.y;
if (_supports3D) {
s.z = tr.z;
s.rotationX = tr.rotationX;
s.rotationY = tr.rotationY;
s.scaleZ = tr.scaleZ;
}
if (s.filters) {
delete s.filters;
}
return s;
},
// @private analyzes two style objects (as returned by _getAllStyles()) and only looks for differences between them that contain tweenable values (like a number or color). It returns an object with a "difs" property which refers to an object containing only those isolated properties and values for tweening, and a "firstMPT" property which refers to the first MiniPropTween instance in a linked list that recorded all the starting values of the different properties so that we can revert to them at the end or beginning of the tween - we don't want the cascading to get messed up. The forceLookup parameter is an optional generic object with properties that should be forced into the results - this is necessary for className tweens that are overwriting others because imagine a scenario where a rollover/rollout adds/removes a class and the user swipes the mouse over the target SUPER fast, thus nothing actually changed yet and the subsequent comparison of the properties would indicate they match (especially when px rounding is taken into consideration), thus no tweening is necessary even though it SHOULD tween and remove those properties after the tween (otherwise the inline styles will contaminate things). See the className SpecialProp code for details.
_cssDif = function (t, s1, s2, vars, forceLookup) {
var difs = {},
style = t.style,
val, p, mpt;
for (p in s2) {
if (p !== "cssText") if (p !== "length") if (isNaN(p)) if (s1[p] !== (val = s2[p]) || (forceLookup && forceLookup[p])) if (p.indexOf("Origin") === -1) if (typeof (val) === "number" || typeof (val) === "string") {
difs[p] = (val === "auto" && (p === "left" || p === "top")) ? _calculateOffset(t, p) : ((val === "" || val === "auto" || val === "none") && typeof (s1[p]) === "string" && s1[p].replace(_NaNExp, "") !== "") ? 0 : val; //if the ending value is defaulting ("" or "auto"), we check the starting value and if it can be parsed into a number (a string which could have a suffix too, like 700px), then we swap in 0 for "" or "auto" so that things actually tween.
if (style[p] !== undefined) { //for className tweens, we must remember which properties already existed inline - the ones that didn't should be removed when the tween isn't in progress because they were only introduced to facilitate the transition between classes.
mpt = new MiniPropTween(style, p, style[p], mpt);
}
}
}
if (vars) {
for (p in vars) { //copy properties (except className)
if (p !== "className") {
difs[p] = vars[p];
}
}
}
return { difs: difs, firstMPT: mpt };
},
_dimensions = { width: ["Left", "Right"], height: ["Top", "Bottom"] },
_margins = ["marginLeft", "marginRight", "marginTop", "marginBottom"],
/**
* @private Gets the width or height of an element
* @param {!Object} t Target element
* @param {!string} p Property name ("width" or "height")
* @param {Object=} cs Computed style object (if one exists). Just a speed optimization.
* @return {number} Dimension (in pixels)
*/
_getDimension = function (t, p, cs) {
if ((t.nodeName + "").toLowerCase() === "svg") { //Chrome no longer supports offsetWidth/offsetHeight on SVG elements.
return (cs || _getComputedStyle(t))[p] || 0;
} else if (t.getBBox && _isSVG(t)) {
return t.getBBox()[p] || 0;
}
var v = parseFloat((p === "width") ? t.offsetWidth : t.offsetHeight),
a = _dimensions[p],
i = a.length;
cs = cs || _getComputedStyle(t, null);
while (--i > -1) {
v -= parseFloat(_getStyle(t, "padding" + a[i], cs, true)) || 0;
v -= parseFloat(_getStyle(t, "border" + a[i] + "Width", cs, true)) || 0;
}
return v;
},
// @private Parses position-related complex strings like "top left" or "50px 10px" or "70% 20%", etc. which are used for things like transformOrigin or backgroundPosition. Optionally decorates a supplied object (recObj) with the following properties: "ox" (offsetX), "oy" (offsetY), "oxp" (if true, "ox" is a percentage not a pixel value), and "oxy" (if true, "oy" is a percentage not a pixel value)
_parsePosition = function (v, recObj) {
if (v === "contain" || v === "auto" || v === "auto auto") {
return v + " ";
}
if (v == null || v === "") { //note: Firefox uses "auto auto" as default whereas Chrome uses "auto".
v = "0 0";
}
var a = v.split(" "),
x = (v.indexOf("left") !== -1) ? "0%" : (v.indexOf("right") !== -1) ? "100%" : a[0],
y = (v.indexOf("top") !== -1) ? "0%" : (v.indexOf("bottom") !== -1) ? "100%" : a[1],
i;
if (a.length > 3 && !recObj) { //multiple positions
a = v.split(", ").join(",").split(",");
v = [];
for (i = 0; i < a.length; i++) {
v.push(_parsePosition(a[i]));
}
return v.join(",");
}
if (y == null) {
y = (x === "center") ? "50%" : "0";
} else if (y === "center") {
y = "50%";
}
if (x === "center" || (isNaN(parseFloat(x)) && (x + "").indexOf("=") === -1)) { //remember, the user could flip-flop the values and say "bottom center" or "center bottom", etc. "center" is ambiguous because it could be used to describe horizontal or vertical, hence the isNaN(). If there's an "=" sign in the value, it's relative.
x = "50%";
}
v = x + " " + y + ((a.length > 2) ? " " + a[2] : "");
if (recObj) {
recObj.oxp = (x.indexOf("%") !== -1);
recObj.oyp = (y.indexOf("%") !== -1);
recObj.oxr = (x.charAt(1) === "=");
recObj.oyr = (y.charAt(1) === "=");
recObj.ox = parseFloat(x.replace(_NaNExp, ""));
recObj.oy = parseFloat(y.replace(_NaNExp, ""));
recObj.v = v;
}
return recObj || v;
},
/**
* @private Takes an ending value (typically a string, but can be a number) and a starting value and returns the change between the two, looking for relative value indicators like += and -= and it also ignores suffixes (but make sure the ending value starts with a number or +=/-= and that the starting value is a NUMBER!)
* @param {(number|string)} e End value which is typically a string, but could be a number
* @param {(number|string)} b Beginning value which is typically a string but could be a number
* @return {number} Amount of change between the beginning and ending values (relative values that have a "+=" or "-=" are recognized)
*/
_parseChange = function (e, b) {
return (typeof (e) === "string" && e.charAt(1) === "=") ? parseInt(e.charAt(0) + "1", 10) * parseFloat(e.substr(2)) : (parseFloat(e) - parseFloat(b)) || 0;
},
/**
* @private Takes a value and a default number, checks if the value is relative, null, or numeric and spits back a normalized number accordingly. Primarily used in the _parseTransform() function.
* @param {Object} v Value to be parsed
* @param {!number} d Default value (which is also used for relative calculations if "+=" or "-=" is found in the first parameter)
* @return {number} Parsed value
*/
_parseVal = function (v, d) {
return (v == null) ? d : (typeof (v) === "string" && v.charAt(1) === "=") ? parseInt(v.charAt(0) + "1", 10) * parseFloat(v.substr(2)) + d : parseFloat(v) || 0;
},
/**
* @private Translates strings like "40deg" or "40" or 40rad" or "+=40deg" or "270_short" or "-90_cw" or "+=45_ccw" to a numeric radian angle. Of course a starting/default value must be fed in too so that relative values can be calculated properly.
* @param {Object} v Value to be parsed
* @param {!number} d Default value (which is also used for relative calculations if "+=" or "-=" is found in the first parameter)
* @param {string=} p property name for directionalEnd (optional - only used when the parsed value is directional ("_short", "_cw", or "_ccw" suffix). We need a way to store the uncompensated value so that at the end of the tween, we set it to exactly what was requested with no directional compensation). Property name would be "rotation", "rotationX", or "rotationY"
* @param {Object=} directionalEnd An object that will store the raw end values for directional angles ("_short", "_cw", or "_ccw" suffix). We need a way to store the uncompensated value so that at the end of the tween, we set it to exactly what was requested with no directional compensation.
* @return {number} parsed angle in radians
*/
_parseAngle = function (v, d, p, directionalEnd) {
var min = 0.000001,
cap, split, dif, result, isRelative;
if (v == null) {
result = d;
} else if (typeof (v) === "number") {
result = v;
} else {
cap = 360;
split = v.split("_");
isRelative = (v.charAt(1) === "=");
dif = (isRelative ? parseInt(v.charAt(0) + "1", 10) * parseFloat(split[0].substr(2)) : parseFloat(split[0])) * ((v.indexOf("rad") === -1) ? 1 : _RAD2DEG) - (isRelative ? 0 : d);
if (split.length) {
if (directionalEnd) {
directionalEnd[p] = d + dif;
}
if (v.indexOf("short") !== -1) {
dif = dif % cap;
if (dif !== dif % (cap / 2)) {
dif = (dif < 0) ? dif + cap : dif - cap;
}
}
if (v.indexOf("_cw") !== -1 && dif < 0) {
dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
} else if (v.indexOf("ccw") !== -1 && dif > 0) {
dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
}
}
result = d + dif;
}
if (result < min && result > -min) {
result = 0;
}
return result;
},
_colorLookup = {
aqua: [0, 255, 255],
lime: [0, 255, 0],
silver: [192, 192, 192],
black: [0, 0, 0],
maroon: [128, 0, 0],
teal: [0, 128, 128],
blue: [0, 0, 255],
navy: [0, 0, 128],
white: [255, 255, 255],
fuchsia: [255, 0, 255],
olive: [128, 128, 0],
yellow: [255, 255, 0],
orange: [255, 165, 0],
gray: [128, 128, 128],
purple: [128, 0, 128],
green: [0, 128, 0],
red: [255, 0, 0],
pink: [255, 192, 203],
cyan: [0, 255, 255],
transparent: [255, 255, 255, 0]
},
_hue = function (h, m1, m2) {
h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h;
return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < 0.5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * 255) + 0.5) | 0;
},
/**
* @private Parses a color (like #9F0, #FF9900, rgb(255,51,153) or hsl(108, 50%, 10%)) into an array with 3 elements for red, green, and blue or if toHSL parameter is true, it will populate the array with hue, saturation, and lightness values. If a relative value is found in an hsl() or hsla() string, it will preserve those relative prefixes and all the values in the array will be strings instead of numbers (in all other cases it will be populated with numbers).
* @param {(string|number)} v The value the should be parsed which could be a string like #9F0 or rgb(255,102,51) or rgba(255,0,0,0.5) or it could be a number like 0xFF00CC or even a named color like red, blue, purple, etc.
* @param {(boolean)} toHSL If true, an hsl() or hsla() value will be returned instead of rgb() or rgba()
* @return {Array.<number>} An array containing red, green, and blue (and optionally alpha) in that order, or if the toHSL parameter was true, the array will contain hue, saturation and lightness (and optionally alpha) in that order. Always numbers unless there's a relative prefix found in an hsl() or hsla() string and toHSL is true.
*/
_parseColor = CSSPlugin.parseColor = function (v, toHSL) {
var a, r, g, b, h, s, l, max, min, d, wasHSL;
if (!v) {
a = _colorLookup.black;
} else if (typeof (v) === "number") {
a = [v >> 16, (v >> 8) & 255, v & 255];
} else {
if (v.charAt(v.length - 1) === ",") { //sometimes a trailing comma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value.
v = v.substr(0, v.length - 1);
}
if (_colorLookup[v]) {
a = _colorLookup[v];
} else if (v.charAt(0) === "#") {
if (v.length === 4) { //for shorthand like #9F0
r = v.charAt(1);
g = v.charAt(2);
b = v.charAt(3);
v = "#" + r + r + g + g + b + b;
}
v = parseInt(v.substr(1), 16);
a = [v >> 16, (v >> 8) & 255, v & 255];
} else if (v.substr(0, 3) === "hsl") {
a = wasHSL = v.match(_numExp);
if (!toHSL) {
h = (Number(a[0]) % 360) / 360;
s = Number(a[1]) / 100;
l = Number(a[2]) / 100;
g = (l <= 0.5) ? l * (s + 1) : l + s - l * s;
r = l * 2 - g;
if (a.length > 3) {
a[3] = Number(v[3]);
}
a[0] = _hue(h + 1 / 3, r, g);
a[1] = _hue(h, r, g);
a[2] = _hue(h - 1 / 3, r, g);
} else if (v.indexOf("=") !== -1) { //if relative values are found, just return the raw strings with the relative prefixes in place.
return v.match(_relNumExp);
}
} else {
a = v.match(_numExp) || _colorLookup.transparent;
}
a[0] = Number(a[0]);
a[1] = Number(a[1]);
a[2] = Number(a[2]);
if (a.length > 3) {
a[3] = Number(a[3]);
}
}
if (toHSL && !wasHSL) {
r = a[0] / 255;
g = a[1] / 255;
b = a[2] / 255;
max = Math.max(r, g, b);
min = Math.min(r, g, b);
l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
h = (max === r) ? (g - b) / d + (g < b ? 6 : 0) : (max === g) ? (b - r) / d + 2 : (r - g) / d + 4;
h *= 60;
}
a[0] = (h + 0.5) | 0;
a[1] = (s * 100 + 0.5) | 0;
a[2] = (l * 100 + 0.5) | 0;
}
return a;
},
_formatColors = function (s, toHSL) {
var colors = s.match(_colorExp) || [],
charIndex = 0,
parsed = colors.length ? "" : s,
i, color, temp;
for (i = 0; i < colors.length; i++) {
color = colors[i];
temp = s.substr(charIndex, s.indexOf(color, charIndex) - charIndex);
charIndex += temp.length + color.length;
color = _parseColor(color, toHSL);
if (color.length === 3) {
color.push(1);
}
parsed += temp + (toHSL ? "hsla(" + color[0] + "," + color[1] + "%," + color[2] + "%," + color[3] : "rgba(" + color.join(",")) + ")";
}
return parsed + s.substr(charIndex);
},
_colorExp = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3}){1,2}\\b"; //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc.
for (p in _colorLookup) {
_colorExp += "|" + p + "\\b";
}
_colorExp = new RegExp(_colorExp + ")", "gi");
CSSPlugin.colorStringFilter = function (a) {
var combined = a[0] + a[1],
toHSL;
if (_colorExp.test(combined)) {
toHSL = (combined.indexOf("hsl(") !== -1 || combined.indexOf("hsla(") !== -1);
a[0] = _formatColors(a[0], toHSL);
a[1] = _formatColors(a[1], toHSL);
}
_colorExp.lastIndex = 0;
};
if (!TweenLite.defaultStringFilter) {
TweenLite.defaultStringFilter = CSSPlugin.colorStringFilter;
}
/**
* @private Returns a formatter function that handles taking a string (or number in some cases) and returning a consistently formatted one in terms of delimiters, quantity of values, etc. For example, we may get boxShadow values defined as "0px red" or "0px 0px 10px rgb(255,0,0)" or "0px 0px 20px 20px #F00" and we need to ensure that what we get back is described with 4 numbers and a color. This allows us to feed it into the _parseComplex() method and split the values up appropriately. The neat thing about this _getFormatter() function is that the dflt defines a pattern as well as a default, so for example, _getFormatter("0px 0px 0px 0px #777", true) not only sets the default as 0px for all distances and #777 for the color, but also sets the pattern such that 4 numbers and a color will always get returned.
* @param {!string} dflt The default value and pattern to follow. So "0px 0px 0px 0px #777" will ensure that 4 numbers and a color will always get returned.
* @param {boolean=} clr If true, the values should be searched for color-related data. For example, boxShadow values typically contain a color whereas borderRadius don't.
* @param {boolean=} collapsible If true, the value is a top/left/right/bottom style one that acts like margin or padding, where if only one value is received, it's used for all 4; if 2 are received, the first is duplicated for 3rd (bottom) and the 2nd is duplicated for the 4th spot (left), etc.
* @return {Function} formatter function
*/
var _getFormatter = function (dflt, clr, collapsible, multi) {
if (dflt == null) {
return function (v) { return v; };
}
var dColor = clr ? (dflt.match(_colorExp) || [""])[0] : "",
dVals = dflt.split(dColor).join("").match(_valuesExp) || [],
pfx = dflt.substr(0, dflt.indexOf(dVals[0])),
sfx = (dflt.charAt(dflt.length - 1) === ")") ? ")" : "",
delim = (dflt.indexOf(" ") !== -1) ? " " : ",",
numVals = dVals.length,
dSfx = (numVals > 0) ? dVals[0].replace(_numExp, "") : "",
formatter;
if (!numVals) {
return function (v) { return v; };
}
if (clr) {
formatter = function (v) {
var color, vals, i, a;
if (typeof (v) === "number") {
v += dSfx;
} else if (multi && _commasOutsideParenExp.test(v)) {
a = v.replace(_commasOutsideParenExp, "|").split("|");
for (i = 0; i < a.length; i++) {
a[i] = formatter(a[i]);
}
return a.join(",");
}
color = (v.match(_colorExp) || [dColor])[0];
vals = v.split(color).join("").match(_valuesExp) || [];
i = vals.length;
if (numVals > i--) {
while (++i < numVals) {
vals[i] = collapsible ? vals[(((i - 1) / 2) | 0)] : dVals[i];
}
}
return pfx + vals.join(delim) + delim + color + sfx + (v.indexOf("inset") !== -1 ? " inset" : "");
};
return formatter;
}
formatter = function (v) {
var vals, a, i;
if (typeof (v) === "number") {
v += dSfx;
} else if (multi && _commasOutsideParenExp.test(v)) {
a = v.replace(_commasOutsideParenExp, "|").split("|");
for (i = 0; i < a.length; i++) {
a[i] = formatter(a[i]);
}
return a.join(",");
}
vals = v.match(_valuesExp) || [];
i = vals.length;
if (numVals > i--) {
while (++i < numVals) {
vals[i] = collapsible ? vals[(((i - 1) / 2) | 0)] : dVals[i];
}
}
return pfx + vals.join(delim) + sfx;
};
return formatter;
},
/**
* @private returns a formatter function that's used for edge-related values like marginTop, marginLeft, paddingBottom, paddingRight, etc. Just pass a comma-delimited list of property names related to the edges.
* @param {!string} props a comma-delimited list of property names in order from top to left, like "marginTop,marginRight,marginBottom,marginLeft"
* @return {Function} a formatter function
*/
_getEdgeParser = function (props) {
props = props.split(",");
return function (t, e, p, cssp, pt, plugin, vars) {
var a = (e + "").split(" "),
i;
vars = {};
for (i = 0; i < 4; i++) {
vars[props[i]] = a[i] = a[i] || a[(((i - 1) / 2) >> 0)];
}
return cssp.parse(t, vars, pt, plugin);
};
},
// @private used when other plugins must tween values first, like BezierPlugin or ThrowPropsPlugin, etc. That plugin's setRatio() gets called first so that the values are updated, and then we loop through the MiniPropTweens which handle copying the values into their appropriate slots so that they can then be applied correctly in the main CSSPlugin setRatio() method. Remember, we typically create a proxy object that has a bunch of uniquely-named properties that we feed to the sub-plugin and it does its magic normally, and then we must interpret those values and apply them to the css because often numbers must get combined/concatenated, suffixes added, etc. to work with css, like boxShadow could have 4 values plus a color.
_setPluginRatio = _internals._setPluginRatio = function (v) {
this.plugin.setRatio(v);
var d = this.data,
proxy = d.proxy,
mpt = d.firstMPT,
min = 0.000001,
val, pt, i, str, p;
while (mpt) {
val = proxy[mpt.v];
if (mpt.r) {
val = Math.round(val);
} else if (val < min && val > -min) {
val = 0;
}
mpt.t[mpt.p] = val;
mpt = mpt._next;
}
if (d.autoRotate) {
d.autoRotate.rotation = proxy.rotation;
}
//at the end, we must set the CSSPropTween's "e" (end) value dynamically here because that's what is used in the final setRatio() method. Same for "b" at the beginning.
if (v === 1 || v === 0) {
mpt = d.firstMPT;
p = (v === 1) ? "e" : "b";
while (mpt) {
pt = mpt.t;
if (!pt.type) {
pt[p] = pt.s + pt.xs0;
} else if (pt.type === 1) {
str = pt.xs0 + pt.s + pt.xs1;
for (i = 1; i < pt.l; i++) {
str += pt["xn" + i] + pt["xs" + (i + 1)];
}
pt[p] = str;
}
mpt = mpt._next;
}
}
},
/**
* @private @constructor Used by a few SpecialProps to hold important values for proxies. For example, _parseToProxy() creates a MiniPropTween instance for each property that must get tweened on the proxy, and we record the original property name as well as the unique one we create for the proxy, plus whether or not the value needs to be rounded plus the original value.
* @param {!Object} t target object whose property we're tweening (often a CSSPropTween)
* @param {!string} p property name
* @param {(number|string|object)} v value
* @param {MiniPropTween=} next next MiniPropTween in the linked list
* @param {boolean=} r if true, the tweened value should be rounded to the nearest integer
*/
MiniPropTween = function (t, p, v, next, r) {
this.t = t;
this.p = p;
this.v = v;
this.r = r;
if (next) {
next._prev = this;
this._next = next;
}
},
/**
* @private Most other plugins (like BezierPlugin and ThrowPropsPlugin and others) can only tween numeric values, but CSSPlugin must accommodate special values that have a bunch of extra data (like a suffix or strings between numeric values, etc.). For example, boxShadow has values like "10px 10px 20px 30px rgb(255,0,0)" which would utterly confuse other plugins. This method allows us to split that data apart and grab only the numeric data and attach it to uniquely-named properties of a generic proxy object ({}) so that we can feed that to virtually any plugin to have the numbers tweened. However, we must also keep track of which properties from the proxy go with which CSSPropTween values and instances. So we create a linked list of MiniPropTweens. Each one records a target (the original CSSPropTween), property (like "s" or "xn1" or "xn2") that we're tweening and the unique property name that was used for the proxy (like "boxShadow_xn1" and "boxShadow_xn2") and whether or not they need to be rounded. That way, in the _setPluginRatio() method we can simply copy the values over from the proxy to the CSSPropTween instance(s). Then, when the main CSSPlugin setRatio() method runs and applies the CSSPropTween values accordingly, they're updated nicely. So the external plugin tweens the numbers, _setPluginRatio() copies them over, and setRatio() acts normally, applying css-specific values to the element.
* This method returns an object that has the following properties:
* - proxy: a generic object containing the starting values for all the properties that will be tweened by the external plugin. This is what we feed to the external _onInitTween() as the target
* - end: a generic object containing the ending values for all the properties that will be tweened by the external plugin. This is what we feed to the external plugin's _onInitTween() as the destination values
* - firstMPT: the first MiniPropTween in the linked list
* - pt: the first CSSPropTween in the linked list that was created when parsing. If shallow is true, this linked list will NOT attach to the one passed into the _parseToProxy() as the "pt" (4th) parameter.
* @param {!Object} t target object to be tweened
* @param {!(Object|string)} vars the object containing the information about the tweening values (typically the end/destination values) that should be parsed
* @param {!CSSPlugin} cssp The CSSPlugin instance
* @param {CSSPropTween=} pt the next CSSPropTween in the linked list
* @param {TweenPlugin=} plugin the external TweenPlugin instance that will be handling tweening the numeric values
* @param {boolean=} shallow if true, the resulting linked list from the parse will NOT be attached to the CSSPropTween that was passed in as the "pt" (4th) parameter.
* @return An object containing the following properties: proxy, end, firstMPT, and pt (see above for descriptions)
*/
_parseToProxy = _internals._parseToProxy = function (t, vars, cssp, pt, plugin, shallow) {
var bpt = pt,
start = {},
end = {},
transform = cssp._transform,
oldForce = _forcePT,
i, p, xp, mpt, firstPT;
cssp._transform = null;
_forcePT = vars;
pt = firstPT = cssp.parse(t, vars, pt, plugin);
_forcePT = oldForce;
//break off from the linked list so the new ones are isolated.
if (shallow) {
cssp._transform = transform;
if (bpt) {
bpt._prev = null;
if (bpt._prev) {
bpt._prev._next = null;
}
}
}
while (pt && pt !== bpt) {
if (pt.type <= 1) {
p = pt.p;
end[p] = pt.s + pt.c;
start[p] = pt.s;
if (!shallow) {
mpt = new MiniPropTween(pt, "s", p, mpt, pt.r);
pt.c = 0;
}
if (pt.type === 1) {
i = pt.l;
while (--i > 0) {
xp = "xn" + i;
p = pt.p + "_" + xp;
end[p] = pt.data[xp];
start[p] = pt[xp];
if (!shallow) {
mpt = new MiniPropTween(pt, xp, p, mpt, pt.rxp[xp]);
}
}
}
}
pt = pt._next;
}
return { proxy: start, end: end, firstMPT: mpt, pt: firstPT };
},
/**
* @constructor Each property that is tweened has at least one CSSPropTween associated with it. These instances store important information like the target, property, starting value, amount of change, etc. They can also optionally have a number of "extra" strings and numeric values named xs1, xn1, xs2, xn2, xs3, xn3, etc. where "s" indicates string and "n" indicates number. These can be pieced together in a complex-value tween (type:1) that has alternating types of data like a string, number, string, number, etc. For example, boxShadow could be "5px 5px 8px rgb(102, 102, 51)". In that value, there are 6 numbers that may need to tween and then pieced back together into a string again with spaces, suffixes, etc. xs0 is special in that it stores the suffix for standard (type:0) tweens, -OR- the first string (prefix) in a complex-value (type:1) CSSPropTween -OR- it can be the non-tweening value in a type:-1 CSSPropTween. We do this to conserve memory.
* CSSPropTweens have the following optional properties as well (not defined through the constructor):
* - l: Length in terms of the number of extra properties that the CSSPropTween has (default: 0). For example, for a boxShadow we may need to tween 5 numbers in which case l would be 5; Keep in mind that the start/end values for the first number that's tweened are always stored in the s and c properties to conserve memory. All additional values thereafter are stored in xn1, xn2, etc.
* - xfirst: The first instance of any sub-CSSPropTweens that are tweening properties of this instance. For example, we may split up a boxShadow tween so that there's a main CSSPropTween of type:1 that has various xs* and xn* values associated with the h-shadow, v-shadow, blur, color, etc. Then we spawn a CSSPropTween for each of those that has a higher priority and runs BEFORE the main CSSPropTween so that the values are all set by the time it needs to re-assemble them. The xfirst gives us an easy way to identify the first one in that chain which typically ends at the main one (because they're all prepende to the linked list)
* - plugin: The TweenPlugin instance that will handle the tweening of any complex values. For example, sometimes we don't want to use normal subtweens (like xfirst refers to) to tween the values - we might want ThrowPropsPlugin or BezierPlugin some other plugin to do the actual tweening, so we create a plugin instance and store a reference here. We need this reference so that if we get a request to round values or disable a tween, we can pass along that request.
* - data: Arbitrary data that needs to be stored with the CSSPropTween. Typically if we're going to have a plugin handle the tweening of a complex-value tween, we create a generic object that stores the END values that we're tweening to and the CSSPropTween's xs1, xs2, etc. have the starting values. We store that object as data. That way, we can simply pass that object to the plugin and use the CSSPropTween as the target.
* - setRatio: Only used for type:2 tweens that require custom functionality. In this case, we call the CSSPropTween's setRatio() method and pass the ratio each time the tween updates. This isn't quite as efficient as doing things directly in the CSSPlugin's setRatio() method, but it's very convenient and flexible.
* @param {!Object} t Target object whose property will be tweened. Often a DOM element, but not always. It could be anything.
* @param {string} p Property to tween (name). For example, to tween element.width, p would be "width".
* @param {number} s Starting numeric value
* @param {number} c Change in numeric value over the course of the entire tween. For example, if element.width starts at 5 and should end at 100, c would be 95.
* @param {CSSPropTween=} next The next CSSPropTween in the linked list. If one is defined, we will define its _prev as the new instance, and the new instance's _next will be pointed at it.
* @param {number=} type The type of CSSPropTween where -1 = a non-tweening value, 0 = a standard simple tween, 1 = a complex value (like one that has multiple numbers in a comma- or space-delimited string like border:"1px solid red"), and 2 = one that uses a custom setRatio function that does all of the work of applying the values on each update.
* @param {string=} n Name of the property that should be used for overwriting purposes which is typically the same as p but not always. For example, we may need to create a subtween for the 2nd part of a "clip:rect(...)" tween in which case "p" might be xs1 but "n" is still "clip"
* @param {boolean=} r If true, the value(s) should be rounded
* @param {number=} pr Priority in the linked list order. Higher priority CSSPropTweens will be updated before lower priority ones. The default priority is 0.
* @param {string=} b Beginning value. We store this to ensure that it is EXACTLY what it was when the tween began without any risk of interpretation issues.
* @param {string=} e Ending value. We store this to ensure that it is EXACTLY what the user defined at the end of the tween without any risk of interpretation issues.
*/
CSSPropTween = _internals.CSSPropTween = function (t, p, s, c, next, type, n, r, pr, b, e) {
this.t = t; //target
this.p = p; //property
this.s = s; //starting value
this.c = c; //change value
this.n = n || p; //name that this CSSPropTween should be associated to (usually the same as p, but not always - n is what overwriting looks at)
if (!(t instanceof CSSPropTween)) {
_overwriteProps.push(this.n);
}
this.r = r; //round (boolean)
this.type = type || 0; //0 = normal tween, -1 = non-tweening (in which case xs0 will be applied to the target's property, like tp.t[tp.p] = tp.xs0), 1 = complex-value SpecialProp, 2 = custom setRatio() that does all the work
if (pr) {
this.pr = pr;
_hasPriority = true;
}
this.b = (b === undefined) ? s : b;
this.e = (e === undefined) ? s + c : e;
if (next) {
this._next = next;
next._prev = this;
}
},
_addNonTweeningNumericPT = function (target, prop, start, end, next, overwriteProp) { //cleans up some code redundancies and helps minification. Just a fast way to add a NUMERIC non-tweening CSSPropTween
var pt = new CSSPropTween(target, prop, start, end - start, next, -1, overwriteProp);
pt.b = start;
pt.e = pt.xs0 = end;
return pt;
},
/**
* Takes a target, the beginning value and ending value (as strings) and parses them into a CSSPropTween (possibly with child CSSPropTweens) that accommodates multiple numbers, colors, comma-delimited values, etc. For example:
* sp.parseComplex(element, "boxShadow", "5px 10px 20px rgb(255,102,51)", "0px 0px 0px red", true, "0px 0px 0px rgb(0,0,0,0)", pt);
* It will walk through the beginning and ending values (which should be in the same format with the same number and type of values) and figure out which parts are numbers, what strings separate the numeric/tweenable values, and then create the CSSPropTweens accordingly. If a plugin is defined, no child CSSPropTweens will be created. Instead, the ending values will be stored in the "data" property of the returned CSSPropTween like: {s:-5, xn1:-10, xn2:-20, xn3:255, xn4:0, xn5:0} so that it can be fed to any other plugin and it'll be plain numeric tweens but the recomposition of the complex value will be handled inside CSSPlugin's setRatio().
* If a setRatio is defined, the type of the CSSPropTween will be set to 2 and recomposition of the values will be the responsibility of that method.
*
* @param {!Object} t Target whose property will be tweened
* @param {!string} p Property that will be tweened (its name, like "left" or "backgroundColor" or "boxShadow")
* @param {string} b Beginning value
* @param {string} e Ending value
* @param {boolean} clrs If true, the value could contain a color value like "rgb(255,0,0)" or "#F00" or "red". The default is false, so no colors will be recognized (a performance optimization)
* @param {(string|number|Object)} dflt The default beginning value that should be used if no valid beginning value is defined or if the number of values inside the complex beginning and ending values don't match
* @param {?CSSPropTween} pt CSSPropTween instance that is the current head of the linked list (we'll prepend to this).
* @param {number=} pr Priority in the linked list order. Higher priority properties will be updated before lower priority ones. The default priority is 0.
* @param {TweenPlugin=} plugin If a plugin should handle the tweening of extra properties, pass the plugin instance here. If one is defined, then NO subtweens will be created for any extra properties (the properties will be created - just not additional CSSPropTween instances to tween them) because the plugin is expected to do so. However, the end values WILL be populated in the "data" property, like {s:100, xn1:50, xn2:300}
* @param {function(number)=} setRatio If values should be set in a custom function instead of being pieced together in a type:1 (complex-value) CSSPropTween, define that custom function here.
* @return {CSSPropTween} The first CSSPropTween in the linked list which includes the new one(s) added by the parseComplex() call.
*/
_parseComplex = CSSPlugin.parseComplex = function (t, p, b, e, clrs, dflt, pt, pr, plugin, setRatio) {
//DEBUG: _log("parseComplex: "+p+", b: "+b+", e: "+e);
b = b || dflt || "";
pt = new CSSPropTween(t, p, 0, 0, pt, (setRatio ? 2 : 1), null, false, pr, b, e);
e += ""; //ensures it's a string
if (clrs && _colorExp.test(e + b)) { //if colors are found, normalize the formatting to rgba() or hsla().
e = [b, e];
CSSPlugin.colorStringFilter(e);
b = e[0];
e = e[1];
}
var ba = b.split(", ").join(",").split(" "), //beginning array
ea = e.split(", ").join(",").split(" "), //ending array
l = ba.length,
autoRound = (_autoRound !== false),
i, xi, ni, bv, ev, bnums, enums, bn, hasAlpha, temp, cv, str, useHSL;
if (e.indexOf(",") !== -1 || b.indexOf(",") !== -1) {
ba = ba.join(" ").replace(_commasOutsideParenExp, ", ").split(" ");
ea = ea.join(" ").replace(_commasOutsideParenExp, ", ").split(" ");
l = ba.length;
}
if (l !== ea.length) {
//DEBUG: _log("mismatched formatting detected on " + p + " (" + b + " vs " + e + ")");
ba = (dflt || "").split(" ");
l = ba.length;
}
pt.plugin = plugin;
pt.setRatio = setRatio;
_colorExp.lastIndex = 0;
for (i = 0; i < l; i++) {
bv = ba[i];
ev = ea[i];
bn = parseFloat(bv);
//if the value begins with a number (most common). It's fine if it has a suffix like px
if (bn || bn === 0) {
pt.appendXtra("", bn, _parseChange(ev, bn), ev.replace(_relNumExp, ""), (autoRound && ev.indexOf("px") !== -1), true);
//if the value is a color
} else if (clrs && _colorExp.test(bv)) {
str = ev.indexOf(")") + 1;
str = ")" + (str ? ev.substr(str) : ""); //if there's a comma or ) at the end, retain it.
useHSL = (ev.indexOf("hsl") !== -1 && _supportsOpacity);
bv = _parseColor(bv, useHSL);
ev = _parseColor(ev, useHSL);
hasAlpha = (bv.length + ev.length > 6);
if (hasAlpha && !_supportsOpacity && ev[3] === 0) { //older versions of IE don't support rgba(), so if the destination alpha is 0, just use "transparent" for the end color
pt["xs" + pt.l] += pt.l ? " transparent" : "transparent";
pt.e = pt.e.split(ea[i]).join("transparent");
} else {
if (!_supportsOpacity) { //old versions of IE don't support rgba().
hasAlpha = false;
}
if (useHSL) {
pt.appendXtra((hasAlpha ? "hsla(" : "hsl("), bv[0], _parseChange(ev[0], bv[0]), ",", false, true)
.appendXtra("", bv[1], _parseChange(ev[1], bv[1]), "%,", false)
.appendXtra("", bv[2], _parseChange(ev[2], bv[2]), (hasAlpha ? "%," : "%" + str), false);
} else {
pt.appendXtra((hasAlpha ? "rgba(" : "rgb("), bv[0], ev[0] - bv[0], ",", true, true)
.appendXtra("", bv[1], ev[1] - bv[1], ",", true)
.appendXtra("", bv[2], ev[2] - bv[2], (hasAlpha ? "," : str), true);
}
if (hasAlpha) {
bv = (bv.length < 4) ? 1 : bv[3];
pt.appendXtra("", bv, ((ev.length < 4) ? 1 : ev[3]) - bv, str, false);
}
}
_colorExp.lastIndex = 0; //otherwise the test() on the RegExp could move the lastIndex and taint future results.
} else {
bnums = bv.match(_numExp); //gets each group of numbers in the beginning value string and drops them into an array
//if no number is found, treat it as a non-tweening value and just append the string to the current xs.
if (!bnums) {
pt["xs" + pt.l] += (pt.l || pt["xs" + pt.l]) ? " " + ev : ev;
//loop through all the numbers that are found and construct the extra values on the pt.
} else {
enums = ev.match(_relNumExp); //get each group of numbers in the end value string and drop them into an array. We allow relative values too, like +=50 or -=.5
if (!enums || enums.length !== bnums.length) {
//DEBUG: _log("mismatched formatting detected on " + p + " (" + b + " vs " + e + ")");
return pt;
}
ni = 0;
for (xi = 0; xi < bnums.length; xi++) {
cv = bnums[xi];
temp = bv.indexOf(cv, ni);
pt.appendXtra(bv.substr(ni, temp - ni), Number(cv), _parseChange(enums[xi], cv), "", (autoRound && bv.substr(temp + cv.length, 2) === "px"), (xi === 0));
ni = temp + cv.length;
}
pt["xs" + pt.l] += bv.substr(ni);
}
}
}
//if there are relative values ("+=" or "-=" prefix), we need to adjust the ending value to eliminate the prefixes and combine the values properly.
if (e.indexOf("=") !== -1) if (pt.data) {
str = pt.xs0 + pt.data.s;
for (i = 1; i < pt.l; i++) {
str += pt["xs" + i] + pt.data["xn" + i];
}
pt.e = str + pt["xs" + i];
}
if (!pt.l) {
pt.type = -1;
pt.xs0 = pt.e;
}
return pt.xfirst || pt;
},
i = 9;
p = CSSPropTween.prototype;
p.l = p.pr = 0; //length (number of extra properties like xn1, xn2, xn3, etc.
while (--i > 0) {
p["xn" + i] = 0;
p["xs" + i] = "";
}
p.xs0 = "";
p._next = p._prev = p.xfirst = p.data = p.plugin = p.setRatio = p.rxp = null;
/**
* Appends and extra tweening value to a CSSPropTween and automatically manages any prefix and suffix strings. The first extra value is stored in the s and c of the main CSSPropTween instance, but thereafter any extras are stored in the xn1, xn2, xn3, etc. The prefixes and suffixes are stored in the xs0, xs1, xs2, etc. properties. For example, if I walk through a clip value like "rect(10px, 5px, 0px, 20px)", the values would be stored like this:
* xs0:"rect(", s:10, xs1:"px, ", xn1:5, xs2:"px, ", xn2:0, xs3:"px, ", xn3:20, xn4:"px)"
* And they'd all get joined together when the CSSPlugin renders (in the setRatio() method).
* @param {string=} pfx Prefix (if any)
* @param {!number} s Starting value
* @param {!number} c Change in numeric value over the course of the entire tween. For example, if the start is 5 and the end is 100, the change would be 95.
* @param {string=} sfx Suffix (if any)
* @param {boolean=} r Round (if true).
* @param {boolean=} pad If true, this extra value should be separated by the previous one by a space. If there is no previous extra and pad is true, it will automatically drop the space.
* @return {CSSPropTween} returns itself so that multiple methods can be chained together.
*/
p.appendXtra = function (pfx, s, c, sfx, r, pad) {
var pt = this,
l = pt.l;
pt["xs" + l] += (pad && (l || pt["xs" + l])) ? " " + pfx : pfx || "";
if (!c) if (l !== 0 && !pt.plugin) { //typically we'll combine non-changing values right into the xs to optimize performance, but we don't combine them when there's a plugin that will be tweening the values because it may depend on the values being split apart, like for a bezier, if a value doesn't change between the first and second iteration but then it does on the 3rd, we'll run into trouble because there's no xn slot for that value!
pt["xs" + l] += s + (sfx || "");
return pt;
}
pt.l++;
pt.type = pt.setRatio ? 2 : 1;
pt["xs" + pt.l] = sfx || "";
if (l > 0) {
pt.data["xn" + l] = s + c;
pt.rxp["xn" + l] = r; //round extra property (we need to tap into this in the _parseToProxy() method)
pt["xn" + l] = s;
if (!pt.plugin) {
pt.xfirst = new CSSPropTween(pt, "xn" + l, s, c, pt.xfirst || pt, 0, pt.n, r, pt.pr);
pt.xfirst.xs0 = 0; //just to ensure that the property stays numeric which helps modern browsers speed up processing. Remember, in the setRatio() method, we do pt.t[pt.p] = val + pt.xs0 so if pt.xs0 is "" (the default), it'll cast the end value as a string. When a property is a number sometimes and a string sometimes, it prevents the compiler from locking in the data type, slowing things down slightly.
}
return pt;
}
pt.data = { s: s + c };
pt.rxp = {};
pt.s = s;
pt.c = c;
pt.r = r;
return pt;
};
/**
* @constructor A SpecialProp is basically a css property that needs to be treated in a non-standard way, like if it may contain a complex value like boxShadow:"5px 10px 15px rgb(255, 102, 51)" or if it is associated with another plugin like ThrowPropsPlugin or BezierPlugin. Every SpecialProp is associated with a particular property name like "boxShadow" or "throwProps" or "bezier" and it will intercept those values in the vars object that's passed to the CSSPlugin and handle them accordingly.
* @param {!string} p Property name (like "boxShadow" or "throwProps")
* @param {Object=} options An object containing any of the following configuration options:
* - defaultValue: the default value
* - parser: A function that should be called when the associated property name is found in the vars. This function should return a CSSPropTween instance and it should ensure that it is properly inserted into the linked list. It will receive 4 paramters: 1) The target, 2) The value defined in the vars, 3) The CSSPlugin instance (whose _firstPT should be used for the linked list), and 4) A computed style object if one was calculated (this is a speed optimization that allows retrieval of starting values quicker)
* - formatter: a function that formats any value received for this special property (for example, boxShadow could take "5px 5px red" and format it to "5px 5px 0px 0px red" so that both the beginning and ending values have a common order and quantity of values.)
* - prefix: if true, we'll determine whether or not this property requires a vendor prefix (like Webkit or Moz or ms or O)
* - color: set this to true if the value for this SpecialProp may contain color-related values like rgb(), rgba(), etc.
* - priority: priority in the linked list order. Higher priority SpecialProps will be updated before lower priority ones. The default priority is 0.
* - multi: if true, the formatter should accommodate a comma-delimited list of values, like boxShadow could have multiple boxShadows listed out.
* - collapsible: if true, the formatter should treat the value like it's a top/right/bottom/left value that could be collapsed, like "5px" would apply to all, "5px, 10px" would use 5px for top/bottom and 10px for right/left, etc.
* - keyword: a special keyword that can [optionally] be found inside the value (like "inset" for boxShadow). This allows us to validate beginning/ending values to make sure they match (if the keyword is found in one, it'll be added to the other for consistency by default).
*/
var SpecialProp = function (p, options) {
options = options || {};
this.p = options.prefix ? _checkPropPrefix(p) || p : p;
_specialProps[p] = _specialProps[this.p] = this;
this.format = options.formatter || _getFormatter(options.defaultValue, options.color, options.collapsible, options.multi);
if (options.parser) {
this.parse = options.parser;
}
this.clrs = options.color;
this.multi = options.multi;
this.keyword = options.keyword;
this.dflt = options.defaultValue;
this.pr = options.priority || 0;
},
//shortcut for creating a new SpecialProp that can accept multiple properties as a comma-delimited list (helps minification). dflt can be an array for multiple values (we don't do a comma-delimited list because the default value may contain commas, like rect(0px,0px,0px,0px)). We attach this method to the SpecialProp class/object instead of using a private _createSpecialProp() method so that we can tap into it externally if necessary, like from another plugin.
_registerComplexSpecialProp = _internals._registerComplexSpecialProp = function (p, options, defaults) {
if (typeof (options) !== "object") {
options = { parser: defaults }; //to make backwards compatible with older versions of BezierPlugin and ThrowPropsPlugin
}
var a = p.split(","),
d = options.defaultValue,
i, temp;
defaults = defaults || [d];
for (i = 0; i < a.length; i++) {
options.prefix = (i === 0 && options.prefix);
options.defaultValue = defaults[i] || d;
temp = new SpecialProp(a[i], options);
}
},
//creates a placeholder special prop for a plugin so that the property gets caught the first time a tween of it is attempted, and at that time it makes the plugin register itself, thus taking over for all future tweens of that property. This allows us to not mandate that things load in a particular order and it also allows us to log() an error that informs the user when they attempt to tween an external plugin-related property without loading its .js file.
_registerPluginProp = function (p) {
if (!_specialProps[p]) {
var pluginName = p.charAt(0).toUpperCase() + p.substr(1) + "Plugin";
_registerComplexSpecialProp(p, {
parser: function (t, e, p, cssp, pt, plugin, vars) {
var pluginClass = _globals.com.greensock.plugins[pluginName];
if (!pluginClass) {
_log("Error: " + pluginName + " js file not loaded.");
return pt;
}
pluginClass._cssRegister();
return _specialProps[p].parse(t, e, p, cssp, pt, plugin, vars);
}
});
}
};
p = SpecialProp.prototype;
/**
* Alias for _parseComplex() that automatically plugs in certain values for this SpecialProp, like its property name, whether or not colors should be sensed, the default value, and priority. It also looks for any keyword that the SpecialProp defines (like "inset" for boxShadow) and ensures that the beginning and ending values have the same number of values for SpecialProps where multi is true (like boxShadow and textShadow can have a comma-delimited list)
* @param {!Object} t target element
* @param {(string|number|object)} b beginning value
* @param {(string|number|object)} e ending (destination) value
* @param {CSSPropTween=} pt next CSSPropTween in the linked list
* @param {TweenPlugin=} plugin If another plugin will be tweening the complex value, that TweenPlugin instance goes here.
* @param {function=} setRatio If a custom setRatio() method should be used to handle this complex value, that goes here.
* @return {CSSPropTween=} First CSSPropTween in the linked list
*/
p.parseComplex = function (t, b, e, pt, plugin, setRatio) {
var kwd = this.keyword,
i, ba, ea, l, bi, ei;
//if this SpecialProp's value can contain a comma-delimited list of values (like boxShadow or textShadow), we must parse them in a special way, and look for a keyword (like "inset" for boxShadow) and ensure that the beginning and ending BOTH have it if the end defines it as such. We also must ensure that there are an equal number of values specified (we can't tween 1 boxShadow to 3 for example)
if (this.multi) if (_commasOutsideParenExp.test(e) || _commasOutsideParenExp.test(b)) {
ba = b.replace(_commasOutsideParenExp, "|").split("|");
ea = e.replace(_commasOutsideParenExp, "|").split("|");
} else if (kwd) {
ba = [b];
ea = [e];
}
if (ea) {
l = (ea.length > ba.length) ? ea.length : ba.length;
for (i = 0; i < l; i++) {
b = ba[i] = ba[i] || this.dflt;
e = ea[i] = ea[i] || this.dflt;
if (kwd) {
bi = b.indexOf(kwd);
ei = e.indexOf(kwd);
if (bi !== ei) {
if (ei === -1) { //if the keyword isn't in the end value, remove it from the beginning one.
ba[i] = ba[i].split(kwd).join("");
} else if (bi === -1) { //if the keyword isn't in the beginning, add it.
ba[i] += " " + kwd;
}
}
}
}
b = ba.join(", ");
e = ea.join(", ");
}
return _parseComplex(t, this.p, b, e, this.clrs, this.dflt, pt, this.pr, plugin, setRatio);
};
/**
* Accepts a target and end value and spits back a CSSPropTween that has been inserted into the CSSPlugin's linked list and conforms with all the conventions we use internally, like type:-1, 0, 1, or 2, setting up any extra property tweens, priority, etc. For example, if we have a boxShadow SpecialProp and call:
* this._firstPT = sp.parse(element, "5px 10px 20px rgb(2550,102,51)", "boxShadow", this);
* It should figure out the starting value of the element's boxShadow, compare it to the provided end value and create all the necessary CSSPropTweens of the appropriate types to tween the boxShadow. The CSSPropTween that gets spit back should already be inserted into the linked list (the 4th parameter is the current head, so prepend to that).
* @param {!Object} t Target object whose property is being tweened
* @param {Object} e End value as provided in the vars object (typically a string, but not always - like a throwProps would be an object).
* @param {!string} p Property name
* @param {!CSSPlugin} cssp The CSSPlugin instance that should be associated with this tween.
* @param {?CSSPropTween} pt The CSSPropTween that is the current head of the linked list (we'll prepend to it)
* @param {TweenPlugin=} plugin If a plugin will be used to tween the parsed value, this is the plugin instance.
* @param {Object=} vars Original vars object that contains the data for parsing.
* @return {CSSPropTween} The first CSSPropTween in the linked list which includes the new one(s) added by the parse() call.
*/
p.parse = function (t, e, p, cssp, pt, plugin, vars) {
return this.parseComplex(t.style, this.format(_getStyle(t, this.p, _cs, false, this.dflt)), this.format(e), pt, plugin);
};
/**
* Registers a special property that should be intercepted from any "css" objects defined in tweens. This allows you to handle them however you want without CSSPlugin doing it for you. The 2nd parameter should be a function that accepts 3 parameters:
* 1) Target object whose property should be tweened (typically a DOM element)
* 2) The end/destination value (could be a string, number, object, or whatever you want)
* 3) The tween instance (you probably don't need to worry about this, but it can be useful for looking up information like the duration)
*
* Then, your function should return a function which will be called each time the tween gets rendered, passing a numeric "ratio" parameter to your function that indicates the change factor (usually between 0 and 1). For example:
*
* CSSPlugin.registerSpecialProp("myCustomProp", function(target, value, tween) {
* var start = target.style.width;
* return function(ratio) {
* target.style.width = (start + value * ratio) + "px";
* console.log("set width to " + target.style.width);
* }
* }, 0);
*
* Then, when I do this tween, it will trigger my special property:
*
* TweenLite.to(element, 1, {css:{myCustomProp:100}});
*
* In the example, of course, we're just changing the width, but you can do anything you want.
*
* @param {!string} name Property name (or comma-delimited list of property names) that should be intercepted and handled by your function. For example, if I define "myCustomProp", then it would handle that portion of the following tween: TweenLite.to(element, 1, {css:{myCustomProp:100}})
* @param {!function(Object, Object, Object, string):function(number)} onInitTween The function that will be called when a tween of this special property is performed. The function will receive 4 parameters: 1) Target object that should be tweened, 2) Value that was passed to the tween, 3) The tween instance itself (rarely used), and 4) The property name that's being tweened. Your function should return a function that should be called on every update of the tween. That function will receive a single parameter that is a "change factor" value (typically between 0 and 1) indicating the amount of change as a ratio. You can use this to determine how to set the values appropriately in your function.
* @param {number=} priority Priority that helps the engine determine the order in which to set the properties (default: 0). Higher priority properties will be updated before lower priority ones.
*/
CSSPlugin.registerSpecialProp = function (name, onInitTween, priority) {
_registerComplexSpecialProp(name, {
parser: function (t, e, p, cssp, pt, plugin, vars) {
var rv = new CSSPropTween(t, p, 0, 0, pt, 2, p, false, priority);
rv.plugin = plugin;
rv.setRatio = onInitTween(t, e, cssp._tween, p);
return rv;
}, priority: priority
});
};
//transform-related methods and properties
CSSPlugin.useSVGTransformAttr = _isSafari || _isFirefox; //Safari and Firefox both have some rendering bugs when applying CSS transforms to SVG elements, so default to using the "transform" attribute instead (users can override this).
var _transformProps = ("scaleX,scaleY,scaleZ,x,y,z,skewX,skewY,rotation,rotationX,rotationY,perspective,xPercent,yPercent").split(","),
_transformProp = _checkPropPrefix("transform"), //the Javascript (camelCase) transform property, like msTransform, WebkitTransform, MozTransform, or OTransform.
_transformPropCSS = _prefixCSS + "transform",
_transformOriginProp = _checkPropPrefix("transformOrigin"),
_supports3D = (_checkPropPrefix("perspective") !== null),
Transform = _internals.Transform = function () {
this.perspective = parseFloat(CSSPlugin.defaultTransformPerspective) || 0;
this.force3D = (CSSPlugin.defaultForce3D === false || !_supports3D) ? false : CSSPlugin.defaultForce3D || "auto";
},
_SVGElement = window.SVGElement,
_useSVGTransformAttr,
//Some browsers (like Firefox and IE) don't honor transform-origin properly in SVG elements, so we need to manually adjust the matrix accordingly. We feature detect here rather than always doing the conversion for certain browsers because they may fix the problem at some point in the future.
_createSVG = function (type, container, attributes) {
var element = _doc.createElementNS("http://www.w3.org/2000/svg", type),
reg = /([a-z])([A-Z])/g,
p;
for (p in attributes) {
element.setAttributeNS(null, p.replace(reg, "$1-$2").toLowerCase(), attributes[p]);
}
container.appendChild(element);
return element;
},
_docElement = _doc.documentElement,
_forceSVGTransformAttr = (function () {
//IE and Android stock don't support CSS transforms on SVG elements, so we must write them to the "transform" attribute. We populate this variable in the _parseTransform() method, and only if/when we come across an SVG element
var force = _ieVers || (/Android/i.test(_agent) && !window.chrome),
svg, rect, width;
if (_doc.createElementNS && !force) { //IE8 and earlier doesn't support SVG anyway
svg = _createSVG("svg", _docElement);
rect = _createSVG("rect", svg, { width: 100, height: 50, x: 100 });
width = rect.getBoundingClientRect().width;
rect.style[_transformOriginProp] = "50% 50%";
rect.style[_transformProp] = "scaleX(0.5)";
force = (width === rect.getBoundingClientRect().width && !(_isFirefox && _supports3D)); //note: Firefox fails the test even though it does support CSS transforms in 3D. Since we can't push 3D stuff into the transform attribute, we force Firefox to pass the test here (as long as it does truly support 3D).
_docElement.removeChild(svg);
}
return force;
})(),
_parseSVGOrigin = function (e, local, decoratee, absolute, smoothOrigin, skipRecord) {
var tm = e._gsTransform,
m = _getMatrix(e, true),
v, x, y, xOrigin, yOrigin, a, b, c, d, tx, ty, determinant, xOriginOld, yOriginOld;
if (tm) {
xOriginOld = tm.xOrigin; //record the original values before we alter them.
yOriginOld = tm.yOrigin;
}
if (!absolute || (v = absolute.split(" ")).length < 2) {
b = e.getBBox();
local = _parsePosition(local).split(" ");
v = [(local[0].indexOf("%") !== -1 ? parseFloat(local[0]) / 100 * b.width : parseFloat(local[0])) + b.x,
(local[1].indexOf("%") !== -1 ? parseFloat(local[1]) / 100 * b.height : parseFloat(local[1])) + b.y];
}
decoratee.xOrigin = xOrigin = parseFloat(v[0]);
decoratee.yOrigin = yOrigin = parseFloat(v[1]);
if (absolute && m !== _identity2DMatrix) { //if svgOrigin is being set, we must invert the matrix and determine where the absolute point is, factoring in the current transforms. Otherwise, the svgOrigin would be based on the element's non-transformed position on the canvas.
a = m[0];
b = m[1];
c = m[2];
d = m[3];
tx = m[4];
ty = m[5];
determinant = (a * d - b * c);
x = xOrigin * (d / determinant) + yOrigin * (-c / determinant) + ((c * ty - d * tx) / determinant);
y = xOrigin * (-b / determinant) + yOrigin * (a / determinant) - ((a * ty - b * tx) / determinant);
xOrigin = decoratee.xOrigin = v[0] = x;
yOrigin = decoratee.yOrigin = v[1] = y;
}
if (tm) { //avoid jump when transformOrigin is changed - adjust the x/y values accordingly
if (skipRecord) {
decoratee.xOffset = tm.xOffset;
decoratee.yOffset = tm.yOffset;
tm = decoratee;
}
if (smoothOrigin || (smoothOrigin !== false && CSSPlugin.defaultSmoothOrigin !== false)) {
x = xOrigin - xOriginOld;
y = yOrigin - yOriginOld;
//originally, we simply adjusted the x and y values, but that would cause problems if, for example, you created a rotational tween part-way through an x/y tween. Managing the offset in a separate variable gives us ultimate flexibility.
//tm.x -= x - (x * m[0] + y * m[2]);
//tm.y -= y - (x * m[1] + y * m[3]);
tm.xOffset += (x * m[0] + y * m[2]) - x;
tm.yOffset += (x * m[1] + y * m[3]) - y;
} else {
tm.xOffset = tm.yOffset = 0;
}
}
if (!skipRecord) {
e.setAttribute("data-svg-origin", v.join(" "));
}
},
_canGetBBox = function (e) {
try {
return e.getBBox(); //Firefox throws errors if you try calling getBBox() on an SVG element that's not rendered (like in a <symbol> or <defs>). https://bugzilla.mozilla.org/show_bug.cgi?id=612118
} catch (e) { }
},
_isSVG = function (e) { //reports if the element is an SVG on which getBBox() actually works
return !!(_SVGElement && e.getBBox && e.getCTM && _canGetBBox(e) && (!e.parentNode || (e.parentNode.getBBox && e.parentNode.getCTM)));
},
_identity2DMatrix = [1, 0, 0, 1, 0, 0],
_getMatrix = function (e, force2D) {
var tm = e._gsTransform || new Transform(),
rnd = 100000,
isDefault, s, m, n, dec;
if (_transformProp) {
s = _getStyle(e, _transformPropCSS, null, true);
} else if (e.currentStyle) {
//for older versions of IE, we need to interpret the filter portion that is in the format: progid:DXImageTransform.Microsoft.Matrix(M11=6.123233995736766e-17, M12=-1, M21=1, M22=6.123233995736766e-17, sizingMethod='auto expand') Notice that we need to swap b and c compared to a normal matrix.
s = e.currentStyle.filter.match(_ieGetMatrixExp);
s = (s && s.length === 4) ? [s[0].substr(4), Number(s[2].substr(4)), Number(s[1].substr(4)), s[3].substr(4), (tm.x || 0), (tm.y || 0)].join(",") : "";
}
isDefault = (!s || s === "none" || s === "matrix(1, 0, 0, 1, 0, 0)");
if (tm.svg || (e.getBBox && _isSVG(e))) {
if (isDefault && (e.style[_transformProp] + "").indexOf("matrix") !== -1) { //some browsers (like Chrome 40) don't correctly report transforms that are applied inline on an SVG element (they don't get included in the computed style), so we double-check here and accept matrix values
s = e.style[_transformProp];
isDefault = 0;
}
m = e.getAttribute("transform");
if (isDefault && m) {
if (m.indexOf("matrix") !== -1) { //just in case there's a "transform" value specified as an attribute instead of CSS style. Accept either a matrix() or simple translate() value though.
s = m;
isDefault = 0;
} else if (m.indexOf("translate") !== -1) {
s = "matrix(1,0,0,1," + m.match(/(?:\-|\b)[\d\-\.e]+\b/gi).join(",") + ")";
isDefault = 0;
}
}
}
if (isDefault) {
return _identity2DMatrix;
}
//split the matrix values out into an array (m for matrix)
m = (s || "").match(_numExp) || [];
i = m.length;
while (--i > -1) {
n = Number(m[i]);
m[i] = (dec = n - (n |= 0)) ? ((dec * rnd + (dec < 0 ? -0.5 : 0.5)) | 0) / rnd + n : n; //convert strings to Numbers and round to 5 decimal places to avoid issues with tiny numbers. Roughly 20x faster than Number.toFixed(). We also must make sure to round before dividing so that values like 0.9999999999 become 1 to avoid glitches in browser rendering and interpretation of flipped/rotated 3D matrices. And don't just multiply the number by rnd, floor it, and then divide by rnd because the bitwise operations max out at a 32-bit signed integer, thus it could get clipped at a relatively low value (like 22,000.00000 for example).
}
return (force2D && m.length > 6) ? [m[0], m[1], m[4], m[5], m[12], m[13]] : m;
},
/**
* Parses the transform values for an element, returning an object with x, y, z, scaleX, scaleY, scaleZ, rotation, rotationX, rotationY, skewX, and skewY properties. Note: by default (for performance reasons), all skewing is combined into skewX and rotation but skewY still has a place in the transform object so that we can record how much of the skew is attributed to skewX vs skewY. Remember, a skewY of 10 looks the same as a rotation of 10 and skewX of -10.
* @param {!Object} t target element
* @param {Object=} cs computed style object (optional)
* @param {boolean=} rec if true, the transform values will be recorded to the target element's _gsTransform object, like target._gsTransform = {x:0, y:0, z:0, scaleX:1...}
* @param {boolean=} parse if true, we'll ignore any _gsTransform values that already exist on the element, and force a reparsing of the css (calculated style)
* @return {object} object containing all of the transform properties/values like {x:0, y:0, z:0, scaleX:1...}
*/
_getTransform = _internals.getTransform = function (t, cs, rec, parse) {
if (t._gsTransform && rec && !parse) {
return t._gsTransform; //if the element already has a _gsTransform, use that. Note: some browsers don't accurately return the calculated style for the transform (particularly for SVG), so it's almost always safest to just use the values we've already applied rather than re-parsing things.
}
var tm = rec ? t._gsTransform || new Transform() : new Transform(),
invX = (tm.scaleX < 0), //in order to interpret things properly, we need to know if the user applied a negative scaleX previously so that we can adjust the rotation and skewX accordingly. Otherwise, if we always interpret a flipped matrix as affecting scaleY and the user only wants to tween the scaleX on multiple sequential tweens, it would keep the negative scaleY without that being the user's intent.
min = 0.00002,
rnd = 100000,
zOrigin = _supports3D ? parseFloat(_getStyle(t, _transformOriginProp, cs, false, "0 0 0").split(" ")[2]) || tm.zOrigin || 0 : 0,
defaultTransformPerspective = parseFloat(CSSPlugin.defaultTransformPerspective) || 0,
m, i, scaleX, scaleY, rotation, skewX;
tm.svg = !!(t.getBBox && _isSVG(t));
if (tm.svg) {
_parseSVGOrigin(t, _getStyle(t, _transformOriginProp, _cs, false, "50% 50%") + "", tm, t.getAttribute("data-svg-origin"));
_useSVGTransformAttr = CSSPlugin.useSVGTransformAttr || _forceSVGTransformAttr;
}
m = _getMatrix(t);
if (m !== _identity2DMatrix) {
if (m.length === 16) {
//we'll only look at these position-related 6 variables first because if x/y/z all match, it's relatively safe to assume we don't need to re-parse everything which risks losing important rotational information (like rotationX:180 plus rotationY:180 would look the same as rotation:180 - there's no way to know for sure which direction was taken based solely on the matrix3d() values)
var a11 = m[0], a21 = m[1], a31 = m[2], a41 = m[3],
a12 = m[4], a22 = m[5], a32 = m[6], a42 = m[7],
a13 = m[8], a23 = m[9], a33 = m[10],
a14 = m[12], a24 = m[13], a34 = m[14],
a43 = m[11],
angle = Math.atan2(a32, a33),
t1, t2, t3, t4, cos, sin;
//we manually compensate for non-zero z component of transformOrigin to work around bugs in Safari
if (tm.zOrigin) {
a34 = -tm.zOrigin;
a14 = a13 * a34 - m[12];
a24 = a23 * a34 - m[13];
a34 = a33 * a34 + tm.zOrigin - m[14];
}
tm.rotationX = angle * _RAD2DEG;
//rotationX
if (angle) {
cos = Math.cos(-angle);
sin = Math.sin(-angle);
t1 = a12 * cos + a13 * sin;
t2 = a22 * cos + a23 * sin;
t3 = a32 * cos + a33 * sin;
a13 = a12 * -sin + a13 * cos;
a23 = a22 * -sin + a23 * cos;
a33 = a32 * -sin + a33 * cos;
a43 = a42 * -sin + a43 * cos;
a12 = t1;
a22 = t2;
a32 = t3;
}
//rotationY
angle = Math.atan2(-a31, a33);
tm.rotationY = angle * _RAD2DEG;
if (angle) {
cos = Math.cos(-angle);
sin = Math.sin(-angle);
t1 = a11 * cos - a13 * sin;
t2 = a21 * cos - a23 * sin;
t3 = a31 * cos - a33 * sin;
a23 = a21 * sin + a23 * cos;
a33 = a31 * sin + a33 * cos;
a43 = a41 * sin + a43 * cos;
a11 = t1;
a21 = t2;
a31 = t3;
}
//rotationZ
angle = Math.atan2(a21, a11);
tm.rotation = angle * _RAD2DEG;
if (angle) {
cos = Math.cos(-angle);
sin = Math.sin(-angle);
a11 = a11 * cos + a12 * sin;
t2 = a21 * cos + a22 * sin;
a22 = a21 * -sin + a22 * cos;
a32 = a31 * -sin + a32 * cos;
a21 = t2;
}
if (tm.rotationX && Math.abs(tm.rotationX) + Math.abs(tm.rotation) > 359.9) { //when rotationY is set, it will often be parsed as 180 degrees different than it should be, and rotationX and rotation both being 180 (it looks the same), so we adjust for that here.
tm.rotationX = tm.rotation = 0;
tm.rotationY = 180 - tm.rotationY;
}
tm.scaleX = ((Math.sqrt(a11 * a11 + a21 * a21) * rnd + 0.5) | 0) / rnd;
tm.scaleY = ((Math.sqrt(a22 * a22 + a23 * a23) * rnd + 0.5) | 0) / rnd;
tm.scaleZ = ((Math.sqrt(a32 * a32 + a33 * a33) * rnd + 0.5) | 0) / rnd;
tm.skewX = (a12 || a22) ? Math.atan2(a12, a22) * _RAD2DEG + tm.rotation : tm.skewX || 0;
if (Math.abs(tm.skewX) > 90 && Math.abs(tm.skewX) < 270) {
if (invX) {
tm.scaleX *= -1;
tm.skewX += (tm.rotation <= 0) ? 180 : -180;
tm.rotation += (tm.rotation <= 0) ? 180 : -180;
} else {
tm.scaleY *= -1;
tm.skewX += (tm.skewX <= 0) ? 180 : -180;
}
}
tm.perspective = a43 ? 1 / ((a43 < 0) ? -a43 : a43) : 0;
tm.x = a14;
tm.y = a24;
tm.z = a34;
if (tm.svg) {
tm.x -= tm.xOrigin - (tm.xOrigin * a11 - tm.yOrigin * a12);
tm.y -= tm.yOrigin - (tm.yOrigin * a21 - tm.xOrigin * a22);
}
} else if ((!_supports3D || parse || !m.length || tm.x !== m[4] || tm.y !== m[5] || (!tm.rotationX && !tm.rotationY)) && !(tm.x !== undefined && _getStyle(t, "display", cs) === "none")) { //sometimes a 6-element matrix is returned even when we performed 3D transforms, like if rotationX and rotationY are 180. In cases like this, we still need to honor the 3D transforms. If we just rely on the 2D info, it could affect how the data is interpreted, like scaleY might get set to -1 or rotation could get offset by 180 degrees. For example, do a TweenLite.to(element, 1, {css:{rotationX:180, rotationY:180}}) and then later, TweenLite.to(element, 1, {css:{rotationX:0}}) and without this conditional logic in place, it'd jump to a state of being unrotated when the 2nd tween starts. Then again, we need to honor the fact that the user COULD alter the transforms outside of CSSPlugin, like by manually applying new css, so we try to sense that by looking at x and y because if those changed, we know the changes were made outside CSSPlugin and we force a reinterpretation of the matrix values. Also, in Webkit browsers, if the element's "display" is "none", its calculated style value will always return empty, so if we've already recorded the values in the _gsTransform object, we'll just rely on those.
var k = (m.length >= 6),
a = k ? m[0] : 1,
b = m[1] || 0,
c = m[2] || 0,
d = k ? m[3] : 1;
tm.x = m[4] || 0;
tm.y = m[5] || 0;
scaleX = Math.sqrt(a * a + b * b);
scaleY = Math.sqrt(d * d + c * c);
rotation = (a || b) ? Math.atan2(b, a) * _RAD2DEG : tm.rotation || 0; //note: if scaleX is 0, we cannot accurately measure rotation. Same for skewX with a scaleY of 0. Therefore, we default to the previously recorded value (or zero if that doesn't exist).
skewX = (c || d) ? Math.atan2(c, d) * _RAD2DEG + rotation : tm.skewX || 0;
if (Math.abs(skewX) > 90 && Math.abs(skewX) < 270) {
if (invX) {
scaleX *= -1;
skewX += (rotation <= 0) ? 180 : -180;
rotation += (rotation <= 0) ? 180 : -180;
} else {
scaleY *= -1;
skewX += (skewX <= 0) ? 180 : -180;
}
}
tm.scaleX = scaleX;
tm.scaleY = scaleY;
tm.rotation = rotation;
tm.skewX = skewX;
if (_supports3D) {
tm.rotationX = tm.rotationY = tm.z = 0;
tm.perspective = defaultTransformPerspective;
tm.scaleZ = 1;
}
if (tm.svg) {
tm.x -= tm.xOrigin - (tm.xOrigin * a + tm.yOrigin * c);
tm.y -= tm.yOrigin - (tm.xOrigin * b + tm.yOrigin * d);
}
}
tm.zOrigin = zOrigin;
//some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 0 in these cases. The conditional logic here is faster than calling Math.abs(). Also, browsers tend to render a SLIGHTLY rotated object in a fuzzy way, so we need to snap to exactly 0 when appropriate.
for (i in tm) {
if (tm[i] < min) if (tm[i] > -min) {
tm[i] = 0;
}
}
}
//DEBUG: _log("parsed rotation of " + t.getAttribute("id")+": "+(tm.rotationX)+", "+(tm.rotationY)+", "+(tm.rotation)+", scale: "+tm.scaleX+", "+tm.scaleY+", "+tm.scaleZ+", position: "+tm.x+", "+tm.y+", "+tm.z+", perspective: "+tm.perspective+ ", origin: "+ tm.xOrigin+ ","+ tm.yOrigin);
if (rec) {
t._gsTransform = tm; //record to the object's _gsTransform which we use so that tweens can control individual properties independently (we need all the properties to accurately recompose the matrix in the setRatio() method)
if (tm.svg) { //if we're supposed to apply transforms to the SVG element's "transform" attribute, make sure there aren't any CSS transforms applied or they'll override the attribute ones. Also clear the transform attribute if we're using CSS, just to be clean.
if (_useSVGTransformAttr && t.style[_transformProp]) {
TweenLite.delayedCall(0.001, function () { //if we apply this right away (before anything has rendered), we risk there being no transforms for a brief moment and it also interferes with adjusting the transformOrigin in a tween with immediateRender:true (it'd try reading the matrix and it wouldn't have the appropriate data in place because we just removed it).
_removeProp(t.style, _transformProp);
});
} else if (!_useSVGTransformAttr && t.getAttribute("transform")) {
TweenLite.delayedCall(0.001, function () {
t.removeAttribute("transform");
});
}
}
}
return tm;
},
//for setting 2D transforms in IE6, IE7, and IE8 (must use a "filter" to emulate the behavior of modern day browser transforms)
_setIETransformRatio = function (v) {
var t = this.data, //refers to the element's _gsTransform object
ang = -t.rotation * _DEG2RAD,
skew = ang + t.skewX * _DEG2RAD,
rnd = 100000,
a = ((Math.cos(ang) * t.scaleX * rnd) | 0) / rnd,
b = ((Math.sin(ang) * t.scaleX * rnd) | 0) / rnd,
c = ((Math.sin(skew) * -t.scaleY * rnd) | 0) / rnd,
d = ((Math.cos(skew) * t.scaleY * rnd) | 0) / rnd,
style = this.t.style,
cs = this.t.currentStyle,
filters, val;
if (!cs) {
return;
}
val = b; //just for swapping the variables an inverting them (reused "val" to avoid creating another variable in memory). IE's filter matrix uses a non-standard matrix configuration (angle goes the opposite way, and b and c are reversed and inverted)
b = -c;
c = -val;
filters = cs.filter;
style.filter = ""; //remove filters so that we can accurately measure offsetWidth/offsetHeight
var w = this.t.offsetWidth,
h = this.t.offsetHeight,
clip = (cs.position !== "absolute"),
m = "progid:DXImageTransform.Microsoft.Matrix(M11=" + a + ", M12=" + b + ", M21=" + c + ", M22=" + d,
ox = t.x + (w * t.xPercent / 100),
oy = t.y + (h * t.yPercent / 100),
dx, dy;
//if transformOrigin is being used, adjust the offset x and y
if (t.ox != null) {
dx = ((t.oxp) ? w * t.ox * 0.01 : t.ox) - w / 2;
dy = ((t.oyp) ? h * t.oy * 0.01 : t.oy) - h / 2;
ox += dx - (dx * a + dy * b);
oy += dy - (dx * c + dy * d);
}
if (!clip) {
m += ", sizingMethod='auto expand')";
} else {
dx = (w / 2);
dy = (h / 2);
//translate to ensure that transformations occur around the correct origin (default is center).
m += ", Dx=" + (dx - (dx * a + dy * b) + ox) + ", Dy=" + (dy - (dx * c + dy * d) + oy) + ")";
}
if (filters.indexOf("DXImageTransform.Microsoft.Matrix(") !== -1) {
style.filter = filters.replace(_ieSetMatrixExp, m);
} else {
style.filter = m + " " + filters; //we must always put the transform/matrix FIRST (before alpha(opacity=xx)) to avoid an IE bug that slices part of the object when rotation is applied with alpha.
}
//at the end or beginning of the tween, if the matrix is normal (1, 0, 0, 1) and opacity is 100 (or doesn't exist), remove the filter to improve browser performance.
if (v === 0 || v === 1) if (a === 1) if (b === 0) if (c === 0) if (d === 1) if (!clip || m.indexOf("Dx=0, Dy=0") !== -1) if (!_opacityExp.test(filters) || parseFloat(RegExp.$1) === 100) if (filters.indexOf("gradient(" && filters.indexOf("Alpha")) === -1) {
style.removeAttribute("filter");
}
//we must set the margins AFTER applying the filter in order to avoid some bugs in IE8 that could (in rare scenarios) cause them to be ignored intermittently (vibration).
if (!clip) {
var mult = (_ieVers < 8) ? 1 : -1, //in Internet Explorer 7 and before, the box model is broken, causing the browser to treat the width/height of the actual rotated filtered image as the width/height of the box itself, but Microsoft corrected that in IE8. We must use a negative offset in IE8 on the right/bottom
marg, prop, dif;
dx = t.ieOffsetX || 0;
dy = t.ieOffsetY || 0;
t.ieOffsetX = Math.round((w - ((a < 0 ? -a : a) * w + (b < 0 ? -b : b) * h)) / 2 + ox);
t.ieOffsetY = Math.round((h - ((d < 0 ? -d : d) * h + (c < 0 ? -c : c) * w)) / 2 + oy);
for (i = 0; i < 4; i++) {
prop = _margins[i];
marg = cs[prop];
//we need to get the current margin in case it is being tweened separately (we want to respect that tween's changes)
val = (marg.indexOf("px") !== -1) ? parseFloat(marg) : _convertToPixels(this.t, prop, parseFloat(marg), marg.replace(_suffixExp, "")) || 0;
if (val !== t[prop]) {
dif = (i < 2) ? -t.ieOffsetX : -t.ieOffsetY; //if another tween is controlling a margin, we cannot only apply the difference in the ieOffsets, so we essentially zero-out the dx and dy here in that case. We record the margin(s) later so that we can keep comparing them, making this code very flexible.
} else {
dif = (i < 2) ? dx - t.ieOffsetX : dy - t.ieOffsetY;
}
style[prop] = (t[prop] = Math.round(val - dif * ((i === 0 || i === 2) ? 1 : mult))) + "px";
}
}
},
/* translates a super small decimal to a string WITHOUT scientific notation
_safeDecimal = function(n) {
var s = (n < 0 ? -n : n) + "",
a = s.split("e-");
return (n < 0 ? "-0." : "0.") + new Array(parseInt(a[1], 10) || 0).join("0") + a[0].split(".").join("");
},
*/
_setTransformRatio = _internals.set3DTransformRatio = _internals.setTransformRatio = function (v) {
var t = this.data, //refers to the element's _gsTransform object
style = this.t.style,
angle = t.rotation,
rotationX = t.rotationX,
rotationY = t.rotationY,
sx = t.scaleX,
sy = t.scaleY,
sz = t.scaleZ,
x = t.x,
y = t.y,
z = t.z,
isSVG = t.svg,
perspective = t.perspective,
force3D = t.force3D,
a11, a12, a13, a21, a22, a23, a31, a32, a33, a41, a42, a43,
zOrigin, min, cos, sin, t1, t2, transform, comma, zero, skew, rnd;
//check to see if we should render as 2D (and SVGs must use 2D when _useSVGTransformAttr is true)
if (((((v === 1 || v === 0) && force3D === "auto" && (this.tween._totalTime === this.tween._totalDuration || !this.tween._totalTime)) || !force3D) && !z && !perspective && !rotationY && !rotationX && sz === 1) || (_useSVGTransformAttr && isSVG) || !_supports3D) { //on the final render (which could be 0 for a from tween), if there are no 3D aspects, render in 2D to free up memory and improve performance especially on mobile devices. Check the tween's totalTime/totalDuration too in order to make sure it doesn't happen between repeats if it's a repeating tween.
//2D
if (angle || t.skewX || isSVG) {
angle *= _DEG2RAD;
skew = t.skewX * _DEG2RAD;
rnd = 100000;
a11 = Math.cos(angle) * sx;
a21 = Math.sin(angle) * sx;
a12 = Math.sin(angle - skew) * -sy;
a22 = Math.cos(angle - skew) * sy;
if (skew && t.skewType === "simple") { //by default, we compensate skewing on the other axis to make it look more natural, but you can set the skewType to "simple" to use the uncompensated skewing that CSS does
t1 = Math.tan(skew);
t1 = Math.sqrt(1 + t1 * t1);
a12 *= t1;
a22 *= t1;
if (t.skewY) {
a11 *= t1;
a21 *= t1;
}
}
if (isSVG) {
x += t.xOrigin - (t.xOrigin * a11 + t.yOrigin * a12) + t.xOffset;
y += t.yOrigin - (t.xOrigin * a21 + t.yOrigin * a22) + t.yOffset;
if (_useSVGTransformAttr && (t.xPercent || t.yPercent)) { //The SVG spec doesn't support percentage-based translation in the "transform" attribute, so we merge it into the matrix to simulate it.
min = this.t.getBBox();
x += t.xPercent * 0.01 * min.width;
y += t.yPercent * 0.01 * min.height;
}
min = 0.000001;
if (x < min) if (x > -min) {
x = 0;
}
if (y < min) if (y > -min) {
y = 0;
}
}
transform = (((a11 * rnd) | 0) / rnd) + "," + (((a21 * rnd) | 0) / rnd) + "," + (((a12 * rnd) | 0) / rnd) + "," + (((a22 * rnd) | 0) / rnd) + "," + x + "," + y + ")";
if (isSVG && _useSVGTransformAttr) {
this.t.setAttribute("transform", "matrix(" + transform);
} else {
//some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 5 decimal places.
style[_transformProp] = ((t.xPercent || t.yPercent) ? "translate(" + t.xPercent + "%," + t.yPercent + "%) matrix(" : "matrix(") + transform;
}
} else {
style[_transformProp] = ((t.xPercent || t.yPercent) ? "translate(" + t.xPercent + "%," + t.yPercent + "%) matrix(" : "matrix(") + sx + ",0,0," + sy + "," + x + "," + y + ")";
}
return;
}
if (_isFirefox) { //Firefox has a bug (at least in v25) that causes it to render the transparent part of 32-bit PNG images as black when displayed inside an iframe and the 3D scale is very small and doesn't change sufficiently enough between renders (like if you use a Power4.easeInOut to scale from 0 to 1 where the beginning values only change a tiny amount to begin the tween before accelerating). In this case, we force the scale to be 0.00002 instead which is visually the same but works around the Firefox issue.
min = 0.0001;
if (sx < min && sx > -min) {
sx = sz = 0.00002;
}
if (sy < min && sy > -min) {
sy = sz = 0.00002;
}
if (perspective && !t.z && !t.rotationX && !t.rotationY) { //Firefox has a bug that causes elements to have an odd super-thin, broken/dotted black border on elements that have a perspective set but aren't utilizing 3D space (no rotationX, rotationY, or z).
perspective = 0;
}
}
if (angle || t.skewX) {
angle *= _DEG2RAD;
cos = a11 = Math.cos(angle);
sin = a21 = Math.sin(angle);
if (t.skewX) {
angle -= t.skewX * _DEG2RAD;
cos = Math.cos(angle);
sin = Math.sin(angle);
if (t.skewType === "simple") { //by default, we compensate skewing on the other axis to make it look more natural, but you can set the skewType to "simple" to use the uncompensated skewing that CSS does
t1 = Math.tan(t.skewX * _DEG2RAD);
t1 = Math.sqrt(1 + t1 * t1);
cos *= t1;
sin *= t1;
if (t.skewY) {
a11 *= t1;
a21 *= t1;
}
}
}
a12 = -sin;
a22 = cos;
} else if (!rotationY && !rotationX && sz === 1 && !perspective && !isSVG) { //if we're only translating and/or 2D scaling, this is faster...
style[_transformProp] = ((t.xPercent || t.yPercent) ? "translate(" + t.xPercent + "%," + t.yPercent + "%) translate3d(" : "translate3d(") + x + "px," + y + "px," + z + "px)" + ((sx !== 1 || sy !== 1) ? " scale(" + sx + "," + sy + ")" : "");
return;
} else {
a11 = a22 = 1;
a12 = a21 = 0;
}
// KEY INDEX AFFECTS
// a11 0 rotation, rotationY, scaleX
// a21 1 rotation, rotationY, scaleX
// a31 2 rotationY, scaleX
// a41 3 rotationY, scaleX
// a12 4 rotation, skewX, rotationX, scaleY
// a22 5 rotation, skewX, rotationX, scaleY
// a32 6 rotationX, scaleY
// a42 7 rotationX, scaleY
// a13 8 rotationY, rotationX, scaleZ
// a23 9 rotationY, rotationX, scaleZ
// a33 10 rotationY, rotationX, scaleZ
// a43 11 rotationY, rotationX, perspective, scaleZ
// a14 12 x, zOrigin, svgOrigin
// a24 13 y, zOrigin, svgOrigin
// a34 14 z, zOrigin
// a44 15
// rotation: Math.atan2(a21, a11)
// rotationY: Math.atan2(a13, a33) (or Math.atan2(a13, a11))
// rotationX: Math.atan2(a32, a33)
a33 = 1;
a13 = a23 = a31 = a32 = a41 = a42 = 0;
a43 = (perspective) ? -1 / perspective : 0;
zOrigin = t.zOrigin;
min = 0.000001; //threshold below which browsers use scientific notation which won't work.
comma = ",";
zero = "0";
angle = rotationY * _DEG2RAD;
if (angle) {
cos = Math.cos(angle);
sin = Math.sin(angle);
a31 = -sin;
a41 = a43 * -sin;
a13 = a11 * sin;
a23 = a21 * sin;
a33 = cos;
a43 *= cos;
a11 *= cos;
a21 *= cos;
}
angle = rotationX * _DEG2RAD;
if (angle) {
cos = Math.cos(angle);
sin = Math.sin(angle);
t1 = a12 * cos + a13 * sin;
t2 = a22 * cos + a23 * sin;
a32 = a33 * sin;
a42 = a43 * sin;
a13 = a12 * -sin + a13 * cos;
a23 = a22 * -sin + a23 * cos;
a33 = a33 * cos;
a43 = a43 * cos;
a12 = t1;
a22 = t2;
}
if (sz !== 1) {
a13 *= sz;
a23 *= sz;
a33 *= sz;
a43 *= sz;
}
if (sy !== 1) {
a12 *= sy;
a22 *= sy;
a32 *= sy;
a42 *= sy;
}
if (sx !== 1) {
a11 *= sx;
a21 *= sx;
a31 *= sx;
a41 *= sx;
}
if (zOrigin || isSVG) {
if (zOrigin) {
x += a13 * -zOrigin;
y += a23 * -zOrigin;
z += a33 * -zOrigin + zOrigin;
}
if (isSVG) { //due to bugs in some browsers, we need to manage the transform-origin of SVG manually
x += t.xOrigin - (t.xOrigin * a11 + t.yOrigin * a12) + t.xOffset;
y += t.yOrigin - (t.xOrigin * a21 + t.yOrigin * a22) + t.yOffset;
}
if (x < min && x > -min) {
x = zero;
}
if (y < min && y > -min) {
y = zero;
}
if (z < min && z > -min) {
z = 0; //don't use string because we calculate perspective later and need the number.
}
}
//optimized way of concatenating all the values into a string. If we do it all in one shot, it's slower because of the way browsers have to create temp strings and the way it affects memory. If we do it piece-by-piece with +=, it's a bit slower too. We found that doing it in these sized chunks works best overall:
transform = ((t.xPercent || t.yPercent) ? "translate(" + t.xPercent + "%," + t.yPercent + "%) matrix3d(" : "matrix3d(");
transform += ((a11 < min && a11 > -min) ? zero : a11) + comma + ((a21 < min && a21 > -min) ? zero : a21) + comma + ((a31 < min && a31 > -min) ? zero : a31);
transform += comma + ((a41 < min && a41 > -min) ? zero : a41) + comma + ((a12 < min && a12 > -min) ? zero : a12) + comma + ((a22 < min && a22 > -min) ? zero : a22);
if (rotationX || rotationY || sz !== 1) { //performance optimization (often there's no rotationX or rotationY, so we can skip these calculations)
transform += comma + ((a32 < min && a32 > -min) ? zero : a32) + comma + ((a42 < min && a42 > -min) ? zero : a42) + comma + ((a13 < min && a13 > -min) ? zero : a13);
transform += comma + ((a23 < min && a23 > -min) ? zero : a23) + comma + ((a33 < min && a33 > -min) ? zero : a33) + comma + ((a43 < min && a43 > -min) ? zero : a43) + comma;
} else {
transform += ",0,0,0,0,1,0,";
}
transform += x + comma + y + comma + z + comma + (perspective ? (1 + (-z / perspective)) : 1) + ")";
style[_transformProp] = transform;
};
p = Transform.prototype;
p.x = p.y = p.z = p.skewX = p.skewY = p.rotation = p.rotationX = p.rotationY = p.zOrigin = p.xPercent = p.yPercent = p.xOffset = p.yOffset = 0;
p.scaleX = p.scaleY = p.scaleZ = 1;
_registerComplexSpecialProp("transform,scale,scaleX,scaleY,scaleZ,x,y,z,rotation,rotationX,rotationY,rotationZ,skewX,skewY,shortRotation,shortRotationX,shortRotationY,shortRotationZ,transformOrigin,svgOrigin,transformPerspective,directionalRotation,parseTransform,force3D,skewType,xPercent,yPercent,smoothOrigin", {
parser: function (t, e, p, cssp, pt, plugin, vars) {
if (cssp._lastParsedTransform === vars) { return pt; } //only need to parse the transform once, and only if the browser supports it.
cssp._lastParsedTransform = vars;
var originalGSTransform = t._gsTransform,
style = t.style,
min = 0.000001,
i = _transformProps.length,
v = vars,
endRotations = {},
transformOriginString = "transformOrigin",
m1, m2, copy, orig, has3D, hasChange, dr, x, y, matrix;
if (vars.display) { //if the user is setting display during this tween, it may not be instantiated yet but we must force it here in order to get accurate readings. If display is "none", some browsers refuse to report the transform properties correctly.
copy = _getStyle(t, "display");
style.display = "block";
m1 = _getTransform(t, _cs, true, vars.parseTransform);
style.display = copy;
} else {
m1 = _getTransform(t, _cs, true, vars.parseTransform);
}
cssp._transform = m1;
if (typeof (v.transform) === "string" && _transformProp) { //for values like transform:"rotate(60deg) scale(0.5, 0.8)"
copy = _tempDiv.style; //don't use the original target because it might be SVG in which case some browsers don't report computed style correctly.
copy[_transformProp] = v.transform;
copy.display = "block"; //if display is "none", the browser often refuses to report the transform properties correctly.
copy.position = "absolute";
_doc.body.appendChild(_tempDiv);
m2 = _getTransform(_tempDiv, null, false);
if (m1.svg) { //if it's an SVG element, x/y part of the matrix will be affected by whatever we use as the origin and the offsets, so compensate here...
x = m1.xOrigin;
y = m1.yOrigin;
m2.x -= m1.xOffset;
m2.y -= m1.yOffset;
if (v.transformOrigin || v.svgOrigin) { //if this tween is altering the origin, we must factor that in here. The actual work of recording the transformOrigin values and setting up the PropTween is done later (still inside this function) so we cannot leave the changes intact here - we only want to update the x/y accordingly.
orig = {};
_parseSVGOrigin(t, _parsePosition(v.transformOrigin), orig, v.svgOrigin, v.smoothOrigin, true);
x = orig.xOrigin;
y = orig.yOrigin;
m2.x -= orig.xOffset - m1.xOffset;
m2.y -= orig.yOffset - m1.yOffset;
}
if (x || y) {
matrix = _getMatrix(_tempDiv);
m2.x -= x - (x * matrix[0] + y * matrix[2]);
m2.y -= y - (x * matrix[1] + y * matrix[3]);
}
}
_doc.body.removeChild(_tempDiv);
if (!m2.perspective) {
m2.perspective = m1.perspective; //tweening to no perspective gives very unintuitive results - just keep the same perspective in that case.
}
if (v.xPercent != null) {
m2.xPercent = _parseVal(v.xPercent, m1.xPercent);
}
if (v.yPercent != null) {
m2.yPercent = _parseVal(v.yPercent, m1.yPercent);
}
} else if (typeof (v) === "object") { //for values like scaleX, scaleY, rotation, x, y, skewX, and skewY or transform:{...} (object)
m2 = {
scaleX: _parseVal((v.scaleX != null) ? v.scaleX : v.scale, m1.scaleX),
scaleY: _parseVal((v.scaleY != null) ? v.scaleY : v.scale, m1.scaleY),
scaleZ: _parseVal(v.scaleZ, m1.scaleZ),
x: _parseVal(v.x, m1.x),
y: _parseVal(v.y, m1.y),
z: _parseVal(v.z, m1.z),
xPercent: _parseVal(v.xPercent, m1.xPercent),
yPercent: _parseVal(v.yPercent, m1.yPercent),
perspective: _parseVal(v.transformPerspective, m1.perspective)
};
dr = v.directionalRotation;
if (dr != null) {
if (typeof (dr) === "object") {
for (copy in dr) {
v[copy] = dr[copy];
}
} else {
v.rotation = dr;
}
}
if (typeof (v.x) === "string" && v.x.indexOf("%") !== -1) {
m2.x = 0;
m2.xPercent = _parseVal(v.x, m1.xPercent);
}
if (typeof (v.y) === "string" && v.y.indexOf("%") !== -1) {
m2.y = 0;
m2.yPercent = _parseVal(v.y, m1.yPercent);
}
m2.rotation = _parseAngle(("rotation" in v) ? v.rotation : ("shortRotation" in v) ? v.shortRotation + "_short" : ("rotationZ" in v) ? v.rotationZ : m1.rotation - m1.skewY, m1.rotation - m1.skewY, "rotation", endRotations); //see notes below about skewY for why we subtract it from rotation here
if (_supports3D) {
m2.rotationX = _parseAngle(("rotationX" in v) ? v.rotationX : ("shortRotationX" in v) ? v.shortRotationX + "_short" : m1.rotationX || 0, m1.rotationX, "rotationX", endRotations);
m2.rotationY = _parseAngle(("rotationY" in v) ? v.rotationY : ("shortRotationY" in v) ? v.shortRotationY + "_short" : m1.rotationY || 0, m1.rotationY, "rotationY", endRotations);
}
m2.skewX = _parseAngle(v.skewX, m1.skewX - m1.skewY); //see notes below about skewY and why we subtract it from skewX here
//note: for performance reasons, we combine all skewing into the skewX and rotation values, ignoring skewY but we must still record it so that we can discern how much of the overall skew is attributed to skewX vs. skewY. Otherwise, if the skewY would always act relative (tween skewY to 10deg, for example, multiple times and if we always combine things into skewX, we can't remember that skewY was 10 from last time). Remember, a skewY of 10 degrees looks the same as a rotation of 10 degrees plus a skewX of -10 degrees.
if ((m2.skewY = _parseAngle(v.skewY, m1.skewY))) {
m2.skewX += m2.skewY;
m2.rotation += m2.skewY;
}
}
if (_supports3D && v.force3D != null) {
m1.force3D = v.force3D;
hasChange = true;
}
m1.skewType = v.skewType || m1.skewType || CSSPlugin.defaultSkewType;
has3D = (m1.force3D || m1.z || m1.rotationX || m1.rotationY || m2.z || m2.rotationX || m2.rotationY || m2.perspective);
if (!has3D && v.scale != null) {
m2.scaleZ = 1; //no need to tween scaleZ.
}
while (--i > -1) {
p = _transformProps[i];
orig = m2[p] - m1[p];
if (orig > min || orig < -min || v[p] != null || _forcePT[p] != null) {
hasChange = true;
pt = new CSSPropTween(m1, p, m1[p], orig, pt);
if (p in endRotations) {
pt.e = endRotations[p]; //directional rotations typically have compensated values during the tween, but we need to make sure they end at exactly what the user requested
}
pt.xs0 = 0; //ensures the value stays numeric in setRatio()
pt.plugin = plugin;
cssp._overwriteProps.push(pt.n);
}
}
orig = v.transformOrigin;
if (m1.svg && (orig || v.svgOrigin)) {
x = m1.xOffset; //when we change the origin, in order to prevent things from jumping we adjust the x/y so we must record those here so that we can create PropTweens for them and flip them at the same time as the origin
y = m1.yOffset;
_parseSVGOrigin(t, _parsePosition(orig), m2, v.svgOrigin, v.smoothOrigin);
pt = _addNonTweeningNumericPT(m1, "xOrigin", (originalGSTransform ? m1 : m2).xOrigin, m2.xOrigin, pt, transformOriginString); //note: if there wasn't a transformOrigin defined yet, just start with the destination one; it's wasteful otherwise, and it causes problems with fromTo() tweens. For example, TweenLite.to("#wheel", 3, {rotation:180, transformOrigin:"50% 50%", delay:1}); TweenLite.fromTo("#wheel", 3, {scale:0.5, transformOrigin:"50% 50%"}, {scale:1, delay:2}); would cause a jump when the from values revert at the beginning of the 2nd tween.
pt = _addNonTweeningNumericPT(m1, "yOrigin", (originalGSTransform ? m1 : m2).yOrigin, m2.yOrigin, pt, transformOriginString);
if (x !== m1.xOffset || y !== m1.yOffset) {
pt = _addNonTweeningNumericPT(m1, "xOffset", (originalGSTransform ? x : m1.xOffset), m1.xOffset, pt, transformOriginString);
pt = _addNonTweeningNumericPT(m1, "yOffset", (originalGSTransform ? y : m1.yOffset), m1.yOffset, pt, transformOriginString);
}
orig = _useSVGTransformAttr ? null : "0px 0px"; //certain browsers (like firefox) completely botch transform-origin, so we must remove it to prevent it from contaminating transforms. We manage it ourselves with xOrigin and yOrigin
}
if (orig || (_supports3D && has3D && m1.zOrigin)) { //if anything 3D is happening and there's a transformOrigin with a z component that's non-zero, we must ensure that the transformOrigin's z-component is set to 0 so that we can manually do those calculations to get around Safari bugs. Even if the user didn't specifically define a "transformOrigin" in this particular tween (maybe they did it via css directly).
if (_transformProp) {
hasChange = true;
p = _transformOriginProp;
orig = (orig || _getStyle(t, p, _cs, false, "50% 50%")) + ""; //cast as string to avoid errors
pt = new CSSPropTween(style, p, 0, 0, pt, -1, transformOriginString);
pt.b = style[p];
pt.plugin = plugin;
if (_supports3D) {
copy = m1.zOrigin;
orig = orig.split(" ");
m1.zOrigin = ((orig.length > 2 && !(copy !== 0 && orig[2] === "0px")) ? parseFloat(orig[2]) : copy) || 0; //Safari doesn't handle the z part of transformOrigin correctly, so we'll manually handle it in the _set3DTransformRatio() method.
pt.xs0 = pt.e = orig[0] + " " + (orig[1] || "50%") + " 0px"; //we must define a z value of 0px specifically otherwise iOS 5 Safari will stick with the old one (if one was defined)!
pt = new CSSPropTween(m1, "zOrigin", 0, 0, pt, -1, pt.n); //we must create a CSSPropTween for the _gsTransform.zOrigin so that it gets reset properly at the beginning if the tween runs backward (as opposed to just setting m1.zOrigin here)
pt.b = copy;
pt.xs0 = pt.e = m1.zOrigin;
} else {
pt.xs0 = pt.e = orig;
}
//for older versions of IE (6-8), we need to manually calculate things inside the setRatio() function. We record origin x and y (ox and oy) and whether or not the values are percentages (oxp and oyp).
} else {
_parsePosition(orig + "", m1);
}
}
if (hasChange) {
cssp._transformType = (!(m1.svg && _useSVGTransformAttr) && (has3D || this._transformType === 3)) ? 3 : 2; //quicker than calling cssp._enableTransforms();
}
return pt;
}, prefix: true
});
_registerComplexSpecialProp("boxShadow", { defaultValue: "0px 0px 0px 0px #999", prefix: true, color: true, multi: true, keyword: "inset" });
_registerComplexSpecialProp("borderRadius", {
defaultValue: "0px", parser: function (t, e, p, cssp, pt, plugin) {
e = this.format(e);
var props = ["borderTopLeftRadius", "borderTopRightRadius", "borderBottomRightRadius", "borderBottomLeftRadius"],
style = t.style,
ea1, i, es2, bs2, bs, es, bn, en, w, h, esfx, bsfx, rel, hn, vn, em;
w = parseFloat(t.offsetWidth);
h = parseFloat(t.offsetHeight);
ea1 = e.split(" ");
for (i = 0; i < props.length; i++) { //if we're dealing with percentages, we must convert things separately for the horizontal and vertical axis!
if (this.p.indexOf("border")) { //older browsers used a prefix
props[i] = _checkPropPrefix(props[i]);
}
bs = bs2 = _getStyle(t, props[i], _cs, false, "0px");
if (bs.indexOf(" ") !== -1) {
bs2 = bs.split(" ");
bs = bs2[0];
bs2 = bs2[1];
}
es = es2 = ea1[i];
bn = parseFloat(bs);
bsfx = bs.substr((bn + "").length);
rel = (es.charAt(1) === "=");
if (rel) {
en = parseInt(es.charAt(0) + "1", 10);
es = es.substr(2);
en *= parseFloat(es);
esfx = es.substr((en + "").length - (en < 0 ? 1 : 0)) || "";
} else {
en = parseFloat(es);
esfx = es.substr((en + "").length);
}
if (esfx === "") {
esfx = _suffixMap[p] || bsfx;
}
if (esfx !== bsfx) {
hn = _convertToPixels(t, "borderLeft", bn, bsfx); //horizontal number (we use a bogus "borderLeft" property just because the _convertToPixels() method searches for the keywords "Left", "Right", "Top", and "Bottom" to determine of it's a horizontal or vertical property, and we need "border" in the name so that it knows it should measure relative to the element itself, not its parent.
vn = _convertToPixels(t, "borderTop", bn, bsfx); //vertical number
if (esfx === "%") {
bs = (hn / w * 100) + "%";
bs2 = (vn / h * 100) + "%";
} else if (esfx === "em") {
em = _convertToPixels(t, "borderLeft", 1, "em");
bs = (hn / em) + "em";
bs2 = (vn / em) + "em";
} else {
bs = hn + "px";
bs2 = vn + "px";
}
if (rel) {
es = (parseFloat(bs) + en) + esfx;
es2 = (parseFloat(bs2) + en) + esfx;
}
}
pt = _parseComplex(style, props[i], bs + " " + bs2, es + " " + es2, false, "0px", pt);
}
return pt;
}, prefix: true, formatter: _getFormatter("0px 0px 0px 0px", false, true)
});
_registerComplexSpecialProp("borderBottomLeftRadius,borderBottomRightRadius,borderTopLeftRadius,borderTopRightRadius", {
defaultValue: "0px", parser: function (t, e, p, cssp, pt, plugin) {
return _parseComplex(t.style, p, this.format(_getStyle(t, p, _cs, false, "0px 0px")), this.format(e), false, "0px", pt);
}, prefix: true, formatter: _getFormatter("0px 0px", false, true)
});
_registerComplexSpecialProp("backgroundPosition", {
defaultValue: "0 0", parser: function (t, e, p, cssp, pt, plugin) {
var bp = "background-position",
cs = (_cs || _getComputedStyle(t, null)),
bs = this.format(((cs) ? _ieVers ? cs.getPropertyValue(bp + "-x") + " " + cs.getPropertyValue(bp + "-y") : cs.getPropertyValue(bp) : t.currentStyle.backgroundPositionX + " " + t.currentStyle.backgroundPositionY) || "0 0"), //Internet Explorer doesn't report background-position correctly - we must query background-position-x and background-position-y and combine them (even in IE10). Before IE9, we must do the same with the currentStyle object and use camelCase
es = this.format(e),
ba, ea, i, pct, overlap, src;
if ((bs.indexOf("%") !== -1) !== (es.indexOf("%") !== -1) && es.split(",").length < 2) {
src = _getStyle(t, "backgroundImage").replace(_urlExp, "");
if (src && src !== "none") {
ba = bs.split(" ");
ea = es.split(" ");
_tempImg.setAttribute("src", src); //set the temp IMG's src to the background-image so that we can measure its width/height
i = 2;
while (--i > -1) {
bs = ba[i];
pct = (bs.indexOf("%") !== -1);
if (pct !== (ea[i].indexOf("%") !== -1)) {
overlap = (i === 0) ? t.offsetWidth - _tempImg.width : t.offsetHeight - _tempImg.height;
ba[i] = pct ? (parseFloat(bs) / 100 * overlap) + "px" : (parseFloat(bs) / overlap * 100) + "%";
}
}
bs = ba.join(" ");
}
}
return this.parseComplex(t.style, bs, es, pt, plugin);
}, formatter: _parsePosition
});
_registerComplexSpecialProp("backgroundSize", { defaultValue: "0 0", formatter: _parsePosition });
_registerComplexSpecialProp("perspective", { defaultValue: "0px", prefix: true });
_registerComplexSpecialProp("perspectiveOrigin", { defaultValue: "50% 50%", prefix: true });
_registerComplexSpecialProp("transformStyle", { prefix: true });
_registerComplexSpecialProp("backfaceVisibility", { prefix: true });
_registerComplexSpecialProp("userSelect", { prefix: true });
_registerComplexSpecialProp("margin", { parser: _getEdgeParser("marginTop,marginRight,marginBottom,marginLeft") });
_registerComplexSpecialProp("padding", { parser: _getEdgeParser("paddingTop,paddingRight,paddingBottom,paddingLeft") });
_registerComplexSpecialProp("clip", {
defaultValue: "rect(0px,0px,0px,0px)", parser: function (t, e, p, cssp, pt, plugin) {
var b, cs, delim;
if (_ieVers < 9) { //IE8 and earlier don't report a "clip" value in the currentStyle - instead, the values are split apart into clipTop, clipRight, clipBottom, and clipLeft. Also, in IE7 and earlier, the values inside rect() are space-delimited, not comma-delimited.
cs = t.currentStyle;
delim = _ieVers < 8 ? " " : ",";
b = "rect(" + cs.clipTop + delim + cs.clipRight + delim + cs.clipBottom + delim + cs.clipLeft + ")";
e = this.format(e).split(",").join(delim);
} else {
b = this.format(_getStyle(t, this.p, _cs, false, this.dflt));
e = this.format(e);
}
return this.parseComplex(t.style, b, e, pt, plugin);
}
});
_registerComplexSpecialProp("textShadow", { defaultValue: "0px 0px 0px #999", color: true, multi: true });
_registerComplexSpecialProp("autoRound,strictUnits", { parser: function (t, e, p, cssp, pt) { return pt; } }); //just so that we can ignore these properties (not tween them)
_registerComplexSpecialProp("border", {
defaultValue: "0px solid #000", parser: function (t, e, p, cssp, pt, plugin) {
return this.parseComplex(t.style, this.format(_getStyle(t, "borderTopWidth", _cs, false, "0px") + " " + _getStyle(t, "borderTopStyle", _cs, false, "solid") + " " + _getStyle(t, "borderTopColor", _cs, false, "#000")), this.format(e), pt, plugin);
}, color: true, formatter: function (v) {
var a = v.split(" ");
return a[0] + " " + (a[1] || "solid") + " " + (v.match(_colorExp) || ["#000"])[0];
}
});
_registerComplexSpecialProp("borderWidth", { parser: _getEdgeParser("borderTopWidth,borderRightWidth,borderBottomWidth,borderLeftWidth") }); //Firefox doesn't pick up on borderWidth set in style sheets (only inline).
_registerComplexSpecialProp("float,cssFloat,styleFloat", {
parser: function (t, e, p, cssp, pt, plugin) {
var s = t.style,
prop = ("cssFloat" in s) ? "cssFloat" : "styleFloat";
return new CSSPropTween(s, prop, 0, 0, pt, -1, p, false, 0, s[prop], e);
}
});
//opacity-related
var _setIEOpacityRatio = function (v) {
var t = this.t, //refers to the element's style property
filters = t.filter || _getStyle(this.data, "filter") || "",
val = (this.s + this.c * v) | 0,
skip;
if (val === 100) { //for older versions of IE that need to use a filter to apply opacity, we should remove the filter if opacity hits 1 in order to improve performance, but make sure there isn't a transform (matrix) or gradient in the filters.
if (filters.indexOf("atrix(") === -1 && filters.indexOf("radient(") === -1 && filters.indexOf("oader(") === -1) {
t.removeAttribute("filter");
skip = (!_getStyle(this.data, "filter")); //if a class is applied that has an alpha filter, it will take effect (we don't want that), so re-apply our alpha filter in that case. We must first remove it and then check.
} else {
t.filter = filters.replace(_alphaFilterExp, "");
skip = true;
}
}
if (!skip) {
if (this.xn1) {
t.filter = filters = filters || ("alpha(opacity=" + val + ")"); //works around bug in IE7/8 that prevents changes to "visibility" from being applied properly if the filter is changed to a different alpha on the same frame.
}
if (filters.indexOf("pacity") === -1) { //only used if browser doesn't support the standard opacity style property (IE 7 and 8). We omit the "O" to avoid case-sensitivity issues
if (val !== 0 || !this.xn1) { //bugs in IE7/8 won't render the filter properly if opacity is ADDED on the same frame/render as "visibility" changes (this.xn1 is 1 if this tween is an "autoAlpha" tween)
t.filter = filters + " alpha(opacity=" + val + ")"; //we round the value because otherwise, bugs in IE7/8 can prevent "visibility" changes from being applied properly.
}
} else {
t.filter = filters.replace(_opacityExp, "opacity=" + val);
}
}
};
_registerComplexSpecialProp("opacity,alpha,autoAlpha", {
defaultValue: "1", parser: function (t, e, p, cssp, pt, plugin) {
var b = parseFloat(_getStyle(t, "opacity", _cs, false, "1")),
style = t.style,
isAutoAlpha = (p === "autoAlpha");
if (typeof (e) === "string" && e.charAt(1) === "=") {
e = ((e.charAt(0) === "-") ? -1 : 1) * parseFloat(e.substr(2)) + b;
}
if (isAutoAlpha && b === 1 && _getStyle(t, "visibility", _cs) === "hidden" && e !== 0) { //if visibility is initially set to "hidden", we should interpret that as intent to make opacity 0 (a convenience)
b = 0;
}
if (_supportsOpacity) {
pt = new CSSPropTween(style, "opacity", b, e - b, pt);
} else {
pt = new CSSPropTween(style, "opacity", b * 100, (e - b) * 100, pt);
pt.xn1 = isAutoAlpha ? 1 : 0; //we need to record whether or not this is an autoAlpha so that in the setRatio(), we know to duplicate the setting of the alpha in order to work around a bug in IE7 and IE8 that prevents changes to "visibility" from taking effect if the filter is changed to a different alpha(opacity) at the same time. Setting it to the SAME value first, then the new value works around the IE7/8 bug.
style.zoom = 1; //helps correct an IE issue.
pt.type = 2;
pt.b = "alpha(opacity=" + pt.s + ")";
pt.e = "alpha(opacity=" + (pt.s + pt.c) + ")";
pt.data = t;
pt.plugin = plugin;
pt.setRatio = _setIEOpacityRatio;
}
if (isAutoAlpha) { //we have to create the "visibility" PropTween after the opacity one in the linked list so that they run in the order that works properly in IE8 and earlier
pt = new CSSPropTween(style, "visibility", 0, 0, pt, -1, null, false, 0, ((b !== 0) ? "inherit" : "hidden"), ((e === 0) ? "hidden" : "inherit"));
pt.xs0 = "inherit";
cssp._overwriteProps.push(pt.n);
cssp._overwriteProps.push(p);
}
return pt;
}
});
var _removeProp = function (s, p) {
if (p) {
if (s.removeProperty) {
if (p.substr(0, 2) === "ms" || p.substr(0, 6) === "webkit") { //Microsoft and some Webkit browsers don't conform to the standard of capitalizing the first prefix character, so we adjust so that when we prefix the caps with a dash, it's correct (otherwise it'd be "ms-transform" instead of "-ms-transform" for IE9, for example)
p = "-" + p;
}
s.removeProperty(p.replace(_capsExp, "-$1").toLowerCase());
} else { //note: old versions of IE use "removeAttribute()" instead of "removeProperty()"
s.removeAttribute(p);
}
}
},
_setClassNameRatio = function (v) {
this.t._gsClassPT = this;
if (v === 1 || v === 0) {
this.t.setAttribute("class", (v === 0) ? this.b : this.e);
var mpt = this.data, //first MiniPropTween
s = this.t.style;
while (mpt) {
if (!mpt.v) {
_removeProp(s, mpt.p);
} else {
s[mpt.p] = mpt.v;
}
mpt = mpt._next;
}
if (v === 1 && this.t._gsClassPT === this) {
this.t._gsClassPT = null;
}
} else if (this.t.getAttribute("class") !== this.e) {
this.t.setAttribute("class", this.e);
}
};
_registerComplexSpecialProp("className", {
parser: function (t, e, p, cssp, pt, plugin, vars) {
var b = t.getAttribute("class") || "", //don't use t.className because it doesn't work consistently on SVG elements; getAttribute("class") and setAttribute("class", value") is more reliable.
cssText = t.style.cssText,
difData, bs, cnpt, cnptLookup, mpt;
pt = cssp._classNamePT = new CSSPropTween(t, p, 0, 0, pt, 2);
pt.setRatio = _setClassNameRatio;
pt.pr = -11;
_hasPriority = true;
pt.b = b;
bs = _getAllStyles(t, _cs);
//if there's a className tween already operating on the target, force it to its end so that the necessary inline styles are removed and the class name is applied before we determine the end state (we don't want inline styles interfering that were there just for class-specific values)
cnpt = t._gsClassPT;
if (cnpt) {
cnptLookup = {};
mpt = cnpt.data; //first MiniPropTween which stores the inline styles - we need to force these so that the inline styles don't contaminate things. Otherwise, there's a small chance that a tween could start and the inline values match the destination values and they never get cleaned.
while (mpt) {
cnptLookup[mpt.p] = 1;
mpt = mpt._next;
}
cnpt.setRatio(1);
}
t._gsClassPT = pt;
pt.e = (e.charAt(1) !== "=") ? e : b.replace(new RegExp("(?:\\s|^)" + e.substr(2) + "(?![\\w-])"), "") + ((e.charAt(0) === "+") ? " " + e.substr(2) : "");
t.setAttribute("class", pt.e);
difData = _cssDif(t, bs, _getAllStyles(t), vars, cnptLookup);
t.setAttribute("class", b);
pt.data = difData.firstMPT;
t.style.cssText = cssText; //we recorded cssText before we swapped classes and ran _getAllStyles() because in cases when a className tween is overwritten, we remove all the related tweening properties from that class change (otherwise class-specific stuff can't override properties we've directly set on the target's style object due to specificity).
pt = pt.xfirst = cssp.parse(t, difData.difs, pt, plugin); //we record the CSSPropTween as the xfirst so that we can handle overwriting propertly (if "className" gets overwritten, we must kill all the properties associated with the className part of the tween, so we can loop through from xfirst to the pt itself)
return pt;
}
});
var _setClearPropsRatio = function (v) {
if (v === 1 || v === 0) if (this.data._totalTime === this.data._totalDuration && this.data.data !== "isFromStart") { //this.data refers to the tween. Only clear at the END of the tween (remember, from() tweens make the ratio go from 1 to 0, so we can't just check that and if the tween is the zero-duration one that's created internally to render the starting values in a from() tween, ignore that because otherwise, for example, from(...{height:100, clearProps:"height", delay:1}) would wipe the height at the beginning of the tween and after 1 second, it'd kick back in).
var s = this.t.style,
transformParse = _specialProps.transform.parse,
a, p, i, clearTransform, transform;
if (this.e === "all") {
s.cssText = "";
clearTransform = true;
} else {
a = this.e.split(" ").join("").split(",");
i = a.length;
while (--i > -1) {
p = a[i];
if (_specialProps[p]) {
if (_specialProps[p].parse === transformParse) {
clearTransform = true;
} else {
p = (p === "transformOrigin") ? _transformOriginProp : _specialProps[p].p; //ensures that special properties use the proper browser-specific property name, like "scaleX" might be "-webkit-transform" or "boxShadow" might be "-moz-box-shadow"
}
}
_removeProp(s, p);
}
}
if (clearTransform) {
_removeProp(s, _transformProp);
transform = this.t._gsTransform;
if (transform) {
if (transform.svg) {
this.t.removeAttribute("data-svg-origin");
this.t.removeAttribute("transform");
}
delete this.t._gsTransform;
}
}
}
};
_registerComplexSpecialProp("clearProps", {
parser: function (t, e, p, cssp, pt) {
pt = new CSSPropTween(t, p, 0, 0, pt, 2);
pt.setRatio = _setClearPropsRatio;
pt.e = e;
pt.pr = -10;
pt.data = cssp._tween;
_hasPriority = true;
return pt;
}
});
p = "bezier,throwProps,physicsProps,physics2D".split(",");
i = p.length;
while (i--) {
_registerPluginProp(p[i]);
}
p = CSSPlugin.prototype;
p._firstPT = p._lastParsedTransform = p._transform = null;
//gets called when the tween renders for the first time. This kicks everything off, recording start/end values, etc.
p._onInitTween = function (target, vars, tween) {
if (!target.nodeType) { //css is only for dom elements
return false;
}
this._target = target;
this._tween = tween;
this._vars = vars;
_autoRound = vars.autoRound;
_hasPriority = false;
_suffixMap = vars.suffixMap || CSSPlugin.suffixMap;
_cs = _getComputedStyle(target, "");
_overwriteProps = this._overwriteProps;
var style = target.style,
v, pt, pt2, first, last, next, zIndex, tpt, threeD;
if (_reqSafariFix) if (style.zIndex === "") {
v = _getStyle(target, "zIndex", _cs);
if (v === "auto" || v === "") {
//corrects a bug in [non-Android] Safari that prevents it from repainting elements in their new positions if they don't have a zIndex set. We also can't just apply this inside _parseTransform() because anything that's moved in any way (like using "left" or "top" instead of transforms like "x" and "y") can be affected, so it is best to ensure that anything that's tweening has a z-index. Setting "WebkitPerspective" to a non-zero value worked too except that on iOS Safari things would flicker randomly. Plus zIndex is less memory-intensive.
this._addLazySet(style, "zIndex", 0);
}
}
if (typeof (vars) === "string") {
first = style.cssText;
v = _getAllStyles(target, _cs);
style.cssText = first + ";" + vars;
v = _cssDif(target, v, _getAllStyles(target)).difs;
if (!_supportsOpacity && _opacityValExp.test(vars)) {
v.opacity = parseFloat(RegExp.$1);
}
vars = v;
style.cssText = first;
}
if (vars.className) { //className tweens will combine any differences they find in the css with the vars that are passed in, so {className:"myClass", scale:0.5, left:20} would work.
this._firstPT = pt = _specialProps.className.parse(target, vars.className, "className", this, null, null, vars);
} else {
this._firstPT = pt = this.parse(target, vars, null);
}
if (this._transformType) {
threeD = (this._transformType === 3);
if (!_transformProp) {
style.zoom = 1; //helps correct an IE issue.
} else if (_isSafari) {
_reqSafariFix = true;
//if zIndex isn't set, iOS Safari doesn't repaint things correctly sometimes (seemingly at random).
if (style.zIndex === "") {
zIndex = _getStyle(target, "zIndex", _cs);
if (zIndex === "auto" || zIndex === "") {
this._addLazySet(style, "zIndex", 0);
}
}
//Setting WebkitBackfaceVisibility corrects 3 bugs:
// 1) [non-Android] Safari skips rendering changes to "top" and "left" that are made on the same frame/render as a transform update.
// 2) iOS Safari sometimes neglects to repaint elements in their new positions. Setting "WebkitPerspective" to a non-zero value worked too except that on iOS Safari things would flicker randomly.
// 3) Safari sometimes displayed odd artifacts when tweening the transform (or WebkitTransform) property, like ghosts of the edges of the element remained. Definitely a browser bug.
//Note: we allow the user to override the auto-setting by defining WebkitBackfaceVisibility in the vars of the tween.
if (_isSafariLT6) {
this._addLazySet(style, "WebkitBackfaceVisibility", this._vars.WebkitBackfaceVisibility || (threeD ? "visible" : "hidden"));
}
}
pt2 = pt;
while (pt2 && pt2._next) {
pt2 = pt2._next;
}
tpt = new CSSPropTween(target, "transform", 0, 0, null, 2);
this._linkCSSP(tpt, null, pt2);
tpt.setRatio = _transformProp ? _setTransformRatio : _setIETransformRatio;
tpt.data = this._transform || _getTransform(target, _cs, true);
tpt.tween = tween;
tpt.pr = -1; //ensures that the transforms get applied after the components are updated.
_overwriteProps.pop(); //we don't want to force the overwrite of all "transform" tweens of the target - we only care about individual transform properties like scaleX, rotation, etc. The CSSPropTween constructor automatically adds the property to _overwriteProps which is why we need to pop() here.
}
if (_hasPriority) {
//reorders the linked list in order of pr (priority)
while (pt) {
next = pt._next;
pt2 = first;
while (pt2 && pt2.pr > pt.pr) {
pt2 = pt2._next;
}
if ((pt._prev = pt2 ? pt2._prev : last)) {
pt._prev._next = pt;
} else {
first = pt;
}
if ((pt._next = pt2)) {
pt2._prev = pt;
} else {
last = pt;
}
pt = next;
}
this._firstPT = first;
}
return true;
};
p.parse = function (target, vars, pt, plugin) {
var style = target.style,
p, sp, bn, en, bs, es, bsfx, esfx, isStr, rel;
for (p in vars) {
es = vars[p]; //ending value string
sp = _specialProps[p]; //SpecialProp lookup.
if (sp) {
pt = sp.parse(target, es, p, this, pt, plugin, vars);
} else {
bs = _getStyle(target, p, _cs) + "";
isStr = (typeof (es) === "string");
if (p === "color" || p === "fill" || p === "stroke" || p.indexOf("Color") !== -1 || (isStr && _rgbhslExp.test(es))) { //Opera uses background: to define color sometimes in addition to backgroundColor:
if (!isStr) {
es = _parseColor(es);
es = ((es.length > 3) ? "rgba(" : "rgb(") + es.join(",") + ")";
}
pt = _parseComplex(style, p, bs, es, true, "transparent", pt, 0, plugin);
} else if (isStr && _complexExp.test(es)) {
pt = _parseComplex(style, p, bs, es, true, null, pt, 0, plugin);
} else {
bn = parseFloat(bs);
bsfx = (bn || bn === 0) ? bs.substr((bn + "").length) : ""; //remember, bs could be non-numeric like "normal" for fontWeight, so we should default to a blank suffix in that case.
if (bs === "" || bs === "auto") {
if (p === "width" || p === "height") {
bn = _getDimension(target, p, _cs);
bsfx = "px";
} else if (p === "left" || p === "top") {
bn = _calculateOffset(target, p, _cs);
bsfx = "px";
} else {
bn = (p !== "opacity") ? 0 : 1;
bsfx = "";
}
}
rel = (isStr && es.charAt(1) === "=");
if (rel) {
en = parseInt(es.charAt(0) + "1", 10);
es = es.substr(2);
en *= parseFloat(es);
esfx = es.replace(_suffixExp, "");
} else {
en = parseFloat(es);
esfx = isStr ? es.replace(_suffixExp, "") : "";
}
if (esfx === "") {
esfx = (p in _suffixMap) ? _suffixMap[p] : bsfx; //populate the end suffix, prioritizing the map, then if none is found, use the beginning suffix.
}
es = (en || en === 0) ? (rel ? en + bn : en) + esfx : vars[p]; //ensures that any += or -= prefixes are taken care of. Record the end value before normalizing the suffix because we always want to end the tween on exactly what they intended even if it doesn't match the beginning value's suffix.
//if the beginning/ending suffixes don't match, normalize them...
if (bsfx !== esfx) if (esfx !== "") if (en || en === 0) if (bn) { //note: if the beginning value (bn) is 0, we don't need to convert units!
bn = _convertToPixels(target, p, bn, bsfx);
if (esfx === "%") {
bn /= _convertToPixels(target, p, 100, "%") / 100;
if (vars.strictUnits !== true) { //some browsers report only "px" values instead of allowing "%" with getComputedStyle(), so we assume that if we're tweening to a %, we should start there too unless strictUnits:true is defined. This approach is particularly useful for responsive designs that use from() tweens.
bs = bn + "%";
}
} else if (esfx === "em" || esfx === "rem" || esfx === "vw" || esfx === "vh") {
bn /= _convertToPixels(target, p, 1, esfx);
//otherwise convert to pixels.
} else if (esfx !== "px") {
en = _convertToPixels(target, p, en, esfx);
esfx = "px"; //we don't use bsfx after this, so we don't need to set it to px too.
}
if (rel) if (en || en === 0) {
es = (en + bn) + esfx; //the changes we made affect relative calculations, so adjust the end value here.
}
}
if (rel) {
en += bn;
}
if ((bn || bn === 0) && (en || en === 0)) { //faster than isNaN(). Also, previously we required en !== bn but that doesn't really gain much performance and it prevents _parseToProxy() from working properly if beginning and ending values match but need to get tweened by an external plugin anyway. For example, a bezier tween where the target starts at left:0 and has these points: [{left:50},{left:0}] wouldn't work properly because when parsing the last point, it'd match the first (current) one and a non-tweening CSSPropTween would be recorded when we actually need a normal tween (type:0) so that things get updated during the tween properly.
pt = new CSSPropTween(style, p, bn, en - bn, pt, 0, p, (_autoRound !== false && (esfx === "px" || p === "zIndex")), 0, bs, es);
pt.xs0 = esfx;
//DEBUG: _log("tween "+p+" from "+pt.b+" ("+bn+esfx+") to "+pt.e+" with suffix: "+pt.xs0);
} else if (style[p] === undefined || !es && (es + "" === "NaN" || es == null)) {
_log("invalid " + p + " tween value: " + vars[p]);
} else {
pt = new CSSPropTween(style, p, en || bn || 0, 0, pt, -1, p, false, 0, bs, es);
pt.xs0 = (es === "none" && (p === "display" || p.indexOf("Style") !== -1)) ? bs : es; //intermediate value should typically be set immediately (end value) except for "display" or things like borderTopStyle, borderBottomStyle, etc. which should use the beginning value during the tween.
//DEBUG: _log("non-tweening value "+p+": "+pt.xs0);
}
}
}
if (plugin) if (pt && !pt.plugin) {
pt.plugin = plugin;
}
}
return pt;
};
//gets called every time the tween updates, passing the new ratio (typically a value between 0 and 1, but not always (for example, if an Elastic.easeOut is used, the value can jump above 1 mid-tween). It will always start and 0 and end at 1.
p.setRatio = function (v) {
var pt = this._firstPT,
min = 0.000001,
val, str, i;
//at the end of the tween, we set the values to exactly what we received in order to make sure non-tweening values (like "position" or "float" or whatever) are set and so that if the beginning/ending suffixes (units) didn't match and we normalized to px, the value that the user passed in is used here. We check to see if the tween is at its beginning in case it's a from() tween in which case the ratio will actually go from 1 to 0 over the course of the tween (backwards).
if (v === 1 && (this._tween._time === this._tween._duration || this._tween._time === 0)) {
while (pt) {
if (pt.type !== 2) {
if (pt.r && pt.type !== -1) {
val = Math.round(pt.s + pt.c);
if (!pt.type) {
pt.t[pt.p] = val + pt.xs0;
} else if (pt.type === 1) { //complex value (one that typically has multiple numbers inside a string, like "rect(5px,10px,20px,25px)"
i = pt.l;
str = pt.xs0 + val + pt.xs1;
for (i = 1; i < pt.l; i++) {
str += pt["xn" + i] + pt["xs" + (i + 1)];
}
pt.t[pt.p] = str;
}
} else {
pt.t[pt.p] = pt.e;
}
} else {
pt.setRatio(v);
}
pt = pt._next;
}
} else if (v || !(this._tween._time === this._tween._duration || this._tween._time === 0) || this._tween._rawPrevTime === -0.000001) {
while (pt) {
val = pt.c * v + pt.s;
if (pt.r) {
val = Math.round(val);
} else if (val < min) if (val > -min) {
val = 0;
}
if (!pt.type) {
pt.t[pt.p] = val + pt.xs0;
} else if (pt.type === 1) { //complex value (one that typically has multiple numbers inside a string, like "rect(5px,10px,20px,25px)"
i = pt.l;
if (i === 2) {
pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2;
} else if (i === 3) {
pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3;
} else if (i === 4) {
pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3 + pt.xn3 + pt.xs4;
} else if (i === 5) {
pt.t[pt.p] = pt.xs0 + val + pt.xs1 + pt.xn1 + pt.xs2 + pt.xn2 + pt.xs3 + pt.xn3 + pt.xs4 + pt.xn4 + pt.xs5;
} else {
str = pt.xs0 + val + pt.xs1;
for (i = 1; i < pt.l; i++) {
str += pt["xn" + i] + pt["xs" + (i + 1)];
}
pt.t[pt.p] = str;
}
} else if (pt.type === -1) { //non-tweening value
pt.t[pt.p] = pt.xs0;
} else if (pt.setRatio) { //custom setRatio() for things like SpecialProps, external plugins, etc.
pt.setRatio(v);
}
pt = pt._next;
}
//if the tween is reversed all the way back to the beginning, we need to restore the original values which may have different units (like % instead of px or em or whatever).
} else {
while (pt) {
if (pt.type !== 2) {
pt.t[pt.p] = pt.b;
} else {
pt.setRatio(v);
}
pt = pt._next;
}
}
};
/**
* @private
* Forces rendering of the target's transforms (rotation, scale, etc.) whenever the CSSPlugin's setRatio() is called.
* Basically, this tells the CSSPlugin to create a CSSPropTween (type 2) after instantiation that runs last in the linked
* list and calls the appropriate (3D or 2D) rendering function. We separate this into its own method so that we can call
* it from other plugins like BezierPlugin if, for example, it needs to apply an autoRotation and this CSSPlugin
* doesn't have any transform-related properties of its own. You can call this method as many times as you
* want and it won't create duplicate CSSPropTweens.
*
* @param {boolean} threeD if true, it should apply 3D tweens (otherwise, just 2D ones are fine and typically faster)
*/
p._enableTransforms = function (threeD) {
this._transform = this._transform || _getTransform(this._target, _cs, true); //ensures that the element has a _gsTransform property with the appropriate values.
this._transformType = (!(this._transform.svg && _useSVGTransformAttr) && (threeD || this._transformType === 3)) ? 3 : 2;
};
var lazySet = function (v) {
this.t[this.p] = this.e;
this.data._linkCSSP(this, this._next, null, true); //we purposefully keep this._next even though it'd make sense to null it, but this is a performance optimization, as this happens during the while (pt) {} loop in setRatio() at the bottom of which it sets pt = pt._next, so if we null it, the linked list will be broken in that loop.
};
/** @private Gives us a way to set a value on the first render (and only the first render). **/
p._addLazySet = function (t, p, v) {
var pt = this._firstPT = new CSSPropTween(t, p, 0, 0, this._firstPT, 2);
pt.e = v;
pt.setRatio = lazySet;
pt.data = this;
};
/** @private **/
p._linkCSSP = function (pt, next, prev, remove) {
if (pt) {
if (next) {
next._prev = pt;
}
if (pt._next) {
pt._next._prev = pt._prev;
}
if (pt._prev) {
pt._prev._next = pt._next;
} else if (this._firstPT === pt) {
this._firstPT = pt._next;
remove = true; //just to prevent resetting this._firstPT 5 lines down in case pt._next is null. (optimized for speed)
}
if (prev) {
prev._next = pt;
} else if (!remove && this._firstPT === null) {
this._firstPT = pt;
}
pt._next = next;
pt._prev = prev;
}
return pt;
};
//we need to make sure that if alpha or autoAlpha is killed, opacity is too. And autoAlpha affects the "visibility" property.
p._kill = function (lookup) {
var copy = lookup,
pt, p, xfirst;
if (lookup.autoAlpha || lookup.alpha) {
copy = {};
for (p in lookup) { //copy the lookup so that we're not changing the original which may be passed elsewhere.
copy[p] = lookup[p];
}
copy.opacity = 1;
if (copy.autoAlpha) {
copy.visibility = 1;
}
}
if (lookup.className && (pt = this._classNamePT)) { //for className tweens, we need to kill any associated CSSPropTweens too; a linked list starts at the className's "xfirst".
xfirst = pt.xfirst;
if (xfirst && xfirst._prev) {
this._linkCSSP(xfirst._prev, pt._next, xfirst._prev._prev); //break off the prev
} else if (xfirst === this._firstPT) {
this._firstPT = pt._next;
}
if (pt._next) {
this._linkCSSP(pt._next, pt._next._next, xfirst._prev);
}
this._classNamePT = null;
}
return TweenPlugin.prototype._kill.call(this, copy);
};
//used by cascadeTo() for gathering all the style properties of each child element into an array for comparison.
var _getChildStyles = function (e, props, targets) {
var children, i, child, type;
if (e.slice) {
i = e.length;
while (--i > -1) {
_getChildStyles(e[i], props, targets);
}
return;
}
children = e.childNodes;
i = children.length;
while (--i > -1) {
child = children[i];
type = child.type;
if (child.style) {
props.push(_getAllStyles(child));
if (targets) {
targets.push(child);
}
}
if ((type === 1 || type === 9 || type === 11) && child.childNodes.length) {
_getChildStyles(child, props, targets);
}
}
};
/**
* Typically only useful for className tweens that may affect child elements, this method creates a TweenLite
* and then compares the style properties of all the target's child elements at the tween's start and end, and
* if any are different, it also creates tweens for those and returns an array containing ALL of the resulting
* tweens (so that you can easily add() them to a TimelineLite, for example). The reason this functionality is
* wrapped into a separate static method of CSSPlugin instead of being integrated into all regular className tweens
* is because it creates entirely new tweens that may have completely different targets than the original tween,
* so if they were all lumped into the original tween instance, it would be inconsistent with the rest of the API
* and it would create other problems. For example:
* - If I create a tween of elementA, that tween instance may suddenly change its target to include 50 other elements (unintuitive if I specifically defined the target I wanted)
* - We can't just create new independent tweens because otherwise, what happens if the original/parent tween is reversed or pause or dropped into a TimelineLite for tight control? You'd expect that tween's behavior to affect all the others.
* - Analyzing every style property of every child before and after the tween is an expensive operation when there are many children, so this behavior shouldn't be imposed on all className tweens by default, especially since it's probably rare that this extra functionality is needed.
*
* @param {Object} target object to be tweened
* @param {number} Duration in seconds (or frames for frames-based tweens)
* @param {Object} Object containing the end values, like {className:"newClass", ease:Linear.easeNone}
* @return {Array} An array of TweenLite instances
*/
CSSPlugin.cascadeTo = function (target, duration, vars) {
var tween = TweenLite.to(target, duration, vars),
results = [tween],
b = [],
e = [],
targets = [],
_reservedProps = TweenLite._internals.reservedProps,
i, difs, p, from;
target = tween._targets || tween.target;
_getChildStyles(target, b, targets);
tween.render(duration, true, true);
_getChildStyles(target, e);
tween.render(0, true, true);
tween._enabled(true);
i = targets.length;
while (--i > -1) {
difs = _cssDif(targets[i], b[i], e[i]);
if (difs.firstMPT) {
difs = difs.difs;
for (p in vars) {
if (_reservedProps[p]) {
difs[p] = vars[p];
}
}
from = {};
for (p in difs) {
from[p] = b[i][p];
}
results.push(TweenLite.fromTo(targets[i], duration, from, difs));
}
}
return results;
};
TweenPlugin.activate([CSSPlugin]);
return CSSPlugin;
}, true);
/*
* ----------------------------------------------------------------
* RoundPropsPlugin
* ----------------------------------------------------------------
*/
(function () {
var RoundPropsPlugin = _gsScope._gsDefine.plugin({
propName: "roundProps",
version: "1.5",
priority: -1,
API: 2,
//called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
init: function (target, value, tween) {
this._tween = tween;
return true;
}
}),
_roundLinkedList = function (node) {
while (node) {
if (!node.f && !node.blob) {
node.r = 1;
}
node = node._next;
}
},
p = RoundPropsPlugin.prototype;
p._onInitAllProps = function () {
var tween = this._tween,
rp = (tween.vars.roundProps.join) ? tween.vars.roundProps : tween.vars.roundProps.split(","),
i = rp.length,
lookup = {},
rpt = tween._propLookup.roundProps,
prop, pt, next;
while (--i > -1) {
lookup[rp[i]] = 1;
}
i = rp.length;
while (--i > -1) {
prop = rp[i];
pt = tween._firstPT;
while (pt) {
next = pt._next; //record here, because it may get removed
if (pt.pg) {
pt.t._roundProps(lookup, true);
} else if (pt.n === prop) {
if (pt.f === 2 && pt.t) { //a blob (text containing multiple numeric values)
_roundLinkedList(pt.t._firstPT);
} else {
this._add(pt.t, prop, pt.s, pt.c);
//remove from linked list
if (next) {
next._prev = pt._prev;
}
if (pt._prev) {
pt._prev._next = next;
} else if (tween._firstPT === pt) {
tween._firstPT = next;
}
pt._next = pt._prev = null;
tween._propLookup[prop] = rpt;
}
}
pt = next;
}
}
return false;
};
p._add = function (target, p, s, c) {
this._addTween(target, p, s, s + c, p, true);
this._overwriteProps.push(p);
};
}());
/*
* ----------------------------------------------------------------
* AttrPlugin
* ----------------------------------------------------------------
*/
(function () {
_gsScope._gsDefine.plugin({
propName: "attr",
API: 2,
version: "0.5.0",
//called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
init: function (target, value, tween) {
var p;
if (typeof (target.setAttribute) !== "function") {
return false;
}
for (p in value) {
this._addTween(target, "setAttribute", target.getAttribute(p) + "", value[p] + "", p, false, p);
this._overwriteProps.push(p);
}
return true;
}
});
}());
/*
* ----------------------------------------------------------------
* DirectionalRotationPlugin
* ----------------------------------------------------------------
*/
_gsScope._gsDefine.plugin({
propName: "directionalRotation",
version: "0.2.1",
API: 2,
//called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.
init: function (target, value, tween) {
if (typeof (value) !== "object") {
value = { rotation: value };
}
this.finals = {};
var cap = (value.useRadians === true) ? Math.PI * 2 : 360,
min = 0.000001,
p, v, start, end, dif, split;
for (p in value) {
if (p !== "useRadians") {
split = (value[p] + "").split("_");
v = split[0];
start = parseFloat((typeof (target[p]) !== "function") ? target[p] : target[((p.indexOf("set") || typeof (target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3))]());
end = this.finals[p] = (typeof (v) === "string" && v.charAt(1) === "=") ? start + parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : Number(v) || 0;
dif = end - start;
if (split.length) {
v = split.join("_");
if (v.indexOf("short") !== -1) {
dif = dif % cap;
if (dif !== dif % (cap / 2)) {
dif = (dif < 0) ? dif + cap : dif - cap;
}
}
if (v.indexOf("_cw") !== -1 && dif < 0) {
dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
} else if (v.indexOf("ccw") !== -1 && dif > 0) {
dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap;
}
}
if (dif > min || dif < -min) {
this._addTween(target, p, start, start + dif, p);
this._overwriteProps.push(p);
}
}
}
return true;
},
//called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.)
set: function (ratio) {
var pt;
if (ratio !== 1) {
this._super.setRatio.call(this, ratio);
} else {
pt = this._firstPT;
while (pt) {
if (pt.f) {
pt.t[pt.p](this.finals[pt.p]);
} else {
pt.t[pt.p] = this.finals[pt.p];
}
pt = pt._next;
}
}
}
})._autoCSS = true;
/*
* ----------------------------------------------------------------
* EasePack
* ----------------------------------------------------------------
*/
_gsScope._gsDefine("easing.Back", ["easing.Ease"], function (Ease) {
var w = (_gsScope.GreenSockGlobals || _gsScope),
gs = w.com.greensock,
_2PI = Math.PI * 2,
_HALF_PI = Math.PI / 2,
_class = gs._class,
_create = function (n, f) {
var C = _class("easing." + n, function () { }, true),
p = C.prototype = new Ease();
p.constructor = C;
p.getRatio = f;
return C;
},
_easeReg = Ease.register || function () { }, //put an empty function in place just as a safety measure in case someone loads an OLD version of TweenLite.js where Ease.register doesn't exist.
_wrap = function (name, EaseOut, EaseIn, EaseInOut, aliases) {
var C = _class("easing." + name, {
easeOut: new EaseOut(),
easeIn: new EaseIn(),
easeInOut: new EaseInOut()
}, true);
_easeReg(C, name);
return C;
},
EasePoint = function (time, value, next) {
this.t = time;
this.v = value;
if (next) {
this.next = next;
next.prev = this;
this.c = next.v - value;
this.gap = next.t - time;
}
},
//Back
_createBack = function (n, f) {
var C = _class("easing." + n, function (overshoot) {
this._p1 = (overshoot || overshoot === 0) ? overshoot : 1.70158;
this._p2 = this._p1 * 1.525;
}, true),
p = C.prototype = new Ease();
p.constructor = C;
p.getRatio = f;
p.config = function (overshoot) {
return new C(overshoot);
};
return C;
},
Back = _wrap("Back",
_createBack("BackOut", function (p) {
return ((p = p - 1) * p * ((this._p1 + 1) * p + this._p1) + 1);
}),
_createBack("BackIn", function (p) {
return p * p * ((this._p1 + 1) * p - this._p1);
}),
_createBack("BackInOut", function (p) {
return ((p *= 2) < 1) ? 0.5 * p * p * ((this._p2 + 1) * p - this._p2) : 0.5 * ((p -= 2) * p * ((this._p2 + 1) * p + this._p2) + 2);
})
),
//SlowMo
SlowMo = _class("easing.SlowMo", function (linearRatio, power, yoyoMode) {
power = (power || power === 0) ? power : 0.7;
if (linearRatio == null) {
linearRatio = 0.7;
} else if (linearRatio > 1) {
linearRatio = 1;
}
this._p = (linearRatio !== 1) ? power : 0;
this._p1 = (1 - linearRatio) / 2;
this._p2 = linearRatio;
this._p3 = this._p1 + this._p2;
this._calcEnd = (yoyoMode === true);
}, true),
p = SlowMo.prototype = new Ease(),
SteppedEase, RoughEase, _createElastic;
p.constructor = SlowMo;
p.getRatio = function (p) {
var r = p + (0.5 - p) * this._p;
if (p < this._p1) {
return this._calcEnd ? 1 - ((p = 1 - (p / this._p1)) * p) : r - ((p = 1 - (p / this._p1)) * p * p * p * r);
} else if (p > this._p3) {
return this._calcEnd ? 1 - (p = (p - this._p3) / this._p1) * p : r + ((p - r) * (p = (p - this._p3) / this._p1) * p * p * p);
}
return this._calcEnd ? 1 : r;
};
SlowMo.ease = new SlowMo(0.7, 0.7);
p.config = SlowMo.config = function (linearRatio, power, yoyoMode) {
return new SlowMo(linearRatio, power, yoyoMode);
};
//SteppedEase
SteppedEase = _class("easing.SteppedEase", function (steps) {
steps = steps || 1;
this._p1 = 1 / steps;
this._p2 = steps + 1;
}, true);
p = SteppedEase.prototype = new Ease();
p.constructor = SteppedEase;
p.getRatio = function (p) {
if (p < 0) {
p = 0;
} else if (p >= 1) {
p = 0.999999999;
}
return ((this._p2 * p) >> 0) * this._p1;
};
p.config = SteppedEase.config = function (steps) {
return new SteppedEase(steps);
};
//RoughEase
RoughEase = _class("easing.RoughEase", function (vars) {
vars = vars || {};
var taper = vars.taper || "none",
a = [],
cnt = 0,
points = (vars.points || 20) | 0,
i = points,
randomize = (vars.randomize !== false),
clamp = (vars.clamp === true),
template = (vars.template instanceof Ease) ? vars.template : null,
strength = (typeof (vars.strength) === "number") ? vars.strength * 0.4 : 0.4,
x, y, bump, invX, obj, pnt;
while (--i > -1) {
x = randomize ? Math.random() : (1 / points) * i;
y = template ? template.getRatio(x) : x;
if (taper === "none") {
bump = strength;
} else if (taper === "out") {
invX = 1 - x;
bump = invX * invX * strength;
} else if (taper === "in") {
bump = x * x * strength;
} else if (x < 0.5) { //"both" (start)
invX = x * 2;
bump = invX * invX * 0.5 * strength;
} else { //"both" (end)
invX = (1 - x) * 2;
bump = invX * invX * 0.5 * strength;
}
if (randomize) {
y += (Math.random() * bump) - (bump * 0.5);
} else if (i % 2) {
y += bump * 0.5;
} else {
y -= bump * 0.5;
}
if (clamp) {
if (y > 1) {
y = 1;
} else if (y < 0) {
y = 0;
}
}
a[cnt++] = { x: x, y: y };
}
a.sort(function (a, b) {
return a.x - b.x;
});
pnt = new EasePoint(1, 1, null);
i = points;
while (--i > -1) {
obj = a[i];
pnt = new EasePoint(obj.x, obj.y, pnt);
}
this._prev = new EasePoint(0, 0, (pnt.t !== 0) ? pnt : pnt.next);
}, true);
p = RoughEase.prototype = new Ease();
p.constructor = RoughEase;
p.getRatio = function (p) {
var pnt = this._prev;
if (p > pnt.t) {
while (pnt.next && p >= pnt.t) {
pnt = pnt.next;
}
pnt = pnt.prev;
} else {
while (pnt.prev && p <= pnt.t) {
pnt = pnt.prev;
}
}
this._prev = pnt;
return (pnt.v + ((p - pnt.t) / pnt.gap) * pnt.c);
};
p.config = function (vars) {
return new RoughEase(vars);
};
RoughEase.ease = new RoughEase();
//Bounce
_wrap("Bounce",
_create("BounceOut", function (p) {
if (p < 1 / 2.75) {
return 7.5625 * p * p;
} else if (p < 2 / 2.75) {
return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
} else if (p < 2.5 / 2.75) {
return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
}
return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
}),
_create("BounceIn", function (p) {
if ((p = 1 - p) < 1 / 2.75) {
return 1 - (7.5625 * p * p);
} else if (p < 2 / 2.75) {
return 1 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75);
} else if (p < 2.5 / 2.75) {
return 1 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375);
}
return 1 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375);
}),
_create("BounceInOut", function (p) {
var invert = (p < 0.5);
if (invert) {
p = 1 - (p * 2);
} else {
p = (p * 2) - 1;
}
if (p < 1 / 2.75) {
p = 7.5625 * p * p;
} else if (p < 2 / 2.75) {
p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
} else if (p < 2.5 / 2.75) {
p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
} else {
p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
}
return invert ? (1 - p) * 0.5 : p * 0.5 + 0.5;
})
);
//CIRC
_wrap("Circ",
_create("CircOut", function (p) {
return Math.sqrt(1 - (p = p - 1) * p);
}),
_create("CircIn", function (p) {
return -(Math.sqrt(1 - (p * p)) - 1);
}),
_create("CircInOut", function (p) {
return ((p *= 2) < 1) ? -0.5 * (Math.sqrt(1 - p * p) - 1) : 0.5 * (Math.sqrt(1 - (p -= 2) * p) + 1);
})
);
//Elastic
_createElastic = function (n, f, def) {
var C = _class("easing." + n, function (amplitude, period) {
this._p1 = (amplitude >= 1) ? amplitude : 1; //note: if amplitude is < 1, we simply adjust the period for a more natural feel. Otherwise the math doesn't work right and the curve starts at 1.
this._p2 = (period || def) / (amplitude < 1 ? amplitude : 1);
this._p3 = this._p2 / _2PI * (Math.asin(1 / this._p1) || 0);
this._p2 = _2PI / this._p2; //precalculate to optimize
}, true),
p = C.prototype = new Ease();
p.constructor = C;
p.getRatio = f;
p.config = function (amplitude, period) {
return new C(amplitude, period);
};
return C;
};
_wrap("Elastic",
_createElastic("ElasticOut", function (p) {
return this._p1 * Math.pow(2, -10 * p) * Math.sin((p - this._p3) * this._p2) + 1;
}, 0.3),
_createElastic("ElasticIn", function (p) {
return -(this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin((p - this._p3) * this._p2));
}, 0.3),
_createElastic("ElasticInOut", function (p) {
return ((p *= 2) < 1) ? -0.5 * (this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin((p - this._p3) * this._p2)) : this._p1 * Math.pow(2, -10 * (p -= 1)) * Math.sin((p - this._p3) * this._p2) * 0.5 + 1;
}, 0.45)
);
//Expo
_wrap("Expo",
_create("ExpoOut", function (p) {
return 1 - Math.pow(2, -10 * p);
}),
_create("ExpoIn", function (p) {
return Math.pow(2, 10 * (p - 1)) - 0.001;
}),
_create("ExpoInOut", function (p) {
return ((p *= 2) < 1) ? 0.5 * Math.pow(2, 10 * (p - 1)) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
})
);
//Sine
_wrap("Sine",
_create("SineOut", function (p) {
return Math.sin(p * _HALF_PI);
}),
_create("SineIn", function (p) {
return -Math.cos(p * _HALF_PI) + 1;
}),
_create("SineInOut", function (p) {
return -0.5 * (Math.cos(Math.PI * p) - 1);
})
);
_class("easing.EaseLookup", {
find: function (s) {
return Ease.map[s];
}
}, true);
//register the non-standard eases
_easeReg(w.SlowMo, "SlowMo", "ease,");
_easeReg(RoughEase, "RoughEase", "ease,");
_easeReg(SteppedEase, "SteppedEase", "ease,");
return Back;
}, true);
});
if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } //necessary in case TweenLite was already loaded separately.
/*
* ----------------------------------------------------------------
* Base classes like TweenLite, SimpleTimeline, Ease, Ticker, etc.
* ----------------------------------------------------------------
*/
(function (window, moduleName) {
"use strict";
var _globals = window.GreenSockGlobals = window.GreenSockGlobals || window;
if (_globals.TweenLite) {
return; //in case the core set of classes is already loaded, don't instantiate twice.
}
var _namespace = function (ns) {
var a = ns.split("."),
p = _globals, i;
for (i = 0; i < a.length; i++) {
p[a[i]] = p = p[a[i]] || {};
}
return p;
},
gs = _namespace("com.greensock"),
_tinyNum = 0.0000000001,
_slice = function (a) { //don't use Array.prototype.slice.call(target, 0) because that doesn't work in IE8 with a NodeList that's returned by querySelectorAll()
var b = [],
l = a.length,
i;
for (i = 0; i !== l; b.push(a[i++])) { }
return b;
},
_emptyFunc = function () { },
_isArray = (function () { //works around issues in iframe environments where the Array global isn't shared, thus if the object originates in a different window/iframe, "(obj instanceof Array)" will evaluate false. We added some speed optimizations to avoid Object.prototype.toString.call() unless it's absolutely necessary because it's VERY slow (like 20x slower)
var toString = Object.prototype.toString,
array = toString.call([]);
return function (obj) {
return obj != null && (obj instanceof Array || (typeof (obj) === "object" && !!obj.push && toString.call(obj) === array));
};
}()),
a, i, p, _ticker, _tickerActive,
_defLookup = {},
/**
* @constructor
* Defines a GreenSock class, optionally with an array of dependencies that must be instantiated first and passed into the definition.
* This allows users to load GreenSock JS files in any order even if they have interdependencies (like CSSPlugin extends TweenPlugin which is
* inside TweenLite.js, but if CSSPlugin is loaded first, it should wait to run its code until TweenLite.js loads and instantiates TweenPlugin
* and then pass TweenPlugin to CSSPlugin's definition). This is all done automatically and internally.
*
* Every definition will be added to a "com.greensock" global object (typically window, but if a window.GreenSockGlobals object is found,
* it will go there as of v1.7). For example, TweenLite will be found at window.com.greensock.TweenLite and since it's a global class that should be available anywhere,
* it is ALSO referenced at window.TweenLite. However some classes aren't considered global, like the base com.greensock.core.Animation class, so
* those will only be at the package like window.com.greensock.core.Animation. Again, if you define a GreenSockGlobals object on the window, everything
* gets tucked neatly inside there instead of on the window directly. This allows you to do advanced things like load multiple versions of GreenSock
* files and put them into distinct objects (imagine a banner ad uses a newer version but the main site uses an older one). In that case, you could
* sandbox the banner one like:
*
* <script>
* var gs = window.GreenSockGlobals = {}; //the newer version we're about to load could now be referenced in a "gs" object, like gs.TweenLite.to(...). Use whatever alias you want as long as it's unique, "gs" or "banner" or whatever.
* </script>
* <script src="js/greensock/v1.7/TweenMax.js"></script>
* <script>
* window.GreenSockGlobals = window._gsQueue = window._gsDefine = null; //reset it back to null (along with the special _gsQueue variable) so that the next load of TweenMax affects the window and we can reference things directly like TweenLite.to(...)
* </script>
* <script src="js/greensock/v1.6/TweenMax.js"></script>
* <script>
* gs.TweenLite.to(...); //would use v1.7
* TweenLite.to(...); //would use v1.6
* </script>
*
* @param {!string} ns The namespace of the class definition, leaving off "com.greensock." as that's assumed. For example, "TweenLite" or "plugins.CSSPlugin" or "easing.Back".
* @param {!Array.<string>} dependencies An array of dependencies (described as their namespaces minus "com.greensock." prefix). For example ["TweenLite","plugins.TweenPlugin","core.Animation"]
* @param {!function():Object} func The function that should be called and passed the resolved dependencies which will return the actual class for this definition.
* @param {boolean=} global If true, the class will be added to the global scope (typically window unless you define a window.GreenSockGlobals object)
*/
Definition = function (ns, dependencies, func, global) {
this.sc = (_defLookup[ns]) ? _defLookup[ns].sc : []; //subclasses
_defLookup[ns] = this;
this.gsClass = null;
this.func = func;
var _classes = [];
this.check = function (init) {
var i = dependencies.length,
missing = i,
cur, a, n, cl, hasModule;
while (--i > -1) {
if ((cur = _defLookup[dependencies[i]] || new Definition(dependencies[i], [])).gsClass) {
_classes[i] = cur.gsClass;
missing--;
} else if (init) {
cur.sc.push(this);
}
}
if (missing === 0 && func) {
a = ("com.greensock." + ns).split(".");
n = a.pop();
cl = _namespace(a.join("."))[n] = this.gsClass = func.apply(func, _classes);
//exports to multiple environments
if (global) {
_globals[n] = cl; //provides a way to avoid global namespace pollution. By default, the main classes like TweenLite, Power1, Strong, etc. are added to window unless a GreenSockGlobals is defined. So if you want to have things added to a custom object instead, just do something like window.GreenSockGlobals = {} before loading any GreenSock files. You can even set up an alias like window.GreenSockGlobals = windows.gs = {} so that you can access everything like gs.TweenLite. Also remember that ALL classes are added to the window.com.greensock object (in their respective packages, like com.greensock.easing.Power1, com.greensock.TweenLite, etc.)
hasModule = (typeof (module) !== "undefined" && module.exports);
if (!hasModule && typeof (define) === "function" && define.amd) { //AMD
define((window.GreenSockAMDPath ? window.GreenSockAMDPath + "/" : "") + ns.split(".").pop(), [], function () { return cl; });
} else if (ns === moduleName && hasModule) { //node
module.exports = cl;
}
}
for (i = 0; i < this.sc.length; i++) {
this.sc[i].check();
}
}
};
this.check(true);
},
//used to create Definition instances (which basically registers a class that has dependencies).
_gsDefine = window._gsDefine = function (ns, dependencies, func, global) {
return new Definition(ns, dependencies, func, global);
},
//a quick way to create a class that doesn't have any dependencies. Returns the class, but first registers it in the GreenSock namespace so that other classes can grab it (other classes might be dependent on the class).
_class = gs._class = function (ns, func, global) {
func = func || function () { };
_gsDefine(ns, [], function () { return func; }, global);
return func;
};
_gsDefine.globals = _globals;
/*
* ----------------------------------------------------------------
* Ease
* ----------------------------------------------------------------
*/
var _baseParams = [0, 0, 1, 1],
_blankArray = [],
Ease = _class("easing.Ease", function (func, extraParams, type, power) {
this._func = func;
this._type = type || 0;
this._power = power || 0;
this._params = extraParams ? _baseParams.concat(extraParams) : _baseParams;
}, true),
_easeMap = Ease.map = {},
_easeReg = Ease.register = function (ease, names, types, create) {
var na = names.split(","),
i = na.length,
ta = (types || "easeIn,easeOut,easeInOut").split(","),
e, name, j, type;
while (--i > -1) {
name = na[i];
e = create ? _class("easing." + name, null, true) : gs.easing[name] || {};
j = ta.length;
while (--j > -1) {
type = ta[j];
_easeMap[name + "." + type] = _easeMap[type + name] = e[type] = ease.getRatio ? ease : ease[type] || new ease();
}
}
};
p = Ease.prototype;
p._calcEnd = false;
p.getRatio = function (p) {
if (this._func) {
this._params[0] = p;
return this._func.apply(null, this._params);
}
var t = this._type,
pw = this._power,
r = (t === 1) ? 1 - p : (t === 2) ? p : (p < 0.5) ? p * 2 : (1 - p) * 2;
if (pw === 1) {
r *= r;
} else if (pw === 2) {
r *= r * r;
} else if (pw === 3) {
r *= r * r * r;
} else if (pw === 4) {
r *= r * r * r * r;
}
return (t === 1) ? 1 - r : (t === 2) ? r : (p < 0.5) ? r / 2 : 1 - (r / 2);
};
//create all the standard eases like Linear, Quad, Cubic, Quart, Quint, Strong, Power0, Power1, Power2, Power3, and Power4 (each with easeIn, easeOut, and easeInOut)
a = ["Linear", "Quad", "Cubic", "Quart", "Quint,Strong"];
i = a.length;
while (--i > -1) {
p = a[i] + ",Power" + i;
_easeReg(new Ease(null, null, 1, i), p, "easeOut", true);
_easeReg(new Ease(null, null, 2, i), p, "easeIn" + ((i === 0) ? ",easeNone" : ""));
_easeReg(new Ease(null, null, 3, i), p, "easeInOut");
}
_easeMap.linear = gs.easing.Linear.easeIn;
_easeMap.swing = gs.easing.Quad.easeInOut; //for jQuery folks
/*
* ----------------------------------------------------------------
* EventDispatcher
* ----------------------------------------------------------------
*/
var EventDispatcher = _class("events.EventDispatcher", function (target) {
this._listeners = {};
this._eventTarget = target || this;
});
p = EventDispatcher.prototype;
p.addEventListener = function (type, callback, scope, useParam, priority) {
priority = priority || 0;
var list = this._listeners[type],
index = 0,
listener, i;
if (list == null) {
this._listeners[type] = list = [];
}
i = list.length;
while (--i > -1) {
listener = list[i];
if (listener.c === callback && listener.s === scope) {
list.splice(i, 1);
} else if (index === 0 && listener.pr < priority) {
index = i + 1;
}
}
list.splice(index, 0, { c: callback, s: scope, up: useParam, pr: priority });
if (this === _ticker && !_tickerActive) {
_ticker.wake();
}
};
p.removeEventListener = function (type, callback) {
var list = this._listeners[type], i;
if (list) {
i = list.length;
while (--i > -1) {
if (list[i].c === callback) {
list.splice(i, 1);
return;
}
}
}
};
p.dispatchEvent = function (type) {
var list = this._listeners[type],
i, t, listener;
if (list) {
i = list.length;
t = this._eventTarget;
while (--i > -1) {
listener = list[i];
if (listener) {
if (listener.up) {
listener.c.call(listener.s || t, { type: type, target: t });
} else {
listener.c.call(listener.s || t);
}
}
}
}
};
/*
* ----------------------------------------------------------------
* Ticker
* ----------------------------------------------------------------
*/
var _reqAnimFrame = window.requestAnimationFrame,
_cancelAnimFrame = window.cancelAnimationFrame,
_getTime = Date.now || function () { return new Date().getTime(); },
_lastUpdate = _getTime();
//now try to determine the requestAnimationFrame and cancelAnimationFrame functions and if none are found, we'll use a setTimeout()/clearTimeout() polyfill.
a = ["ms", "moz", "webkit", "o"];
i = a.length;
while (--i > -1 && !_reqAnimFrame) {
_reqAnimFrame = window[a[i] + "RequestAnimationFrame"];
_cancelAnimFrame = window[a[i] + "CancelAnimationFrame"] || window[a[i] + "CancelRequestAnimationFrame"];
}
_class("Ticker", function (fps, useRAF) {
var _self = this,
_startTime = _getTime(),
_useRAF = (useRAF !== false && _reqAnimFrame) ? "auto" : false,
_lagThreshold = 500,
_adjustedLag = 33,
_tickWord = "tick", //helps reduce gc burden
_fps, _req, _id, _gap, _nextTime,
_tick = function (manual) {
var elapsed = _getTime() - _lastUpdate,
overlap, dispatch;
if (elapsed > _lagThreshold) {
_startTime += elapsed - _adjustedLag;
}
_lastUpdate += elapsed;
_self.time = (_lastUpdate - _startTime) / 1000;
overlap = _self.time - _nextTime;
if (!_fps || overlap > 0 || manual === true) {
_self.frame++;
_nextTime += overlap + (overlap >= _gap ? 0.004 : _gap - overlap);
dispatch = true;
}
if (manual !== true) { //make sure the request is made before we dispatch the "tick" event so that timing is maintained. Otherwise, if processing the "tick" requires a bunch of time (like 15ms) and we're using a setTimeout() that's based on 16.7ms, it'd technically take 31.7ms between frames otherwise.
_id = _req(_tick);
}
if (dispatch) {
_self.dispatchEvent(_tickWord);
}
};
EventDispatcher.call(_self);
_self.time = _self.frame = 0;
_self.tick = function () {
_tick(true);
};
_self.lagSmoothing = function (threshold, adjustedLag) {
_lagThreshold = threshold || (1 / _tinyNum); //zero should be interpreted as basically unlimited
_adjustedLag = Math.min(adjustedLag, _lagThreshold, 0);
};
_self.sleep = function () {
if (_id == null) {
return;
}
if (!_useRAF || !_cancelAnimFrame) {
clearTimeout(_id);
} else {
_cancelAnimFrame(_id);
}
_req = _emptyFunc;
_id = null;
if (_self === _ticker) {
_tickerActive = false;
}
};
_self.wake = function (seamless) {
if (_id !== null) {
_self.sleep();
} else if (seamless) {
_startTime += -_lastUpdate + (_lastUpdate = _getTime());
} else if (_self.frame > 10) { //don't trigger lagSmoothing if we're just waking up, and make sure that at least 10 frames have elapsed because of the iOS bug that we work around below with the 1.5-second setTimout().
_lastUpdate = _getTime() - _lagThreshold + 5;
}
_req = (_fps === 0) ? _emptyFunc : (!_useRAF || !_reqAnimFrame) ? function (f) { return setTimeout(f, ((_nextTime - _self.time) * 1000 + 1) | 0); } : _reqAnimFrame;
if (_self === _ticker) {
_tickerActive = true;
}
_tick(2);
};
_self.fps = function (value) {
if (!arguments.length) {
return _fps;
}
_fps = value;
_gap = 1 / (_fps || 60);
_nextTime = this.time + _gap;
_self.wake();
};
_self.useRAF = function (value) {
if (!arguments.length) {
return _useRAF;
}
_self.sleep();
_useRAF = value;
_self.fps(_fps);
};
_self.fps(fps);
//a bug in iOS 6 Safari occasionally prevents the requestAnimationFrame from working initially, so we use a 1.5-second timeout that automatically falls back to setTimeout() if it senses this condition.
setTimeout(function () {
if (_useRAF === "auto" && _self.frame < 5 && document.visibilityState !== "hidden") {
_self.useRAF(false);
}
}, 1500);
});
p = gs.Ticker.prototype = new gs.events.EventDispatcher();
p.constructor = gs.Ticker;
/*
* ----------------------------------------------------------------
* Animation
* ----------------------------------------------------------------
*/
var Animation = _class("core.Animation", function (duration, vars) {
this.vars = vars = vars || {};
this._duration = this._totalDuration = duration || 0;
this._delay = Number(vars.delay) || 0;
this._timeScale = 1;
this._active = (vars.immediateRender === true);
this.data = vars.data;
this._reversed = (vars.reversed === true);
if (!_rootTimeline) {
return;
}
if (!_tickerActive) { //some browsers (like iOS 6 Safari) shut down JavaScript execution when the tab is disabled and they [occasionally] neglect to start up requestAnimationFrame again when returning - this code ensures that the engine starts up again properly.
_ticker.wake();
}
var tl = this.vars.useFrames ? _rootFramesTimeline : _rootTimeline;
tl.add(this, tl._time);
if (this.vars.paused) {
this.paused(true);
}
});
_ticker = Animation.ticker = new gs.Ticker();
p = Animation.prototype;
p._dirty = p._gc = p._initted = p._paused = false;
p._totalTime = p._time = 0;
p._rawPrevTime = -1;
p._next = p._last = p._onUpdate = p._timeline = p.timeline = null;
p._paused = false;
//some browsers (like iOS) occasionally drop the requestAnimationFrame event when the user switches to a different tab and then comes back again, so we use a 2-second setTimeout() to sense if/when that condition occurs and then wake() the ticker.
var _checkTimeout = function () {
if (_tickerActive && _getTime() - _lastUpdate > 2000) {
_ticker.wake();
}
setTimeout(_checkTimeout, 2000);
};
_checkTimeout();
p.play = function (from, suppressEvents) {
if (from != null) {
this.seek(from, suppressEvents);
}
return this.reversed(false).paused(false);
};
p.pause = function (atTime, suppressEvents) {
if (atTime != null) {
this.seek(atTime, suppressEvents);
}
return this.paused(true);
};
p.resume = function (from, suppressEvents) {
if (from != null) {
this.seek(from, suppressEvents);
}
return this.paused(false);
};
p.seek = function (time, suppressEvents) {
return this.totalTime(Number(time), suppressEvents !== false);
};
p.restart = function (includeDelay, suppressEvents) {
return this.reversed(false).paused(false).totalTime(includeDelay ? -this._delay : 0, (suppressEvents !== false), true);
};
p.reverse = function (from, suppressEvents) {
if (from != null) {
this.seek((from || this.totalDuration()), suppressEvents);
}
return this.reversed(true).paused(false);
};
p.render = function (time, suppressEvents, force) {
//stub - we override this method in subclasses.
};
p.invalidate = function () {
this._time = this._totalTime = 0;
this._initted = this._gc = false;
this._rawPrevTime = -1;
if (this._gc || !this.timeline) {
this._enabled(true);
}
return this;
};
p.isActive = function () {
var tl = this._timeline, //the 2 root timelines won't have a _timeline; they're always active.
startTime = this._startTime,
rawTime;
return (!tl || (!this._gc && !this._paused && tl.isActive() && (rawTime = tl.rawTime()) >= startTime && rawTime < startTime + this.totalDuration() / this._timeScale));
};
p._enabled = function (enabled, ignoreTimeline) {
if (!_tickerActive) {
_ticker.wake();
}
this._gc = !enabled;
this._active = this.isActive();
if (ignoreTimeline !== true) {
if (enabled && !this.timeline) {
this._timeline.add(this, this._startTime - this._delay);
} else if (!enabled && this.timeline) {
this._timeline._remove(this, true);
}
}
return false;
};
p._kill = function (vars, target) {
return this._enabled(false, false);
};
p.kill = function (vars, target) {
this._kill(vars, target);
return this;
};
p._uncache = function (includeSelf) {
var tween = includeSelf ? this : this.timeline;
while (tween) {
tween._dirty = true;
tween = tween.timeline;
}
return this;
};
p._swapSelfInParams = function (params) {
var i = params.length,
copy = params.concat();
while (--i > -1) {
if (params[i] === "{self}") {
copy[i] = this;
}
}
return copy;
};
p._callback = function (type) {
var v = this.vars;
v[type].apply(v[type + "Scope"] || v.callbackScope || this, v[type + "Params"] || _blankArray);
};
//----Animation getters/setters --------------------------------------------------------
p.eventCallback = function (type, callback, params, scope) {
if ((type || "").substr(0, 2) === "on") {
var v = this.vars;
if (arguments.length === 1) {
return v[type];
}
if (callback == null) {
delete v[type];
} else {
v[type] = callback;
v[type + "Params"] = (_isArray(params) && params.join("").indexOf("{self}") !== -1) ? this._swapSelfInParams(params) : params;
v[type + "Scope"] = scope;
}
if (type === "onUpdate") {
this._onUpdate = callback;
}
}
return this;
};
p.delay = function (value) {
if (!arguments.length) {
return this._delay;
}
if (this._timeline.smoothChildTiming) {
this.startTime(this._startTime + value - this._delay);
}
this._delay = value;
return this;
};
p.duration = function (value) {
if (!arguments.length) {
this._dirty = false;
return this._duration;
}
this._duration = this._totalDuration = value;
this._uncache(true); //true in case it's a TweenMax or TimelineMax that has a repeat - we'll need to refresh the totalDuration.
if (this._timeline.smoothChildTiming) if (this._time > 0) if (this._time < this._duration) if (value !== 0) {
this.totalTime(this._totalTime * (value / this._duration), true);
}
return this;
};
p.totalDuration = function (value) {
this._dirty = false;
return (!arguments.length) ? this._totalDuration : this.duration(value);
};
p.time = function (value, suppressEvents) {
if (!arguments.length) {
return this._time;
}
if (this._dirty) {
this.totalDuration();
}
return this.totalTime((value > this._duration) ? this._duration : value, suppressEvents);
};
p.totalTime = function (time, suppressEvents, uncapped) {
if (!_tickerActive) {
_ticker.wake();
}
if (!arguments.length) {
return this._totalTime;
}
if (this._timeline) {
if (time < 0 && !uncapped) {
time += this.totalDuration();
}
if (this._timeline.smoothChildTiming) {
if (this._dirty) {
this.totalDuration();
}
var totalDuration = this._totalDuration,
tl = this._timeline;
if (time > totalDuration && !uncapped) {
time = totalDuration;
}
this._startTime = (this._paused ? this._pauseTime : tl._time) - ((!this._reversed ? time : totalDuration - time) / this._timeScale);
if (!tl._dirty) { //for performance improvement. If the parent's cache is already dirty, it already took care of marking the ancestors as dirty too, so skip the function call here.
this._uncache(false);
}
//in case any of the ancestor timelines had completed but should now be enabled, we should reset their totalTime() which will also ensure that they're lined up properly and enabled. Skip for animations that are on the root (wasteful). Example: a TimelineLite.exportRoot() is performed when there's a paused tween on the root, the export will not complete until that tween is unpaused, but imagine a child gets restarted later, after all [unpaused] tweens have completed. The startTime of that child would get pushed out, but one of the ancestors may have completed.
if (tl._timeline) {
while (tl._timeline) {
if (tl._timeline._time !== (tl._startTime + tl._totalTime) / tl._timeScale) {
tl.totalTime(tl._totalTime, true);
}
tl = tl._timeline;
}
}
}
if (this._gc) {
this._enabled(true, false);
}
if (this._totalTime !== time || this._duration === 0) {
if (_lazyTweens.length) {
_lazyRender();
}
this.render(time, suppressEvents, false);
if (_lazyTweens.length) { //in case rendering caused any tweens to lazy-init, we should render them because typically when someone calls seek() or time() or progress(), they expect an immediate render.
_lazyRender();
}
}
}
return this;
};
p.progress = p.totalProgress = function (value, suppressEvents) {
var duration = this.duration();
return (!arguments.length) ? (duration ? this._time / duration : this.ratio) : this.totalTime(duration * value, suppressEvents);
};
p.startTime = function (value) {
if (!arguments.length) {
return this._startTime;
}
if (value !== this._startTime) {
this._startTime = value;
if (this.timeline) if (this.timeline._sortChildren) {
this.timeline.add(this, value - this._delay); //ensures that any necessary re-sequencing of Animations in the timeline occurs to make sure the rendering order is correct.
}
}
return this;
};
p.endTime = function (includeRepeats) {
return this._startTime + ((includeRepeats != false) ? this.totalDuration() : this.duration()) / this._timeScale;
};
p.timeScale = function (value) {
if (!arguments.length) {
return this._timeScale;
}
value = value || _tinyNum; //can't allow zero because it'll throw the math off
if (this._timeline && this._timeline.smoothChildTiming) {
var pauseTime = this._pauseTime,
t = (pauseTime || pauseTime === 0) ? pauseTime : this._timeline.totalTime();
this._startTime = t - ((t - this._startTime) * this._timeScale / value);
}
this._timeScale = value;
return this._uncache(false);
};
p.reversed = function (value) {
if (!arguments.length) {
return this._reversed;
}
if (value != this._reversed) {
this._reversed = value;
this.totalTime(((this._timeline && !this._timeline.smoothChildTiming) ? this.totalDuration() - this._totalTime : this._totalTime), true);
}
return this;
};
p.paused = function (value) {
if (!arguments.length) {
return this._paused;
}
var tl = this._timeline,
raw, elapsed;
if (value != this._paused) if (tl) {
if (!_tickerActive && !value) {
_ticker.wake();
}
raw = tl.rawTime();
elapsed = raw - this._pauseTime;
if (!value && tl.smoothChildTiming) {
this._startTime += elapsed;
this._uncache(false);
}
this._pauseTime = value ? raw : null;
this._paused = value;
this._active = this.isActive();
if (!value && elapsed !== 0 && this._initted && this.duration()) {
raw = tl.smoothChildTiming ? this._totalTime : (raw - this._startTime) / this._timeScale;
this.render(raw, (raw === this._totalTime), true); //in case the target's properties changed via some other tween or manual update by the user, we should force a render.
}
}
if (this._gc && !value) {
this._enabled(true, false);
}
return this;
};
/*
* ----------------------------------------------------------------
* SimpleTimeline
* ----------------------------------------------------------------
*/
var SimpleTimeline = _class("core.SimpleTimeline", function (vars) {
Animation.call(this, 0, vars);
this.autoRemoveChildren = this.smoothChildTiming = true;
});
p = SimpleTimeline.prototype = new Animation();
p.constructor = SimpleTimeline;
p.kill()._gc = false;
p._first = p._last = p._recent = null;
p._sortChildren = false;
p.add = p.insert = function (child, position, align, stagger) {
var prevTween, st;
child._startTime = Number(position || 0) + child._delay;
if (child._paused) if (this !== child._timeline) { //we only adjust the _pauseTime if it wasn't in this timeline already. Remember, sometimes a tween will be inserted again into the same timeline when its startTime is changed so that the tweens in the TimelineLite/Max are re-ordered properly in the linked list (so everything renders in the proper order).
child._pauseTime = child._startTime + ((this.rawTime() - child._startTime) / child._timeScale);
}
if (child.timeline) {
child.timeline._remove(child, true); //removes from existing timeline so that it can be properly added to this one.
}
child.timeline = child._timeline = this;
if (child._gc) {
child._enabled(true, true);
}
prevTween = this._last;
if (this._sortChildren) {
st = child._startTime;
while (prevTween && prevTween._startTime > st) {
prevTween = prevTween._prev;
}
}
if (prevTween) {
child._next = prevTween._next;
prevTween._next = child;
} else {
child._next = this._first;
this._first = child;
}
if (child._next) {
child._next._prev = child;
} else {
this._last = child;
}
child._prev = prevTween;
this._recent = child;
if (this._timeline) {
this._uncache(true);
}
return this;
};
p._remove = function (tween, skipDisable) {
if (tween.timeline === this) {
if (!skipDisable) {
tween._enabled(false, true);
}
if (tween._prev) {
tween._prev._next = tween._next;
} else if (this._first === tween) {
this._first = tween._next;
}
if (tween._next) {
tween._next._prev = tween._prev;
} else if (this._last === tween) {
this._last = tween._prev;
}
tween._next = tween._prev = tween.timeline = null;
if (tween === this._recent) {
this._recent = this._last;
}
if (this._timeline) {
this._uncache(true);
}
}
return this;
};
p.render = function (time, suppressEvents, force) {
var tween = this._first,
next;
this._totalTime = this._time = this._rawPrevTime = time;
while (tween) {
next = tween._next; //record it here because the value could change after rendering...
if (tween._active || (time >= tween._startTime && !tween._paused)) {
if (!tween._reversed) {
tween.render((time - tween._startTime) * tween._timeScale, suppressEvents, force);
} else {
tween.render(((!tween._dirty) ? tween._totalDuration : tween.totalDuration()) - ((time - tween._startTime) * tween._timeScale), suppressEvents, force);
}
}
tween = next;
}
};
p.rawTime = function () {
if (!_tickerActive) {
_ticker.wake();
}
return this._totalTime;
};
/*
* ----------------------------------------------------------------
* TweenLite
* ----------------------------------------------------------------
*/
var TweenLite = _class("TweenLite", function (target, duration, vars) {
Animation.call(this, duration, vars);
this.render = TweenLite.prototype.render; //speed optimization (avoid prototype lookup on this "hot" method)
if (target == null) {
throw "Cannot tween a null target.";
}
this.target = target = (typeof (target) !== "string") ? target : TweenLite.selector(target) || target;
var isSelector = (target.jquery || (target.length && target !== window && target[0] && (target[0] === window || (target[0].nodeType && target[0].style && !target.nodeType)))),
overwrite = this.vars.overwrite,
i, targ, targets;
this._overwrite = overwrite = (overwrite == null) ? _overwriteLookup[TweenLite.defaultOverwrite] : (typeof (overwrite) === "number") ? overwrite >> 0 : _overwriteLookup[overwrite];
if ((isSelector || target instanceof Array || (target.push && _isArray(target))) && typeof (target[0]) !== "number") {
this._targets = targets = _slice(target); //don't use Array.prototype.slice.call(target, 0) because that doesn't work in IE8 with a NodeList that's returned by querySelectorAll()
this._propLookup = [];
this._siblings = [];
for (i = 0; i < targets.length; i++) {
targ = targets[i];
if (!targ) {
targets.splice(i--, 1);
continue;
} else if (typeof (targ) === "string") {
targ = targets[i--] = TweenLite.selector(targ); //in case it's an array of strings
if (typeof (targ) === "string") {
targets.splice(i + 1, 1); //to avoid an endless loop (can't imagine why the selector would return a string, but just in case)
}
continue;
} else if (targ.length && targ !== window && targ[0] && (targ[0] === window || (targ[0].nodeType && targ[0].style && !targ.nodeType))) { //in case the user is passing in an array of selector objects (like jQuery objects), we need to check one more level and pull things out if necessary. Also note that <select> elements pass all the criteria regarding length and the first child having style, so we must also check to ensure the target isn't an HTML node itself.
targets.splice(i--, 1);
this._targets = targets = targets.concat(_slice(targ));
continue;
}
this._siblings[i] = _register(targ, this, false);
if (overwrite === 1) if (this._siblings[i].length > 1) {
_applyOverwrite(targ, this, null, 1, this._siblings[i]);
}
}
} else {
this._propLookup = {};
this._siblings = _register(target, this, false);
if (overwrite === 1) if (this._siblings.length > 1) {
_applyOverwrite(target, this, null, 1, this._siblings);
}
}
if (this.vars.immediateRender || (duration === 0 && this._delay === 0 && this.vars.immediateRender !== false)) {
this._time = -_tinyNum; //forces a render without having to set the render() "force" parameter to true because we want to allow lazying by default (using the "force" parameter always forces an immediate full render)
this.render(Math.min(0, -this._delay)); //in case delay is negative
}
}, true),
_isSelector = function (v) {
return (v && v.length && v !== window && v[0] && (v[0] === window || (v[0].nodeType && v[0].style && !v.nodeType))); //we cannot check "nodeType" if the target is window from within an iframe, otherwise it will trigger a security error in some browsers like Firefox.
},
_autoCSS = function (vars, target) {
var css = {},
p;
for (p in vars) {
if (!_reservedProps[p] && (!(p in target) || p === "transform" || p === "x" || p === "y" || p === "width" || p === "height" || p === "className" || p === "border") && (!_plugins[p] || (_plugins[p] && _plugins[p]._autoCSS))) { //note: <img> elements contain read-only "x" and "y" properties. We should also prioritize editing css width/height rather than the element's properties.
css[p] = vars[p];
delete vars[p];
}
}
vars.css = css;
};
p = TweenLite.prototype = new Animation();
p.constructor = TweenLite;
p.kill()._gc = false;
//----TweenLite defaults, overwrite management, and root updates ----------------------------------------------------
p.ratio = 0;
p._firstPT = p._targets = p._overwrittenProps = p._startAt = null;
p._notifyPluginsOfEnabled = p._lazy = false;
TweenLite.version = "1.18.4";
TweenLite.defaultEase = p._ease = new Ease(null, null, 1, 1);
TweenLite.defaultOverwrite = "auto";
TweenLite.ticker = _ticker;
TweenLite.autoSleep = 120;
TweenLite.lagSmoothing = function (threshold, adjustedLag) {
_ticker.lagSmoothing(threshold, adjustedLag);
};
TweenLite.selector = window.$ || window.jQuery || function (e) {
var selector = window.$ || window.jQuery;
if (selector) {
TweenLite.selector = selector;
return selector(e);
}
return (typeof (document) === "undefined") ? e : (document.querySelectorAll ? document.querySelectorAll(e) : document.getElementById((e.charAt(0) === "#") ? e.substr(1) : e));
};
var _lazyTweens = [],
_lazyLookup = {},
_numbersExp = /(?:(-|-=|\+=)?\d*\.?\d*(?:e[\-+]?\d+)?)[0-9]/ig,
//_nonNumbersExp = /(?:([\-+](?!(\d|=)))|[^\d\-+=e]|(e(?![\-+][\d])))+/ig,
_setRatio = function (v) {
var pt = this._firstPT,
min = 0.000001,
val;
while (pt) {
val = !pt.blob ? pt.c * v + pt.s : v ? this.join("") : this.start;
if (pt.r) {
val = Math.round(val);
} else if (val < min) if (val > -min) { //prevents issues with converting very small numbers to strings in the browser
val = 0;
}
if (!pt.f) {
pt.t[pt.p] = val;
} else if (pt.fp) {
pt.t[pt.p](pt.fp, val);
} else {
pt.t[pt.p](val);
}
pt = pt._next;
}
},
//compares two strings (start/end), finds the numbers that are different and spits back an array representing the whole value but with the changing values isolated as elements. For example, "rgb(0,0,0)" and "rgb(100,50,0)" would become ["rgb(", 0, ",", 50, ",0)"]. Notice it merges the parts that are identical (performance optimization). The array also has a linked list of PropTweens attached starting with _firstPT that contain the tweening data (t, p, s, c, f, etc.). It also stores the starting value as a "start" property so that we can revert to it if/when necessary, like when a tween rewinds fully. If the quantity of numbers differs between the start and end, it will always prioritize the end value(s). The pt parameter is optional - it's for a PropTween that will be appended to the end of the linked list and is typically for actually setting the value after all of the elements have been updated (with array.join("")).
_blobDif = function (start, end, filter, pt) {
var a = [start, end],
charIndex = 0,
s = "",
color = 0,
startNums, endNums, num, i, l, nonNumbers, currentNum;
a.start = start;
if (filter) {
filter(a); //pass an array with the starting and ending values and let the filter do whatever it needs to the values.
start = a[0];
end = a[1];
}
a.length = 0;
startNums = start.match(_numbersExp) || [];
endNums = end.match(_numbersExp) || [];
if (pt) {
pt._next = null;
pt.blob = 1;
a._firstPT = pt; //apply last in the linked list (which means inserting it first)
}
l = endNums.length;
for (i = 0; i < l; i++) {
currentNum = endNums[i];
nonNumbers = end.substr(charIndex, end.indexOf(currentNum, charIndex) - charIndex);
s += (nonNumbers || !i) ? nonNumbers : ","; //note: SVG spec allows omission of comma/space when a negative sign is wedged between two numbers, like 2.5-5.3 instead of 2.5,-5.3 but when tweening, the negative value may switch to positive, so we insert the comma just in case.
charIndex += nonNumbers.length;
if (color) { //sense rgba() values and round them.
color = (color + 1) % 5;
} else if (nonNumbers.substr(-5) === "rgba(") {
color = 1;
}
if (currentNum === startNums[i] || startNums.length <= i) {
s += currentNum;
} else {
if (s) {
a.push(s);
s = "";
}
num = parseFloat(startNums[i]);
a.push(num);
a._firstPT = { _next: a._firstPT, t: a, p: a.length - 1, s: num, c: ((currentNum.charAt(1) === "=") ? parseInt(currentNum.charAt(0) + "1", 10) * parseFloat(currentNum.substr(2)) : (parseFloat(currentNum) - num)) || 0, f: 0, r: (color && color < 4) };
//note: we don't set _prev because we'll never need to remove individual PropTweens from this list.
}
charIndex += currentNum.length;
}
s += end.substr(charIndex);
if (s) {
a.push(s);
}
a.setRatio = _setRatio;
return a;
},
//note: "funcParam" is only necessary for function-based getters/setters that require an extra parameter like getAttribute("width") and setAttribute("width", value). In this example, funcParam would be "width". Used by AttrPlugin for example.
_addPropTween = function (target, prop, start, end, overwriteProp, round, funcParam, stringFilter) {
var s = (start === "get") ? target[prop] : start,
type = typeof (target[prop]),
isRelative = (typeof (end) === "string" && end.charAt(1) === "="),
pt = { t: target, p: prop, s: s, f: (type === "function"), pg: 0, n: overwriteProp || prop, r: round, pr: 0, c: isRelative ? parseInt(end.charAt(0) + "1", 10) * parseFloat(end.substr(2)) : (parseFloat(end) - s) || 0 },
blob, getterName;
if (type !== "number") {
if (type === "function" && start === "get") {
getterName = ((prop.indexOf("set") || typeof (target["get" + prop.substr(3)]) !== "function") ? prop : "get" + prop.substr(3));
pt.s = s = funcParam ? target[getterName](funcParam) : target[getterName]();
}
if (typeof (s) === "string" && (funcParam || isNaN(s))) {
//a blob (string that has multiple numbers in it)
pt.fp = funcParam;
blob = _blobDif(s, end, stringFilter || TweenLite.defaultStringFilter, pt);
pt = { t: blob, p: "setRatio", s: 0, c: 1, f: 2, pg: 0, n: overwriteProp || prop, pr: 0 }; //"2" indicates it's a Blob property tween. Needed for RoundPropsPlugin for example.
} else if (!isRelative) {
pt.s = parseFloat(s);
pt.c = (parseFloat(end) - pt.s) || 0;
}
}
if (pt.c) { //only add it to the linked list if there's a change.
if ((pt._next = this._firstPT)) {
pt._next._prev = pt;
}
this._firstPT = pt;
return pt;
}
},
_internals = TweenLite._internals = { isArray: _isArray, isSelector: _isSelector, lazyTweens: _lazyTweens, blobDif: _blobDif }, //gives us a way to expose certain private values to other GreenSock classes without contaminating tha main TweenLite object.
_plugins = TweenLite._plugins = {},
_tweenLookup = _internals.tweenLookup = {},
_tweenLookupNum = 0,
_reservedProps = _internals.reservedProps = { ease: 1, delay: 1, overwrite: 1, onComplete: 1, onCompleteParams: 1, onCompleteScope: 1, useFrames: 1, runBackwards: 1, startAt: 1, onUpdate: 1, onUpdateParams: 1, onUpdateScope: 1, onStart: 1, onStartParams: 1, onStartScope: 1, onReverseComplete: 1, onReverseCompleteParams: 1, onReverseCompleteScope: 1, onRepeat: 1, onRepeatParams: 1, onRepeatScope: 1, easeParams: 1, yoyo: 1, immediateRender: 1, repeat: 1, repeatDelay: 1, data: 1, paused: 1, reversed: 1, autoCSS: 1, lazy: 1, onOverwrite: 1, callbackScope: 1, stringFilter: 1 },
_overwriteLookup = { none: 0, all: 1, auto: 2, concurrent: 3, allOnStart: 4, preexisting: 5, "true": 1, "false": 0 },
_rootFramesTimeline = Animation._rootFramesTimeline = new SimpleTimeline(),
_rootTimeline = Animation._rootTimeline = new SimpleTimeline(),
_nextGCFrame = 30,
_lazyRender = _internals.lazyRender = function () {
var i = _lazyTweens.length,
tween;
_lazyLookup = {};
while (--i > -1) {
tween = _lazyTweens[i];
if (tween && tween._lazy !== false) {
tween.render(tween._lazy[0], tween._lazy[1], true);
tween._lazy = false;
}
}
_lazyTweens.length = 0;
};
_rootTimeline._startTime = _ticker.time;
_rootFramesTimeline._startTime = _ticker.frame;
_rootTimeline._active = _rootFramesTimeline._active = true;
setTimeout(_lazyRender, 1); //on some mobile devices, there isn't a "tick" before code runs which means any lazy renders wouldn't run before the next official "tick".
Animation._updateRoot = TweenLite.render = function () {
var i, a, p;
if (_lazyTweens.length) { //if code is run outside of the requestAnimationFrame loop, there may be tweens queued AFTER the engine refreshed, so we need to ensure any pending renders occur before we refresh again.
_lazyRender();
}
_rootTimeline.render((_ticker.time - _rootTimeline._startTime) * _rootTimeline._timeScale, false, false);
_rootFramesTimeline.render((_ticker.frame - _rootFramesTimeline._startTime) * _rootFramesTimeline._timeScale, false, false);
if (_lazyTweens.length) {
_lazyRender();
}
if (_ticker.frame >= _nextGCFrame) { //dump garbage every 120 frames or whatever the user sets TweenLite.autoSleep to
_nextGCFrame = _ticker.frame + (parseInt(TweenLite.autoSleep, 10) || 120);
for (p in _tweenLookup) {
a = _tweenLookup[p].tweens;
i = a.length;
while (--i > -1) {
if (a[i]._gc) {
a.splice(i, 1);
}
}
if (a.length === 0) {
delete _tweenLookup[p];
}
}
//if there are no more tweens in the root timelines, or if they're all paused, make the _timer sleep to reduce load on the CPU slightly
p = _rootTimeline._first;
if (!p || p._paused) if (TweenLite.autoSleep && !_rootFramesTimeline._first && _ticker._listeners.tick.length === 1) {
while (p && p._paused) {
p = p._next;
}
if (!p) {
_ticker.sleep();
}
}
}
};
_ticker.addEventListener("tick", Animation._updateRoot);
var _register = function (target, tween, scrub) {
var id = target._gsTweenID, a, i;
if (!_tweenLookup[id || (target._gsTweenID = id = "t" + (_tweenLookupNum++))]) {
_tweenLookup[id] = { target: target, tweens: [] };
}
if (tween) {
a = _tweenLookup[id].tweens;
a[(i = a.length)] = tween;
if (scrub) {
while (--i > -1) {
if (a[i] === tween) {
a.splice(i, 1);
}
}
}
}
return _tweenLookup[id].tweens;
},
_onOverwrite = function (overwrittenTween, overwritingTween, target, killedProps) {
var func = overwrittenTween.vars.onOverwrite, r1, r2;
if (func) {
r1 = func(overwrittenTween, overwritingTween, target, killedProps);
}
func = TweenLite.onOverwrite;
if (func) {
r2 = func(overwrittenTween, overwritingTween, target, killedProps);
}
return (r1 !== false && r2 !== false);
},
_applyOverwrite = function (target, tween, props, mode, siblings) {
var i, changed, curTween, l;
if (mode === 1 || mode >= 4) {
l = siblings.length;
for (i = 0; i < l; i++) {
if ((curTween = siblings[i]) !== tween) {
if (!curTween._gc) {
if (curTween._kill(null, target, tween)) {
changed = true;
}
}
} else if (mode === 5) {
break;
}
}
return changed;
}
//NOTE: Add 0.0000000001 to overcome floating point errors that can cause the startTime to be VERY slightly off (when a tween's time() is set for example)
var startTime = tween._startTime + _tinyNum,
overlaps = [],
oCount = 0,
zeroDur = (tween._duration === 0),
globalStart;
i = siblings.length;
while (--i > -1) {
if ((curTween = siblings[i]) === tween || curTween._gc || curTween._paused) {
//ignore
} else if (curTween._timeline !== tween._timeline) {
globalStart = globalStart || _checkOverlap(tween, 0, zeroDur);
if (_checkOverlap(curTween, globalStart, zeroDur) === 0) {
overlaps[oCount++] = curTween;
}
} else if (curTween._startTime <= startTime) if (curTween._startTime + curTween.totalDuration() / curTween._timeScale > startTime) if (!((zeroDur || !curTween._initted) && startTime - curTween._startTime <= 0.0000000002)) {
overlaps[oCount++] = curTween;
}
}
i = oCount;
while (--i > -1) {
curTween = overlaps[i];
if (mode === 2) if (curTween._kill(props, target, tween)) {
changed = true;
}
if (mode !== 2 || (!curTween._firstPT && curTween._initted)) {
if (mode !== 2 && !_onOverwrite(curTween, tween)) {
continue;
}
if (curTween._enabled(false, false)) { //if all property tweens have been overwritten, kill the tween.
changed = true;
}
}
}
return changed;
},
_checkOverlap = function (tween, reference, zeroDur) {
var tl = tween._timeline,
ts = tl._timeScale,
t = tween._startTime;
while (tl._timeline) {
t += tl._startTime;
ts *= tl._timeScale;
if (tl._paused) {
return -100;
}
tl = tl._timeline;
}
t /= ts;
return (t > reference) ? t - reference : ((zeroDur && t === reference) || (!tween._initted && t - reference < 2 * _tinyNum)) ? _tinyNum : ((t += tween.totalDuration() / tween._timeScale / ts) > reference + _tinyNum) ? 0 : t - reference - _tinyNum;
};
//---- TweenLite instance methods -----------------------------------------------------------------------------
p._init = function () {
var v = this.vars,
op = this._overwrittenProps,
dur = this._duration,
immediate = !!v.immediateRender,
ease = v.ease,
i, initPlugins, pt, p, startVars;
if (v.startAt) {
if (this._startAt) {
this._startAt.render(-1, true); //if we've run a startAt previously (when the tween instantiated), we should revert it so that the values re-instantiate correctly particularly for relative tweens. Without this, a TweenLite.fromTo(obj, 1, {x:"+=100"}, {x:"-=100"}), for example, would actually jump to +=200 because the startAt would run twice, doubling the relative change.
this._startAt.kill();
}
startVars = {};
for (p in v.startAt) { //copy the properties/values into a new object to avoid collisions, like var to = {x:0}, from = {x:500}; timeline.fromTo(e, 1, from, to).fromTo(e, 1, to, from);
startVars[p] = v.startAt[p];
}
startVars.overwrite = false;
startVars.immediateRender = true;
startVars.lazy = (immediate && v.lazy !== false);
startVars.startAt = startVars.delay = null; //no nesting of startAt objects allowed (otherwise it could cause an infinite loop).
this._startAt = TweenLite.to(this.target, 0, startVars);
if (immediate) {
if (this._time > 0) {
this._startAt = null; //tweens that render immediately (like most from() and fromTo() tweens) shouldn't revert when their parent timeline's playhead goes backward past the startTime because the initial render could have happened anytime and it shouldn't be directly correlated to this tween's startTime. Imagine setting up a complex animation where the beginning states of various objects are rendered immediately but the tween doesn't happen for quite some time - if we revert to the starting values as soon as the playhead goes backward past the tween's startTime, it will throw things off visually. Reversion should only happen in TimelineLite/Max instances where immediateRender was false (which is the default in the convenience methods like from()).
} else if (dur !== 0) {
return; //we skip initialization here so that overwriting doesn't occur until the tween actually begins. Otherwise, if you create several immediateRender:true tweens of the same target/properties to drop into a TimelineLite or TimelineMax, the last one created would overwrite the first ones because they didn't get placed into the timeline yet before the first render occurs and kicks in overwriting.
}
}
} else if (v.runBackwards && dur !== 0) {
//from() tweens must be handled uniquely: their beginning values must be rendered but we don't want overwriting to occur yet (when time is still 0). Wait until the tween actually begins before doing all the routines like overwriting. At that time, we should render at the END of the tween to ensure that things initialize correctly (remember, from() tweens go backwards)
if (this._startAt) {
this._startAt.render(-1, true);
this._startAt.kill();
this._startAt = null;
} else {
if (this._time !== 0) { //in rare cases (like if a from() tween runs and then is invalidate()-ed), immediateRender could be true but the initial forced-render gets skipped, so there's no need to force the render in this context when the _time is greater than 0
immediate = false;
}
pt = {};
for (p in v) { //copy props into a new object and skip any reserved props, otherwise onComplete or onUpdate or onStart could fire. We should, however, permit autoCSS to go through.
if (!_reservedProps[p] || p === "autoCSS") {
pt[p] = v[p];
}
}
pt.overwrite = 0;
pt.data = "isFromStart"; //we tag the tween with as "isFromStart" so that if [inside a plugin] we need to only do something at the very END of a tween, we have a way of identifying this tween as merely the one that's setting the beginning values for a "from()" tween. For example, clearProps in CSSPlugin should only get applied at the very END of a tween and without this tag, from(...{height:100, clearProps:"height", delay:1}) would wipe the height at the beginning of the tween and after 1 second, it'd kick back in.
pt.lazy = (immediate && v.lazy !== false);
pt.immediateRender = immediate; //zero-duration tweens render immediately by default, but if we're not specifically instructed to render this tween immediately, we should skip this and merely _init() to record the starting values (rendering them immediately would push them to completion which is wasteful in that case - we'd have to render(-1) immediately after)
this._startAt = TweenLite.to(this.target, 0, pt);
if (!immediate) {
this._startAt._init(); //ensures that the initial values are recorded
this._startAt._enabled(false); //no need to have the tween render on the next cycle. Disable it because we'll always manually control the renders of the _startAt tween.
if (this.vars.immediateRender) {
this._startAt = null;
}
} else if (this._time === 0) {
return;
}
}
}
this._ease = ease = (!ease) ? TweenLite.defaultEase : (ease instanceof Ease) ? ease : (typeof (ease) === "function") ? new Ease(ease, v.easeParams) : _easeMap[ease] || TweenLite.defaultEase;
if (v.easeParams instanceof Array && ease.config) {
this._ease = ease.config.apply(ease, v.easeParams);
}
this._easeType = this._ease._type;
this._easePower = this._ease._power;
this._firstPT = null;
if (this._targets) {
i = this._targets.length;
while (--i > -1) {
if (this._initProps(this._targets[i], (this._propLookup[i] = {}), this._siblings[i], (op ? op[i] : null))) {
initPlugins = true;
}
}
} else {
initPlugins = this._initProps(this.target, this._propLookup, this._siblings, op);
}
if (initPlugins) {
TweenLite._onPluginEvent("_onInitAllProps", this); //reorders the array in order of priority. Uses a static TweenPlugin method in order to minimize file size in TweenLite
}
if (op) if (!this._firstPT) if (typeof (this.target) !== "function") { //if all tweening properties have been overwritten, kill the tween. If the target is a function, it's probably a delayedCall so let it live.
this._enabled(false, false);
}
if (v.runBackwards) {
pt = this._firstPT;
while (pt) {
pt.s += pt.c;
pt.c = -pt.c;
pt = pt._next;
}
}
this._onUpdate = v.onUpdate;
this._initted = true;
};
p._initProps = function (target, propLookup, siblings, overwrittenProps) {
var p, i, initPlugins, plugin, pt, v;
if (target == null) {
return false;
}
if (_lazyLookup[target._gsTweenID]) {
_lazyRender(); //if other tweens of the same target have recently initted but haven't rendered yet, we've got to force the render so that the starting values are correct (imagine populating a timeline with a bunch of sequential tweens and then jumping to the end)
}
if (!this.vars.css) if (target.style) if (target !== window && target.nodeType) if (_plugins.css) if (this.vars.autoCSS !== false) { //it's so common to use TweenLite/Max to animate the css of DOM elements, we assume that if the target is a DOM element, that's what is intended (a convenience so that users don't have to wrap things in css:{}, although we still recommend it for a slight performance boost and better specificity). Note: we cannot check "nodeType" on the window inside an iframe.
_autoCSS(this.vars, target);
}
for (p in this.vars) {
v = this.vars[p];
if (_reservedProps[p]) {
if (v) if ((v instanceof Array) || (v.push && _isArray(v))) if (v.join("").indexOf("{self}") !== -1) {
this.vars[p] = v = this._swapSelfInParams(v, this);
}
} else if (_plugins[p] && (plugin = new _plugins[p]())._onInitTween(target, this.vars[p], this)) {
//t - target [object]
//p - property [string]
//s - start [number]
//c - change [number]
//f - isFunction [boolean]
//n - name [string]
//pg - isPlugin [boolean]
//pr - priority [number]
this._firstPT = pt = { _next: this._firstPT, t: plugin, p: "setRatio", s: 0, c: 1, f: 1, n: p, pg: 1, pr: plugin._priority };
i = plugin._overwriteProps.length;
while (--i > -1) {
propLookup[plugin._overwriteProps[i]] = this._firstPT;
}
if (plugin._priority || plugin._onInitAllProps) {
initPlugins = true;
}
if (plugin._onDisable || plugin._onEnable) {
this._notifyPluginsOfEnabled = true;
}
if (pt._next) {
pt._next._prev = pt;
}
} else {
propLookup[p] = _addPropTween.call(this, target, p, "get", v, p, 0, null, this.vars.stringFilter);
}
}
if (overwrittenProps) if (this._kill(overwrittenProps, target)) { //another tween may have tried to overwrite properties of this tween before init() was called (like if two tweens start at the same time, the one created second will run first)
return this._initProps(target, propLookup, siblings, overwrittenProps);
}
if (this._overwrite > 1) if (this._firstPT) if (siblings.length > 1) if (_applyOverwrite(target, this, propLookup, this._overwrite, siblings)) {
this._kill(propLookup, target);
return this._initProps(target, propLookup, siblings, overwrittenProps);
}
if (this._firstPT) if ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration)) { //zero duration tweens don't lazy render by default; everything else does.
_lazyLookup[target._gsTweenID] = true;
}
return initPlugins;
};
p.render = function (time, suppressEvents, force) {
var prevTime = this._time,
duration = this._duration,
prevRawPrevTime = this._rawPrevTime,
isComplete, callback, pt, rawPrevTime;
if (time >= duration - 0.0000001) { //to work around occasional floating point math artifacts.
this._totalTime = this._time = duration;
this.ratio = this._ease._calcEnd ? this._ease.getRatio(1) : 1;
if (!this._reversed) {
isComplete = true;
callback = "onComplete";
force = (force || this._timeline.autoRemoveChildren); //otherwise, if the animation is unpaused/activated after it's already finished, it doesn't get removed from the parent timeline.
}
if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
if (this._startTime === this._timeline._duration) { //if a zero-duration tween is at the VERY end of a timeline and that timeline renders at its end, it will typically add a tiny bit of cushion to the render time to prevent rounding errors from getting in the way of tweens rendering their VERY end. If we then reverse() that timeline, the zero-duration tween will trigger its onReverseComplete even though technically the playhead didn't pass over it again. It's a very specific edge case we must accommodate.
time = 0;
}
if (prevRawPrevTime < 0 || (time <= 0 && time >= -0.0000001) || (prevRawPrevTime === _tinyNum && this.data !== "isPause")) if (prevRawPrevTime !== time) { //note: when this.data is "isPause", it's a callback added by addPause() on a timeline that we should not be triggered when LEAVING its exact start time. In other words, tl.addPause(1).play(1) shouldn't pause.
force = true;
if (prevRawPrevTime > _tinyNum) {
callback = "onReverseComplete";
}
}
this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
}
} else if (time < 0.0000001) { //to work around occasional floating point math artifacts, round super small values to 0.
this._totalTime = this._time = 0;
this.ratio = this._ease._calcEnd ? this._ease.getRatio(0) : 0;
if (prevTime !== 0 || (duration === 0 && prevRawPrevTime > 0)) {
callback = "onReverseComplete";
isComplete = this._reversed;
}
if (time < 0) {
this._active = false;
if (duration === 0) if (this._initted || !this.vars.lazy || force) { //zero-duration tweens are tricky because we must discern the momentum/direction of time in order to determine whether the starting values should be rendered or the ending values. If the "playhead" of its timeline goes past the zero-duration tween in the forward direction or lands directly on it, the end values should be rendered, but if the timeline's "playhead" moves past it in the backward direction (from a postitive time to a negative time), the starting values must be rendered.
if (prevRawPrevTime >= 0 && !(prevRawPrevTime === _tinyNum && this.data === "isPause")) {
force = true;
}
this._rawPrevTime = rawPrevTime = (!suppressEvents || time || prevRawPrevTime === time) ? time : _tinyNum; //when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect. We set the _rawPrevTime to be a precise tiny number to indicate this scenario rather than using another property/variable which would increase memory usage. This technique is less readable, but more efficient.
}
}
if (!this._initted) { //if we render the very beginning (time == 0) of a fromTo(), we must force the render (normal tweens wouldn't need to render at a time of 0 when the prevTime was also 0). This is also mandatory to make sure overwriting kicks in immediately.
force = true;
}
} else {
this._totalTime = this._time = time;
if (this._easeType) {
var r = time / duration, type = this._easeType, pow = this._easePower;
if (type === 1 || (type === 3 && r >= 0.5)) {
r = 1 - r;
}
if (type === 3) {
r *= 2;
}
if (pow === 1) {
r *= r;
} else if (pow === 2) {
r *= r * r;
} else if (pow === 3) {
r *= r * r * r;
} else if (pow === 4) {
r *= r * r * r * r;
}
if (type === 1) {
this.ratio = 1 - r;
} else if (type === 2) {
this.ratio = r;
} else if (time / duration < 0.5) {
this.ratio = r / 2;
} else {
this.ratio = 1 - (r / 2);
}
} else {
this.ratio = this._ease.getRatio(time / duration);
}
}
if (this._time === prevTime && !force) {
return;
} else if (!this._initted) {
this._init();
if (!this._initted || this._gc) { //immediateRender tweens typically won't initialize until the playhead advances (_time is greater than 0) in order to ensure that overwriting occurs properly. Also, if all of the tweening properties have been overwritten (which would cause _gc to be true, as set in _init()), we shouldn't continue otherwise an onStart callback could be called for example.
return;
} else if (!force && this._firstPT && ((this.vars.lazy !== false && this._duration) || (this.vars.lazy && !this._duration))) {
this._time = this._totalTime = prevTime;
this._rawPrevTime = prevRawPrevTime;
_lazyTweens.push(this);
this._lazy = [time, suppressEvents];
return;
}
//_ease is initially set to defaultEase, so now that init() has run, _ease is set properly and we need to recalculate the ratio. Overall this is faster than using conditional logic earlier in the method to avoid having to set ratio twice because we only init() once but renderTime() gets called VERY frequently.
if (this._time && !isComplete) {
this.ratio = this._ease.getRatio(this._time / duration);
} else if (isComplete && this._ease._calcEnd) {
this.ratio = this._ease.getRatio((this._time === 0) ? 0 : 1);
}
}
if (this._lazy !== false) { //in case a lazy render is pending, we should flush it because the new render is occurring now (imagine a lazy tween instantiating and then immediately the user calls tween.seek(tween.duration()), skipping to the end - the end render would be forced, and then if we didn't flush the lazy render, it'd fire AFTER the seek(), rendering it at the wrong time.
this._lazy = false;
}
if (!this._active) if (!this._paused && this._time !== prevTime && time >= 0) {
this._active = true; //so that if the user renders a tween (as opposed to the timeline rendering it), the timeline is forced to re-render and align it with the proper time/frame on the next rendering cycle. Maybe the tween already finished but the user manually re-renders it as halfway done.
}
if (prevTime === 0) {
if (this._startAt) {
if (time >= 0) {
this._startAt.render(time, suppressEvents, force);
} else if (!callback) {
callback = "_dummyGS"; //if no callback is defined, use a dummy value just so that the condition at the end evaluates as true because _startAt should render AFTER the normal render loop when the time is negative. We could handle this in a more intuitive way, of course, but the render loop is the MOST important thing to optimize, so this technique allows us to avoid adding extra conditional logic in a high-frequency area.
}
}
if (this.vars.onStart) if (this._time !== 0 || duration === 0) if (!suppressEvents) {
this._callback("onStart");
}
}
pt = this._firstPT;
while (pt) {
if (pt.f) {
pt.t[pt.p](pt.c * this.ratio + pt.s);
} else {
pt.t[pt.p] = pt.c * this.ratio + pt.s;
}
pt = pt._next;
}
if (this._onUpdate) {
if (time < 0) if (this._startAt && time !== -0.0001) { //if the tween is positioned at the VERY beginning (_startTime 0) of its parent timeline, it's illegal for the playhead to go back further, so we should not render the recorded startAt values.
this._startAt.render(time, suppressEvents, force); //note: for performance reasons, we tuck this conditional logic inside less traveled areas (most tweens don't have an onUpdate). We'd just have it at the end before the onComplete, but the values should be updated before any onUpdate is called, so we ALSO put it here and then if it's not called, we do so later near the onComplete.
}
if (!suppressEvents) if (this._time !== prevTime || isComplete || force) {
this._callback("onUpdate");
}
}
if (callback) if (!this._gc || force) { //check _gc because there's a chance that kill() could be called in an onUpdate
if (time < 0 && this._startAt && !this._onUpdate && time !== -0.0001) { //-0.0001 is a special value that we use when looping back to the beginning of a repeated TimelineMax, in which case we shouldn't render the _startAt values.
this._startAt.render(time, suppressEvents, force);
}
if (isComplete) {
if (this._timeline.autoRemoveChildren) {
this._enabled(false, false);
}
this._active = false;
}
if (!suppressEvents && this.vars[callback]) {
this._callback(callback);
}
if (duration === 0 && this._rawPrevTime === _tinyNum && rawPrevTime !== _tinyNum) { //the onComplete or onReverseComplete could trigger movement of the playhead and for zero-duration tweens (which must discern direction) that land directly back on their start time, we don't want to fire again on the next render. Think of several addPause()'s in a timeline that forces the playhead to a certain spot, but what if it's already paused and another tween is tweening the "time" of the timeline? Each time it moves [forward] past that spot, it would move back, and since suppressEvents is true, it'd reset _rawPrevTime to _tinyNum so that when it begins again, the callback would fire (so ultimately it could bounce back and forth during that tween). Again, this is a very uncommon scenario, but possible nonetheless.
this._rawPrevTime = 0;
}
}
};
p._kill = function (vars, target, overwritingTween) {
if (vars === "all") {
vars = null;
}
if (vars == null) if (target == null || target === this.target) {
this._lazy = false;
return this._enabled(false, false);
}
target = (typeof (target) !== "string") ? (target || this._targets || this.target) : TweenLite.selector(target) || target;
var simultaneousOverwrite = (overwritingTween && this._time && overwritingTween._startTime === this._startTime && this._timeline === overwritingTween._timeline),
i, overwrittenProps, p, pt, propLookup, changed, killProps, record, killed;
if ((_isArray(target) || _isSelector(target)) && typeof (target[0]) !== "number") {
i = target.length;
while (--i > -1) {
if (this._kill(vars, target[i], overwritingTween)) {
changed = true;
}
}
} else {
if (this._targets) {
i = this._targets.length;
while (--i > -1) {
if (target === this._targets[i]) {
propLookup = this._propLookup[i] || {};
this._overwrittenProps = this._overwrittenProps || [];
overwrittenProps = this._overwrittenProps[i] = vars ? this._overwrittenProps[i] || {} : "all";
break;
}
}
} else if (target !== this.target) {
return false;
} else {
propLookup = this._propLookup;
overwrittenProps = this._overwrittenProps = vars ? this._overwrittenProps || {} : "all";
}
if (propLookup) {
killProps = vars || propLookup;
record = (vars !== overwrittenProps && overwrittenProps !== "all" && vars !== propLookup && (typeof (vars) !== "object" || !vars._tempKill)); //_tempKill is a super-secret way to delete a particular tweening property but NOT have it remembered as an official overwritten property (like in BezierPlugin)
if (overwritingTween && (TweenLite.onOverwrite || this.vars.onOverwrite)) {
for (p in killProps) {
if (propLookup[p]) {
if (!killed) {
killed = [];
}
killed.push(p);
}
}
if ((killed || !vars) && !_onOverwrite(this, overwritingTween, target, killed)) { //if the onOverwrite returned false, that means the user wants to override the overwriting (cancel it).
return false;
}
}
for (p in killProps) {
if ((pt = propLookup[p])) {
if (simultaneousOverwrite) { //if another tween overwrites this one and they both start at exactly the same time, yet this tween has already rendered once (for example, at 0.001) because it's first in the queue, we should revert the values to where they were at 0 so that the starting values aren't contaminated on the overwriting tween.
if (pt.f) {
pt.t[pt.p](pt.s);
} else {
pt.t[pt.p] = pt.s;
}
changed = true;
}
if (pt.pg && pt.t._kill(killProps)) {
changed = true; //some plugins need to be notified so they can perform cleanup tasks first
}
if (!pt.pg || pt.t._overwriteProps.length === 0) {
if (pt._prev) {
pt._prev._next = pt._next;
} else if (pt === this._firstPT) {
this._firstPT = pt._next;
}
if (pt._next) {
pt._next._prev = pt._prev;
}
pt._next = pt._prev = null;
}
delete propLookup[p];
}
if (record) {
overwrittenProps[p] = 1;
}
}
if (!this._firstPT && this._initted) { //if all tweening properties are killed, kill the tween. Without this line, if there's a tween with multiple targets and then you killTweensOf() each target individually, the tween would technically still remain active and fire its onComplete even though there aren't any more properties tweening.
this._enabled(false, false);
}
}
}
return changed;
};
p.invalidate = function () {
if (this._notifyPluginsOfEnabled) {
TweenLite._onPluginEvent("_onDisable", this);
}
this._firstPT = this._overwrittenProps = this._startAt = this._onUpdate = null;
this._notifyPluginsOfEnabled = this._active = this._lazy = false;
this._propLookup = (this._targets) ? {} : [];
Animation.prototype.invalidate.call(this);
if (this.vars.immediateRender) {
this._time = -_tinyNum; //forces a render without having to set the render() "force" parameter to true because we want to allow lazying by default (using the "force" parameter always forces an immediate full render)
this.render(Math.min(0, -this._delay)); //in case delay is negative.
}
return this;
};
p._enabled = function (enabled, ignoreTimeline) {
if (!_tickerActive) {
_ticker.wake();
}
if (enabled && this._gc) {
var targets = this._targets,
i;
if (targets) {
i = targets.length;
while (--i > -1) {
this._siblings[i] = _register(targets[i], this, true);
}
} else {
this._siblings = _register(this.target, this, true);
}
}
Animation.prototype._enabled.call(this, enabled, ignoreTimeline);
if (this._notifyPluginsOfEnabled) if (this._firstPT) {
return TweenLite._onPluginEvent((enabled ? "_onEnable" : "_onDisable"), this);
}
return false;
};
//----TweenLite static methods -----------------------------------------------------
TweenLite.to = function (target, duration, vars) {
return new TweenLite(target, duration, vars);
};
TweenLite.from = function (target, duration, vars) {
vars.runBackwards = true;
vars.immediateRender = (vars.immediateRender != false);
return new TweenLite(target, duration, vars);
};
TweenLite.fromTo = function (target, duration, fromVars, toVars) {
toVars.startAt = fromVars;
toVars.immediateRender = (toVars.immediateRender != false && fromVars.immediateRender != false);
return new TweenLite(target, duration, toVars);
};
TweenLite.delayedCall = function (delay, callback, params, scope, useFrames) {
return new TweenLite(callback, 0, { delay: delay, onComplete: callback, onCompleteParams: params, callbackScope: scope, onReverseComplete: callback, onReverseCompleteParams: params, immediateRender: false, lazy: false, useFrames: useFrames, overwrite: 0 });
};
TweenLite.set = function (target, vars) {
return new TweenLite(target, 0, vars);
};
TweenLite.getTweensOf = function (target, onlyActive) {
if (target == null) { return []; }
target = (typeof (target) !== "string") ? target : TweenLite.selector(target) || target;
var i, a, j, t;
if ((_isArray(target) || _isSelector(target)) && typeof (target[0]) !== "number") {
i = target.length;
a = [];
while (--i > -1) {
a = a.concat(TweenLite.getTweensOf(target[i], onlyActive));
}
i = a.length;
//now get rid of any duplicates (tweens of arrays of objects could cause duplicates)
while (--i > -1) {
t = a[i];
j = i;
while (--j > -1) {
if (t === a[j]) {
a.splice(i, 1);
}
}
}
} else {
a = _register(target).concat();
i = a.length;
while (--i > -1) {
if (a[i]._gc || (onlyActive && !a[i].isActive())) {
a.splice(i, 1);
}
}
}
return a;
};
TweenLite.killTweensOf = TweenLite.killDelayedCallsTo = function (target, onlyActive, vars) {
if (typeof (onlyActive) === "object") {
vars = onlyActive; //for backwards compatibility (before "onlyActive" parameter was inserted)
onlyActive = false;
}
var a = TweenLite.getTweensOf(target, onlyActive),
i = a.length;
while (--i > -1) {
a[i]._kill(vars, target);
}
};
/*
* ----------------------------------------------------------------
* TweenPlugin (could easily be split out as a separate file/class, but included for ease of use (so that people don't need to include another script call before loading plugins which is easy to forget)
* ----------------------------------------------------------------
*/
var TweenPlugin = _class("plugins.TweenPlugin", function (props, priority) {
this._overwriteProps = (props || "").split(",");
this._propName = this._overwriteProps[0];
this._priority = priority || 0;
this._super = TweenPlugin.prototype;
}, true);
p = TweenPlugin.prototype;
TweenPlugin.version = "1.18.0";
TweenPlugin.API = 2;
p._firstPT = null;
p._addTween = _addPropTween;
p.setRatio = _setRatio;
p._kill = function (lookup) {
var a = this._overwriteProps,
pt = this._firstPT,
i;
if (lookup[this._propName] != null) {
this._overwriteProps = [];
} else {
i = a.length;
while (--i > -1) {
if (lookup[a[i]] != null) {
a.splice(i, 1);
}
}
}
while (pt) {
if (lookup[pt.n] != null) {
if (pt._next) {
pt._next._prev = pt._prev;
}
if (pt._prev) {
pt._prev._next = pt._next;
pt._prev = null;
} else if (this._firstPT === pt) {
this._firstPT = pt._next;
}
}
pt = pt._next;
}
return false;
};
p._roundProps = function (lookup, value) {
var pt = this._firstPT;
while (pt) {
if (lookup[this._propName] || (pt.n != null && lookup[pt.n.split(this._propName + "_").join("")])) { //some properties that are very plugin-specific add a prefix named after the _propName plus an underscore, so we need to ignore that extra stuff here.
pt.r = value;
}
pt = pt._next;
}
};
TweenLite._onPluginEvent = function (type, tween) {
var pt = tween._firstPT,
changed, pt2, first, last, next;
if (type === "_onInitAllProps") {
//sorts the PropTween linked list in order of priority because some plugins need to render earlier/later than others, like MotionBlurPlugin applies its effects after all x/y/alpha tweens have rendered on each frame.
while (pt) {
next = pt._next;
pt2 = first;
while (pt2 && pt2.pr > pt.pr) {
pt2 = pt2._next;
}
if ((pt._prev = pt2 ? pt2._prev : last)) {
pt._prev._next = pt;
} else {
first = pt;
}
if ((pt._next = pt2)) {
pt2._prev = pt;
} else {
last = pt;
}
pt = next;
}
pt = tween._firstPT = first;
}
while (pt) {
if (pt.pg) if (typeof (pt.t[type]) === "function") if (pt.t[type]()) {
changed = true;
}
pt = pt._next;
}
return changed;
};
TweenPlugin.activate = function (plugins) {
var i = plugins.length;
while (--i > -1) {
if (plugins[i].API === TweenPlugin.API) {
_plugins[(new plugins[i]())._propName] = plugins[i];
}
}
return true;
};
//provides a more concise way to define plugins that have no dependencies besides TweenPlugin and TweenLite, wrapping common boilerplate stuff into one function (added in 1.9.0). You don't NEED to use this to define a plugin - the old way still works and can be useful in certain (rare) situations.
_gsDefine.plugin = function (config) {
if (!config || !config.propName || !config.init || !config.API) { throw "illegal plugin definition."; }
var propName = config.propName,
priority = config.priority || 0,
overwriteProps = config.overwriteProps,
map = { init: "_onInitTween", set: "setRatio", kill: "_kill", round: "_roundProps", initAll: "_onInitAllProps" },
Plugin = _class("plugins." + propName.charAt(0).toUpperCase() + propName.substr(1) + "Plugin",
function () {
TweenPlugin.call(this, propName, priority);
this._overwriteProps = overwriteProps || [];
}, (config.global === true)),
p = Plugin.prototype = new TweenPlugin(propName),
prop;
p.constructor = Plugin;
Plugin.API = config.API;
for (prop in map) {
if (typeof (config[prop]) === "function") {
p[map[prop]] = config[prop];
}
}
Plugin.version = config.version;
TweenPlugin.activate([Plugin]);
return Plugin;
};
//now run through all the dependencies discovered and if any are missing, log that to the console as a warning. This is why it's best to have TweenLite load last - it can check all the dependencies for you.
a = window._gsQueue;
if (a) {
for (i = 0; i < a.length; i++) {
a[i]();
}
for (p in _defLookup) {
if (!_defLookup[p].func) {
window.console.log("GSAP encountered missing dependency: com.greensock." + p);
}
}
}
_tickerActive = false; //ensures that the first official animation forces a ticker.tick() to update the time when it is instantiated
})((typeof (module) !== "undefined" && module.exports && typeof (global) !== "undefined") ? global : this || window, "TweenMax");
/*
panr - v0.0.1
jQuery plugin for zoom & pan elements on mousemove
by Robert Bue (@robert_bue)
Powered by the Greensock Tweening Platform
http://www.greensock.com
Greensock License info at http://www.greensock.com/licensing/
Dual licensed under MIT and GPL.
*/
; (function ($, window, document, undefined) {
// Create the defaults once
var pluginName = "panr",
defaults = {
sensitivity: 20,
scale: false,
scaleOnHover: true,
scaleTo: 1.1,
scaleDuration: .25,
panY: true,
panX: true,
panDuration: 0,
resetPanOnMouseLeave: false,
onEnter: function () { },
onLeave: function () { }
};
// The actual plugin constructor
function Plugin(element, options) {
this.element = element;
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function () {
var settings = this.settings,
target = $(this.element),
w = target.width(),
h = target.height(),
targetWidth = target.width() - settings.sensitivity,
cx = (w - targetWidth) / targetWidth,
x,
y,
panVars,
xPanVars,
yPanVars,
mouseleaveVars;
if (settings.scale || (!settings.scaleOnHover && settings.scale)) {
TweenMax.set(target.find('img'), { scale: settings.scaleTo });
}
// moveTarget
if (jQuery.type(settings.moveTarget) === "string") {
settings.moveTarget = $(this.element).parent(settings.moveTarget);
}
// If no target provided we'll use the hovered element
if (!settings.moveTarget) {
settings.moveTarget = $(this.element);
}
settings.moveTarget.on('mousemove', function (e) {
x = e.pageX - target.offset().left; // mouse x coordinate relative to the container
y = e.pageY - target.offset().top; // mouse x coordinate relative to the container
if (settings.panX) {
xPanVars = { x: -cx * x };
}
if (settings.panY) {
yPanVars = { y: -cx * y };
}
panVars = $.extend({}, xPanVars, yPanVars);
// Pan element
TweenMax.to(target.find('img'), settings.panDuration, panVars);
});
// On mouseover
settings.moveTarget.on('mouseenter', function (e) {
if (settings.scaleOnHover) {
// Scale up element
TweenMax.to(target.find('img'), settings.scaleDuration, { scale: settings.scaleTo });
}
settings.onEnter(target);
});
if (!settings.scale || (!settings.scaleOnHover && !settings.scale)) {
mouseleaveVars = { scale: 1, x: 0, y: 0 };
} else {
if (settings.resetPanOnMouseLeave) {
mouseleaveVars = { x: 0, y: 0 };
}
}
settings.moveTarget.on('mouseleave', function (e) {
// Reset element
TweenMax.to(target.find('img'), settings.scaleDuration, mouseleaveVars);
settings.onLeave(target);
});
}
};
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName, new Plugin(this, options));
}
});
};
})(jQuery, window, document);
/*
*
* TERMS OF USE - EASING EQUATIONS
*
* Open source under the BSD License.
*
* Copyright © 2001 Robert Penner
* 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.
*
*/
(function( $ ){
$(function() {
if ( $(window).width() <= 1024 || ( ! $('body').hasClass('elementor-editor-active') && 0 === $('.wd-parallax-on-scroll').length && 0 === $('[data-parallax]').length ) ) return;
ParallaxScroll.init();
});
var ParallaxScroll = {
/* PUBLIC VARIABLES */
showLogs: false,
round: 1000,
/* PUBLIC FUNCTIONS */
init: function() {
this._log("init");
if (this._inited) {
this._log("Already Inited");
this._inited = true;
return;
}
this._requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
this._onScroll(true);
},
/* PRIVATE VARIABLES */
_inited: false,
_properties: ['x', 'y', 'z', 'rotateX', 'rotateY', 'rotateZ', 'scaleX', 'scaleY', 'scaleZ', 'scale'],
_requestAnimationFrame:null,
/* PRIVATE FUNCTIONS */
_log: function(message) {
if (this.showLogs) console.log("Parallax Scroll / " + message);
},
_onScroll: function(noSmooth) {
var scroll = $(document).scrollTop();
var windowHeight = $(window).height();
this._log("onScroll " + scroll);
$("[data-parallax], .wd-parallax-on-scroll").each($.proxy(function(index, el) {
var $el = $(el);
var properties = [];
var applyProperties = false;
var style = $el.data("style");
if (style == undefined) {
style = $el.attr("style") || "";
$el.data("style", style);
}
var datas;
if (!$el.hasClass('wd-parallax-on-scroll')) {
datas = [$el.data("parallax")];
} else {
var classes = $el.attr('class').split(' ');
datas = [[]];
for (var index = 0; index < classes.length; index++) {
if (classes[index].indexOf('wd_scroll') >= 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(scrollCurrent<scrollTo) {
if($el.data("sens") == "go") {
currentTime = 1;
$el.data("sens", "back");
}
else {
currentTime++;
}
}
if(noSmooth) currentTime = totalTime;
$el.data("current-time", currentTime);
}
this._properties.map($.proxy(function(prop) {
var defaultProp = 0;
var to = data[prop];
if (to == undefined) return;
if(prop=="scale" || prop=="scaleX" || prop=="scaleY" || prop=="scaleZ" ) {
defaultProp = 1;
}
else {
to = to | 0;
}
var prev = $el.data("_" + prop);
if (prev == undefined) prev = defaultProp;
var next = ((to-defaultProp) * ((scrollCurrent - scrollFrom) / (scrollTo - scrollFrom))) + defaultProp;
var val = prev + (next - prev) / smoothness;
if(easing && currentTime>0 && 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.css('backgroundPosition', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
});
}
$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: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>',
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 += '<a href="' + shareURL + '" target="_blank" ' +
'class="pswp__share--' + shareButtonData.id + '"' +
(shareButtonData.download ? 'download' : '') + '>' +
shareButtonData.label + '</a>';
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($) {
// 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 - Where to stick the response body. Usually a String selector.
// $(container).html(xhr.responseBody)
// (default: current jquery context)
// 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) {
var context = this;
return this.on('click.pjax', selector, function(event) {
var opts = $.extend({}, optionsFor(container, options));
if (!opts.container) {
opts.container = $(this).attr('data-pjax') || context;
}
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')
//
// $(document).on('click', 'a', function(event) {
// var container = $(this).closest('[data-pjax-container]')
// $.pjax.click(event, container)
// })
//
// Returns nothing.
function handleClick(event, container, options) {
options = optionsFor(container, options);
var link = event.currentTarget;
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) {
// var container = $(this).closest('[data-pjax-container]')
// $.pjax.submit(event, 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 - Where to stick the response body.
// $(container).html(xhr.responseBody)
// 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 target = options.target;
var hash = parseURL(options.url).hash;
var _container = findContainerFor(options.container);
var context = options.context = _container[0];
var selector = _container[1];
// 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 ($.isArray(options.data)) {
options.data.push({
name : '_pjax',
value: selector
});
} else {
options.data._pjax = selector;
}
function fire(type, args, props) {
if (!props) {
props = {};
}
props.relatedTarget = 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', selector);
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: selector,
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(options.container, document.activeElement);
// Clear out any focused controls before inserting new page contents.
if (blurFocus) {
try {
document.activeElement.blur();
}
catch (e) {
}
}
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.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: selector,
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, cloneContents(context, selector));
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 selector = cache[0] || state.container;
var container = $(selector), 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, cloneContents(container, selector));
}
var popstateEvent = $.Event('pjax:popstate', {
state : state,
direction: direction
});
container.trigger(popstateEvent);
var options = {
id : state.id,
url : state.url,
container: container,
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;
} 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 = $('<form>', {
method: method === 'GET' ? 'GET' : 'POST',
action: url,
style : 'display:none'
});
if (method !== 'GET' && method !== 'POST') {
form.append($('<input>', {
type : 'hidden',
name : '_method',
value: method.toLowerCase()
}));
}
var data = options.data;
if (typeof data === 'string') {
$.each(data.split('&'), function(index, value) {
var pair = value.split('=');
form.append($('<input>', {
type : 'hidden',
name : pair[0],
value: pair[1]
}));
});
} else if ($.isArray(data)) {
$.each(data, function(index, value) {
form.append($('<input>', {
type : 'hidden',
name : value.name,
value: value.value
}));
});
} else if (typeof data === 'object') {
var key;
for (key in data) {
form.append($('<input>', {
type : 'hidden',
name : key,
value: data[key]
}));
}
}
$(document.body).append(form);
form.submit();
}
// Internal: Abort an XmlHttpRequest if it hasn't been completed,
// also removing its event handlers.
function abortXHR(xhr) {
if (xhr && xhr.readyState < 4) {
xhr.onreadystatechange = $.noop;
xhr.abort();
}
}
// Internal: Generate unique id for state object.
//
// Use a timestamp instead of a counter since ids should still be
// unique across page loads.
//
// Returns Number.
function uniqueId() {
return (new Date).getTime();
}
function cloneContents(container, selector) {
var cloned = container.clone();
// Unmark script tags as already being eval'd so they can get executed again
// when restored from cache. HAXX: Uses jQuery internal method.
cloned.find('script').each(function() {
if (!this.src) {
jQuery._data(this, 'globalEval', false);
}
});
return [
selector,
cloned.contents()
];
}
// Internal: Strip internal query params from parsed URL.
//
// Returns sanitized url.href String.
function stripInternalParams(url) {
url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '');
return url.href.replace(/\?($|#)/, '$1');
}
// Internal: Parse URL components and returns a Locationish object.
//
// url - String URL
//
// Returns HTMLAnchorElement that acts like Location.
function parseURL(url) {
var a = document.createElement('a');
a.href = url;
return a;
}
// Internal: Return the `href` component of given URL object with the hash
// portion removed.
//
// location - Location or HTMLAnchorElement
//
// Returns String
function stripHash(location) {
return location.href.replace(/#.*/, '');
}
// Internal: Build options Object for arguments.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Examples
//
// optionsFor('#container')
// // => {container: '#container'}
//
// optionsFor('#container', {push: true})
// // => {container: '#container', push: true}
//
// optionsFor({container: '#container', push: true})
// // => {container: '#container', push: true}
//
// Returns options Object.
function optionsFor(container, options) {
// Both container and options
if (container && options) {
options.container = container;
}// First argument is options Object
else if ($.isPlainObject(container)) {
options = container;
}// Only container
else {
options = {container: container};
}
// Find and validate container
if (options.container) {
options.container = findContainerFor(options.container);
}
return options;
}
// Internal: Find container element for a variety of inputs.
//
// Because we can't persist elements using the history API, we must be
// able to find a String selector that will consistently find the Element.
//
// container - A selector String, jQuery object, or DOM Element.
//
// Returns a jQuery object whose context is `document` and has a selector.
function findContainerFor(container) {
var selector, $container;
if ($.isArray(container)) {
$container = container[0];
selector = container[1];
} else {
selector = container;
$container = $(selector);
}
if (!$container.length) {
throw 'no pjax container for ' + selector;
} else if (true) {
return [
$container,
selector
];
} else if (container.selector !== '' && container.context === document) {
return container;
} else if (container.attr('id')) {
return $('#' + container.attr('id'));
} else {
throw 'cant get selector for pjax container!';
}
}
// Internal: Filter and find all elements matching the selector.
//
// Where $.fn.find only matches descendants, findAll will test all the
// top level elements in the jQuery object as well.
//
// elems - jQuery object of Elements
// selector - String selector to match
//
// Returns a jQuery object.
function findAll(elems, selector) {
return elems.filter(selector).add(elems.find(selector));
}
function parseHTML(html) {
return $.parseHTML(html, document, true);
}
// Internal: Extracts container and metadata from response.
//
// 1. Extracts X-PJAX-URL header if set
// 2. Extracts inline <title> tags
// 3. Builds response Element and extracts fragment if set
//
// data - String response data
// xhr - XHR response
// options - pjax options Object
//
// Returns an Object with url, title, and contents keys.
function extractContainer(data, xhr, options) {
var obj = {}, fullDocument = /<html/i.test(data);
// Prefer X-PJAX-URL header if it was set, otherwise fallback to
// using the original requested url.
var serverUrl = xhr.getResponseHeader('X-PJAX-URL');
obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl;
// Attempt to parse response html into elements
if (fullDocument) {
var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]));
var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]));
} else {
var $head = $body = $(parseHTML(data));
}
// If response data is empty, return fast
if ($body.length === 0) {
return obj;
}
// If there's a <title> tag in the header, use it as
// the page's title.
obj.title = findAll($head, 'title').last().text();
if (options.fragment) {
// If they specified a fragment, look for it in the response
// and pull it out.
if (options.fragment === 'body') {
var $fragment = $body;
} else {
var $fragment = findAll($body, options.fragment).first();
}
if ($fragment.length) {
obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents();
// If there's no title, look for data-title and title attributes
// on the fragment
if (!obj.title) {
obj.title = $fragment.attr('title') || $fragment.data('title');
}
}
} else if (!fullDocument) {
obj.contents = $body;
}
// Clean up any <title> tags
if (obj.contents) {
// Remove any parent title elements
obj.contents = obj.contents.not(function() { return $(this).is('title'); });
// Then scrub any titles from their descendants
obj.contents.find('title').remove();
// Gather all script[src] elements
obj.scripts = findAll(obj.contents, 'script[src]').remove();
obj.contents = obj.contents.not(obj.scripts);
}
// Trim any whitespace off the title
if (obj.title) {
obj.title = $.trim(obj.title);
}
return obj;
}
// Load an execute scripts using standard script request.
//
// Avoids jQuery's traditional $.getScript which does a XHR request and
// globalEval.
//
// scripts - jQuery object of script Elements
//
// Returns nothing.
function executeScriptTags(scripts) {
if (!scripts) {
return;
}
var existingScripts = $('script[src]');
scripts.each(function() {
var src = this.src;
var matchedScripts = existingScripts.filter(function() {
return this.src === src;
});
if (matchedScripts.length) {
return;
}
var script = document.createElement('script');
var type = $(this).attr('type');
if (type) {
script.type = type;
}
script.src = $(this).attr('src');
document.head.appendChild(script);
});
}
// Internal: History DOM caching class.
var cacheMapping = {};
var cacheForwardStack = [];
var cacheBackStack = [];
// Push previous state id and container contents into the history
// cache. Should be called in conjunction with `pushState` to save the
// previous container contents.
//
// id - State ID Number
// value - DOM Element to cache
//
// Returns nothing.
function cachePush(id, value) {
cacheMapping[id] = value;
cacheBackStack.push(id);
// Remove all entries in forward history stack after pushing a new page.
trimCacheStack(cacheForwardStack, 0);
// Trim back history stack to max cache length.
trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength);
}
// Shifts cache from directional history cache. Should be
// called on `popstate` with the previous state id and container
// contents.
//
// direction - "forward" or "back" String
// id - State ID Number
// value - DOM Element to cache
//
// Returns nothing.
function cachePop(direction, id, value) {
var pushStack, popStack;
cacheMapping[id] = value;
if (direction === 'forward') {
pushStack = cacheBackStack;
popStack = cacheForwardStack;
} else {
pushStack = cacheForwardStack;
popStack = cacheBackStack;
}
pushStack.push(id);
if (id = popStack.pop()) {
delete cacheMapping[id];
}
// Trim whichever stack we just pushed to to max cache length.
trimCacheStack(pushStack, pjax.defaults.maxCacheLength);
}
// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no
// longer than the specified length, deleting cached DOM elements as necessary.
//
// stack - Array of state IDs
// length - Maximum length to trim to
//
// Returns nothing.
function trimCacheStack(stack, length) {
while (stack.length > length) {
delete cacheMapping[stack.shift()];
}
}
// Public: Find version identifier for the initial page load.
//
// Returns String version or undefined.
function findVersion() {
return $('meta').filter(function() {
var name = $(this).attr('http-equiv');
return name && name.toUpperCase() === 'X-PJAX-VERSION';
}).attr('content');
}
// Install pjax functions on $.pjax to enable pushState behavior.
//
// Does nothing if already enabled.
//
// Examples
//
// $.pjax.enable()
//
// Returns nothing.
function enable() {
$.fn.pjax = fnPjax;
$.pjax = pjax;
$.pjax.enable = $.noop;
$.pjax.disable = disable;
$.pjax.click = handleClick;
$.pjax.submit = handleSubmit;
$.pjax.reload = pjaxReload;
$.pjax.defaults = {
timeout : 650,
push : true,
replace : false,
type : 'GET',
dataType : 'html',
scrollTo : 0,
renderCallback: false,
maxCacheLength: 20,
version : findVersion
};
$(window).on('popstate.pjax', onPjaxPopstate);
}
// Disable pushState behavior.
//
// This is the case when a browser doesn't support pushState. It is
// sometimes useful to disable pushState for debugging on a modern
// browser.
//
// Examples
//
// $.pjax.disable()
//
// Returns nothing.
function disable() {
$.fn.pjax = function() { return this; };
$.pjax = fallbackPjax;
$.pjax.enable = enable;
$.pjax.disable = $.noop;
$.pjax.click = $.noop;
$.pjax.submit = $.noop;
$.pjax.reload = function() { window.location.reload(); };
$(window).off('popstate.pjax', onPjaxPopstate);
}
// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
if ($.event.props && $.inArray('state', $.event.props) < 0) {
$.event.props.push('state');
} else if (!('state' in $.Event.prototype)) {
$.event.addProp('state');
}
// Is pjax supported by this browser?
$.support.pjax =
window.history && window.history.pushState && window.history.replaceState &&
// pushState isn't reliable on iOS until 5.
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
$.support.pjax ? enable() : disable()
})(jQuery);
/*
_ _ _ _
___| (_) ___| | __ (_)___
/ __| | |/ __| |/ / | / __|
\__ \ | | (__| < _ | \__ \
|___/_|_|\___|_|\_(_)/ |___/
|__/
Version: 1.6.0
Author: Ken Wheeler
Website: http://kenwheeler.github.io
Docs: http://kenwheeler.github.io/slick
Repo: http://github.com/kenwheeler/slick
Issues: http://github.com/kenwheeler/slick/issues
*/
/* global window, document, define, jQuery, setInterval, clearInterval */
(function(factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports !== 'undefined') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
'use strict';
var Slick = window.Slick || {};
Slick = (function() {
var instanceUid = 0;
function Slick(element, settings) {
var _ = this, dataSettings;
_.defaults = {
accessibility: true,
adaptiveHeight: false,
appendArrows: $(element),
appendDots: $(element),
arrows: true,
asNavFor: null,
prevArrow: '<button type="button" data-role="none" class="slick-prev" aria-label="Previous" tabindex="0" role="button">Previous</button>',
nextArrow: '<button type="button" data-role="none" class="slick-next" aria-label="Next" tabindex="0" role="button">Next</button>',
autoplay: false,
autoplaySpeed: 3000,
centerMode: false,
centerPadding: '50px',
cssEase: 'ease',
customPaging: function(slider, i) {
return $('<button type="button" data-role="none" role="button" tabindex="0" />').text(i + 1);
},
dots: false,
dotsClass: 'slick-dots',
draggable: true,
easing: 'linear',
edgeFriction: 0.35,
fade: false,
focusOnSelect: false,
infinite: true,
initialSlide: 0,
lazyLoad: 'ondemand',
mobileFirst: false,
pauseOnHover: true,
pauseOnFocus: true,
pauseOnDotsHover: false,
respondTo: 'window',
responsive: null,
rows: 1,
rtl: false,
slide: '',
slidesPerRow: 1,
slidesToShow: 1,
slidesToScroll: 1,
speed: 500,
swipe: true,
swipeToSlide: false,
touchMove: true,
touchThreshold: 5,
useCSS: true,
useTransform: true,
variableWidth: false,
vertical: false,
verticalSwiping: false,
waitForAnimate: true,
zIndex: 1000
};
_.initials = {
animating: false,
dragging: false,
autoPlayTimer: null,
currentDirection: 0,
currentLeft: null,
currentSlide: 0,
direction: 1,
$dots: null,
listWidth: null,
listHeight: null,
loadIndex: 0,
$nextArrow: null,
$prevArrow: null,
slideCount: null,
slideWidth: null,
$slideTrack: null,
$slides: null,
sliding: false,
slideOffset: 0,
swipeLeft: null,
$list: null,
touchObject: {},
transformsEnabled: false,
unslicked: false
};
$.extend(_, _.initials);
_.activeBreakpoint = null;
_.animType = null;
_.animProp = null;
_.breakpoints = [];
_.breakpointSettings = [];
_.cssTransitions = false;
_.focussed = false;
_.interrupted = false;
_.hidden = 'hidden';
_.paused = true;
_.positionProp = null;
_.respondTo = null;
_.rowCount = 1;
_.shouldClick = true;
_.$slider = $(element);
_.$slidesCache = null;
_.transformType = null;
_.transitionType = null;
_.visibilityChange = 'visibilitychange';
_.windowWidth = 0;
_.windowTimer = null;
dataSettings = $(element).data('slick') || {};
_.options = $.extend({}, _.defaults, settings, dataSettings);
_.currentSlide = _.options.initialSlide;
_.originalSettings = _.options;
if (typeof document.mozHidden !== 'undefined') {
_.hidden = 'mozHidden';
_.visibilityChange = 'mozvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
_.hidden = 'webkitHidden';
_.visibilityChange = 'webkitvisibilitychange';
}
_.autoPlay = $.proxy(_.autoPlay, _);
_.autoPlayClear = $.proxy(_.autoPlayClear, _);
_.autoPlayIterator = $.proxy(_.autoPlayIterator, _);
_.changeSlide = $.proxy(_.changeSlide, _);
_.clickHandler = $.proxy(_.clickHandler, _);
_.selectHandler = $.proxy(_.selectHandler, _);
_.setPosition = $.proxy(_.setPosition, _);
_.swipeHandler = $.proxy(_.swipeHandler, _);
_.dragHandler = $.proxy(_.dragHandler, _);
_.keyHandler = $.proxy(_.keyHandler, _);
_.instanceUid = instanceUid++;
// A simple way to check for HTML strings
// Strict HTML recognition (must start with <)
// Extracted from jQuery v1.11 source
_.htmlExpr = /^(?:\s*(<[\w\W]+>)[^>]*)$/;
_.registerBreakpoints();
_.init(true);
}
return Slick;
}());
Slick.prototype.activateADA = function() {
var _ = this;
_.$slideTrack.find('.slick-active').attr({
'aria-hidden': 'false'
}).find('a, input, button, select').attr({
'tabindex': '0'
});
};
Slick.prototype.addSlide = Slick.prototype.slickAdd = function(markup, index, addBefore) {
var _ = this;
if (typeof(index) === 'boolean') {
addBefore = index;
index = null;
} else if (index < 0 || (index >= _.slideCount)) {
return false;
}
_.unload();
if (typeof(index) === 'number') {
if (index === 0 && _.$slides.length === 0) {
$(markup).appendTo(_.$slideTrack);
} else if (addBefore) {
$(markup).insertBefore(_.$slides.eq(index));
} else {
$(markup).insertAfter(_.$slides.eq(index));
}
} else {
if (addBefore === true) {
$(markup).prependTo(_.$slideTrack);
} else {
$(markup).appendTo(_.$slideTrack);
}
}
_.$slides = _.$slideTrack.children(this.options.slide);
_.$slideTrack.children(this.options.slide).detach();
_.$slideTrack.append(_.$slides);
_.$slides.each(function(index, element) {
$(element).attr('data-slick-index', index);
});
_.$slidesCache = _.$slides;
_.reinit();
};
Slick.prototype.animateHeight = function() {
var _ = this;
if (_.options.slidesToShow === 1 && _.options.adaptiveHeight === true && _.options.vertical === false) {
var targetHeight = _.$slides.eq(_.currentSlide).outerHeight(true);
_.$list.animate({
height: targetHeight
}, _.options.speed);
}
};
Slick.prototype.animateSlide = function(targetLeft, callback) {
var animProps = {},
_ = this;
_.animateHeight();
if (_.options.rtl === true && _.options.vertical === false) {
targetLeft = -targetLeft;
}
if (_.transformsEnabled === false) {
if (_.options.vertical === false) {
_.$slideTrack.animate({
left: targetLeft
}, _.options.speed, _.options.easing, callback);
} else {
_.$slideTrack.animate({
top: targetLeft
}, _.options.speed, _.options.easing, callback);
}
} else {
if (_.cssTransitions === false) {
if (_.options.rtl === true) {
_.currentLeft = -(_.currentLeft);
}
$({
animStart: _.currentLeft
}).animate({
animStart: targetLeft
}, {
duration: _.options.speed,
easing: _.options.easing,
step: function(now) {
now = Math.ceil(now);
if (_.options.vertical === false) {
animProps[_.animType] = 'translate(' +
now + 'px, 0px)';
_.$slideTrack.css(animProps);
} else {
animProps[_.animType] = 'translate(0px,' +
now + 'px)';
_.$slideTrack.css(animProps);
}
},
complete: function() {
if (callback) {
callback.call();
}
}
});
} else {
_.applyTransition();
targetLeft = Math.ceil(targetLeft);
if (_.options.vertical === false) {
animProps[_.animType] = 'translate3d(' + targetLeft + 'px, 0px, 0px)';
} else {
animProps[_.animType] = 'translate3d(0px,' + targetLeft + 'px, 0px)';
}
_.$slideTrack.css(animProps);
if (callback) {
setTimeout(function() {
_.disableTransition();
callback.call();
}, _.options.speed);
}
}
}
};
Slick.prototype.getNavTarget = function() {
var _ = this,
asNavFor = _.options.asNavFor;
if ( asNavFor && asNavFor !== null ) {
asNavFor = $(asNavFor).not(_.$slider);
}
return asNavFor;
};
Slick.prototype.asNavFor = function(index) {
var _ = this,
asNavFor = _.getNavTarget();
if ( asNavFor !== null && typeof asNavFor === 'object' ) {
asNavFor.each(function() {
var target = $(this).slick('getSlick');
if(!target.unslicked) {
target.slideHandler(index, true);
}
});
}
};
Slick.prototype.applyTransition = function(slide) {
var _ = this,
transition = {};
if (_.options.fade === false) {
transition[_.transitionType] = _.transformType + ' ' + _.options.speed + 'ms ' + _.options.cssEase;
} else {
transition[_.transitionType] = 'opacity ' + _.options.speed + 'ms ' + _.options.cssEase;
}
if (_.options.fade === false) {
_.$slideTrack.css(transition);
} else {
_.$slides.eq(slide).css(transition);
}
};
Slick.prototype.autoPlay = function() {
var _ = this;
_.autoPlayClear();
if ( _.slideCount > _.options.slidesToShow ) {
_.autoPlayTimer = setInterval( _.autoPlayIterator, _.options.autoplaySpeed );
}
};
Slick.prototype.autoPlayClear = function() {
var _ = this;
if (_.autoPlayTimer) {
clearInterval(_.autoPlayTimer);
}
};
Slick.prototype.autoPlayIterator = function() {
var _ = this,
slideTo = _.currentSlide + _.options.slidesToScroll;
if ( !_.paused && !_.interrupted && !_.focussed ) {
if ( _.options.infinite === false ) {
if ( _.direction === 1 && ( _.currentSlide + 1 ) === ( _.slideCount - 1 )) {
_.direction = 0;
}
else if ( _.direction === 0 ) {
slideTo = _.currentSlide - _.options.slidesToScroll;
if ( _.currentSlide - 1 === 0 ) {
_.direction = 1;
}
}
}
_.slideHandler( slideTo );
}
};
Slick.prototype.buildArrows = function() {
var _ = this;
if (_.options.arrows === true ) {
_.$prevArrow = $(_.options.prevArrow).addClass('slick-arrow');
_.$nextArrow = $(_.options.nextArrow).addClass('slick-arrow');
if( _.slideCount > _.options.slidesToShow ) {
_.$prevArrow.removeClass('slick-hidden').removeAttr('aria-hidden tabindex');
_.$nextArrow.removeClass('slick-hidden').removeAttr('aria-hidden tabindex');
if (_.htmlExpr.test(_.options.prevArrow)) {
_.$prevArrow.prependTo(_.options.appendArrows);
}
if (_.htmlExpr.test(_.options.nextArrow)) {
_.$nextArrow.appendTo(_.options.appendArrows);
}
if (_.options.infinite !== true) {
_.$prevArrow
.addClass('slick-disabled')
.attr('aria-disabled', 'true');
}
} else {
_.$prevArrow.add( _.$nextArrow )
.addClass('slick-hidden')
.attr({
'aria-disabled': 'true',
'tabindex': '-1'
});
}
}
};
Slick.prototype.buildDots = function() {
var _ = this,
i, dot;
if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
_.$slider.addClass('slick-dotted');
dot = $('<ul />').addClass(_.options.dotsClass);
for (i = 0; i <= _.getDotCount(); i += 1) {
dot.append($('<li />').append(_.options.customPaging.call(this, _, i)));
}
_.$dots = dot.appendTo(_.options.appendDots);
_.$dots.find('li').first().addClass('slick-active').attr('aria-hidden', 'false');
}
};
Slick.prototype.buildOut = function() {
var _ = this;
_.$slides =
_.$slider
.children( _.options.slide + ':not(.slick-cloned)')
.addClass('slick-slide');
_.slideCount = _.$slides.length;
_.$slides.each(function(index, element) {
$(element)
.attr('data-slick-index', index)
.data('originalStyling', $(element).attr('style') || '');
});
_.$slider.addClass('slick-slider');
_.$slideTrack = (_.slideCount === 0) ?
$('<div class="slick-track"/>').appendTo(_.$slider) :
_.$slides.wrapAll('<div class="slick-track"/>').parent();
_.$list = _.$slideTrack.wrap(
'<div aria-live="polite" class="slick-list"/>').parent();
_.$slideTrack.css('opacity', 0);
if (_.options.centerMode === true || _.options.swipeToSlide === true) {
_.options.slidesToScroll = 1;
}
$('img[data-lazy]', _.$slider).not('[src]').addClass('slick-loading');
_.setupInfinite();
_.buildArrows();
_.buildDots();
_.updateDots();
_.setSlideClasses(typeof _.currentSlide === 'number' ? _.currentSlide : 0);
if (_.options.draggable === true) {
_.$list.addClass('draggable');
}
};
Slick.prototype.buildRows = function() {
var _ = this, a, b, c, newSlides, numOfSlides, originalSlides,slidesPerSection;
newSlides = document.createDocumentFragment();
originalSlides = _.$slider.children();
if(_.options.rows > 1) {
slidesPerSection = _.options.slidesPerRow * _.options.rows;
numOfSlides = Math.ceil(
originalSlides.length / slidesPerSection
);
for(a = 0; a < numOfSlides; a++){
var slide = document.createElement('div');
for(b = 0; b < _.options.rows; b++) {
var row = document.createElement('div');
for(c = 0; c < _.options.slidesPerRow; c++) {
var target = (a * slidesPerSection + ((b * _.options.slidesPerRow) + c));
if (originalSlides.get(target)) {
row.appendChild(originalSlides.get(target));
}
}
slide.appendChild(row);
}
newSlides.appendChild(slide);
}
_.$slider.empty().append(newSlides);
_.$slider.children().children().children()
.css({
'width':(100 / _.options.slidesPerRow) + '%',
'display': 'inline-block'
});
}
};
Slick.prototype.checkResponsive = function(initial, forceUpdate) {
var _ = this,
breakpoint, targetBreakpoint, respondToWidth, triggerBreakpoint = false;
var sliderWidth = _.$slider.width();
var windowWidth = window.innerWidth || $(window).width();
if (_.respondTo === 'window') {
respondToWidth = windowWidth;
} else if (_.respondTo === 'slider') {
respondToWidth = sliderWidth;
} else if (_.respondTo === 'min') {
respondToWidth = Math.min(windowWidth, sliderWidth);
}
if ( _.options.responsive &&
_.options.responsive.length &&
_.options.responsive !== null) {
targetBreakpoint = null;
for (breakpoint in _.breakpoints) {
if (_.breakpoints.hasOwnProperty(breakpoint)) {
if (_.originalSettings.mobileFirst === false) {
if (respondToWidth < _.breakpoints[breakpoint]) {
targetBreakpoint = _.breakpoints[breakpoint];
}
} else {
if (respondToWidth > _.breakpoints[breakpoint]) {
targetBreakpoint = _.breakpoints[breakpoint];
}
}
}
}
if (targetBreakpoint !== null) {
if (_.activeBreakpoint !== null) {
if (targetBreakpoint !== _.activeBreakpoint || forceUpdate) {
_.activeBreakpoint =
targetBreakpoint;
if (_.breakpointSettings[targetBreakpoint] === 'unslick') {
_.unslick(targetBreakpoint);
} else {
_.options = $.extend({}, _.originalSettings,
_.breakpointSettings[
targetBreakpoint]);
if (initial === true) {
_.currentSlide = _.options.initialSlide;
}
_.refresh(initial);
}
triggerBreakpoint = targetBreakpoint;
}
} else {
_.activeBreakpoint = targetBreakpoint;
if (_.breakpointSettings[targetBreakpoint] === 'unslick') {
_.unslick(targetBreakpoint);
} else {
_.options = $.extend({}, _.originalSettings,
_.breakpointSettings[
targetBreakpoint]);
if (initial === true) {
_.currentSlide = _.options.initialSlide;
}
_.refresh(initial);
}
triggerBreakpoint = targetBreakpoint;
}
} else {
if (_.activeBreakpoint !== null) {
_.activeBreakpoint = null;
_.options = _.originalSettings;
if (initial === true) {
_.currentSlide = _.options.initialSlide;
}
_.refresh(initial);
triggerBreakpoint = targetBreakpoint;
}
}
// only trigger breakpoints during an actual break. not on initialize.
if( !initial && triggerBreakpoint !== false ) {
_.$slider.trigger('breakpoint', [_, triggerBreakpoint]);
}
}
};
Slick.prototype.changeSlide = function(event, dontAnimate) {
var _ = this,
$target = $(event.currentTarget),
indexOffset, slideOffset, unevenOffset;
// If target is a link, prevent default action.
if($target.is('a')) {
event.preventDefault();
}
// If target is not the <li> element (ie: a child), find the <li>.
if(!$target.is('li')) {
$target = $target.closest('li');
}
unevenOffset = (_.slideCount % _.options.slidesToScroll !== 0);
indexOffset = unevenOffset ? 0 : (_.slideCount - _.currentSlide) % _.options.slidesToScroll;
switch (event.data.message) {
case 'previous':
slideOffset = indexOffset === 0 ? _.options.slidesToScroll : _.options.slidesToShow - indexOffset;
if (_.slideCount > _.options.slidesToShow) {
_.slideHandler(_.currentSlide - slideOffset, false, dontAnimate);
}
break;
case 'next':
slideOffset = indexOffset === 0 ? _.options.slidesToScroll : indexOffset;
if (_.slideCount > _.options.slidesToShow) {
_.slideHandler(_.currentSlide + slideOffset, false, dontAnimate);
}
break;
case 'index':
var index = event.data.index === 0 ? 0 :
event.data.index || $target.index() * _.options.slidesToScroll;
_.slideHandler(_.checkNavigable(index), false, dontAnimate);
$target.children().trigger('focus');
break;
default:
return;
}
};
Slick.prototype.checkNavigable = function(index) {
var _ = this,
navigables, prevNavigable;
navigables = _.getNavigableIndexes();
prevNavigable = 0;
if (index > navigables[navigables.length - 1]) {
index = navigables[navigables.length - 1];
} else {
for (var n in navigables) {
if (index < navigables[n]) {
index = prevNavigable;
break;
}
prevNavigable = navigables[n];
}
}
return index;
};
Slick.prototype.cleanUpEvents = function() {
var _ = this;
if (_.options.dots && _.$dots !== null) {
$('li', _.$dots)
.off('click.slick', _.changeSlide)
.off('mouseenter.slick', $.proxy(_.interrupt, _, true))
.off('mouseleave.slick', $.proxy(_.interrupt, _, false));
}
_.$slider.off('focus.slick blur.slick');
if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {
_.$prevArrow && _.$prevArrow.off('click.slick', _.changeSlide);
_.$nextArrow && _.$nextArrow.off('click.slick', _.changeSlide);
}
_.$list.off('touchstart.slick mousedown.slick', _.swipeHandler);
_.$list.off('touchmove.slick mousemove.slick', _.swipeHandler);
_.$list.off('touchend.slick mouseup.slick', _.swipeHandler);
_.$list.off('touchcancel.slick mouseleave.slick', _.swipeHandler);
_.$list.off('click.slick', _.clickHandler);
$(document).off(_.visibilityChange, _.visibility);
_.cleanUpSlideEvents();
if (_.options.accessibility === true) {
_.$list.off('keydown.slick', _.keyHandler);
}
if (_.options.focusOnSelect === true) {
$(_.$slideTrack).children().off('click.slick', _.selectHandler);
}
$(window).off('orientationchange.slick.slick-' + _.instanceUid, _.orientationChange);
$(window).off('resize.slick.slick-' + _.instanceUid, _.resize);
$('[draggable!=true]', _.$slideTrack).off('dragstart', _.preventDefault);
$(window).off('load.slick.slick-' + _.instanceUid, _.setPosition);
$(document).off('ready.slick.slick-' + _.instanceUid, _.setPosition);
};
Slick.prototype.cleanUpSlideEvents = function() {
var _ = this;
_.$list.off('mouseenter.slick', $.proxy(_.interrupt, _, true));
_.$list.off('mouseleave.slick', $.proxy(_.interrupt, _, false));
};
Slick.prototype.cleanUpRows = function() {
var _ = this, originalSlides;
if(_.options.rows > 1) {
originalSlides = _.$slides.children().children();
originalSlides.removeAttr('style');
_.$slider.empty().append(originalSlides);
}
};
Slick.prototype.clickHandler = function(event) {
var _ = this;
if (_.shouldClick === false) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
}
};
Slick.prototype.destroy = function(refresh) {
var _ = this;
_.autoPlayClear();
_.touchObject = {};
_.cleanUpEvents();
$('.slick-cloned', _.$slider).detach();
if (_.$dots) {
_.$dots.remove();
}
if ( _.$prevArrow && _.$prevArrow.length ) {
_.$prevArrow
.removeClass('slick-disabled slick-arrow slick-hidden')
.removeAttr('aria-hidden aria-disabled tabindex')
.css('display','');
if ( _.htmlExpr.test( _.options.prevArrow )) {
_.$prevArrow.remove();
}
}
if ( _.$nextArrow && _.$nextArrow.length ) {
_.$nextArrow
.removeClass('slick-disabled slick-arrow slick-hidden')
.removeAttr('aria-hidden aria-disabled tabindex')
.css('display','');
if ( _.htmlExpr.test( _.options.nextArrow )) {
_.$nextArrow.remove();
}
}
if (_.$slides) {
_.$slides
.removeClass('slick-slide slick-active slick-center slick-visible slick-current')
.removeAttr('aria-hidden')
.removeAttr('data-slick-index')
.each(function(){
$(this).attr('style', $(this).data('originalStyling'));
});
_.$slideTrack.children(this.options.slide).detach();
_.$slideTrack.detach();
_.$list.detach();
_.$slider.append(_.$slides);
}
_.cleanUpRows();
_.$slider.removeClass('slick-slider');
_.$slider.removeClass('slick-initialized');
_.$slider.removeClass('slick-dotted');
_.unslicked = true;
if(!refresh) {
_.$slider.trigger('destroy', [_]);
}
};
Slick.prototype.disableTransition = function(slide) {
var _ = this,
transition = {};
transition[_.transitionType] = '';
if (_.options.fade === false) {
_.$slideTrack.css(transition);
} else {
_.$slides.eq(slide).css(transition);
}
};
Slick.prototype.fadeSlide = function(slideIndex, callback) {
var _ = this;
if (_.cssTransitions === false) {
_.$slides.eq(slideIndex).css({
zIndex: _.options.zIndex
});
_.$slides.eq(slideIndex).animate({
opacity: 1
}, _.options.speed, _.options.easing, callback);
} else {
_.applyTransition(slideIndex);
_.$slides.eq(slideIndex).css({
opacity: 1,
zIndex: _.options.zIndex
});
if (callback) {
setTimeout(function() {
_.disableTransition(slideIndex);
callback.call();
}, _.options.speed);
}
}
};
Slick.prototype.fadeSlideOut = function(slideIndex) {
var _ = this;
if (_.cssTransitions === false) {
_.$slides.eq(slideIndex).animate({
opacity: 0,
zIndex: _.options.zIndex - 2
}, _.options.speed, _.options.easing);
} else {
_.applyTransition(slideIndex);
_.$slides.eq(slideIndex).css({
opacity: 0,
zIndex: _.options.zIndex - 2
});
}
};
Slick.prototype.filterSlides = Slick.prototype.slickFilter = function(filter) {
var _ = this;
if (filter !== null) {
_.$slidesCache = _.$slides;
_.unload();
_.$slideTrack.children(this.options.slide).detach();
_.$slidesCache.filter(filter).appendTo(_.$slideTrack);
_.reinit();
}
};
Slick.prototype.focusHandler = function() {
var _ = this;
_.$slider
.off('focus.slick blur.slick')
.on('focus.slick blur.slick',
'*:not(.slick-arrow)', function(event) {
event.stopImmediatePropagation();
var $sf = $(this);
setTimeout(function() {
if( _.options.pauseOnFocus ) {
_.focussed = $sf.is(':focus');
_.autoPlay();
}
}, 0);
});
};
Slick.prototype.getCurrent = Slick.prototype.slickCurrentSlide = function() {
var _ = this;
return _.currentSlide;
};
Slick.prototype.getDotCount = function() {
var _ = this;
var breakPoint = 0;
var counter = 0;
var pagerQty = 0;
if (_.options.infinite === true) {
while (breakPoint < _.slideCount) {
++pagerQty;
breakPoint = counter + _.options.slidesToScroll;
counter += _.options.slidesToScroll <= _.options.slidesToShow ? _.options.slidesToScroll : _.options.slidesToShow;
}
} else if (_.options.centerMode === true) {
pagerQty = _.slideCount;
} else if(!_.options.asNavFor) {
pagerQty = 1 + Math.ceil((_.slideCount - _.options.slidesToShow) / _.options.slidesToScroll);
}else {
while (breakPoint < _.slideCount) {
++pagerQty;
breakPoint = counter + _.options.slidesToScroll;
counter += _.options.slidesToScroll <= _.options.slidesToShow ? _.options.slidesToScroll : _.options.slidesToShow;
}
}
return pagerQty - 1;
};
Slick.prototype.getLeft = function(slideIndex) {
var _ = this,
targetLeft,
verticalHeight,
verticalOffset = 0,
targetSlide;
_.slideOffset = 0;
verticalHeight = _.$slides.first().outerHeight(true);
if (_.options.infinite === true) {
if (_.slideCount > _.options.slidesToShow) {
_.slideOffset = (_.slideWidth * _.options.slidesToShow) * -1;
verticalOffset = (verticalHeight * _.options.slidesToShow) * -1;
}
if (_.slideCount % _.options.slidesToScroll !== 0) {
if (slideIndex + _.options.slidesToScroll > _.slideCount && _.slideCount > _.options.slidesToShow) {
if (slideIndex > _.slideCount) {
_.slideOffset = ((_.options.slidesToShow - (slideIndex - _.slideCount)) * _.slideWidth) * -1;
verticalOffset = ((_.options.slidesToShow - (slideIndex - _.slideCount)) * verticalHeight) * -1;
} else {
_.slideOffset = ((_.slideCount % _.options.slidesToScroll) * _.slideWidth) * -1;
verticalOffset = ((_.slideCount % _.options.slidesToScroll) * verticalHeight) * -1;
}
}
}
} else {
if (slideIndex + _.options.slidesToShow > _.slideCount) {
_.slideOffset = ((slideIndex + _.options.slidesToShow) - _.slideCount) * _.slideWidth;
verticalOffset = ((slideIndex + _.options.slidesToShow) - _.slideCount) * verticalHeight;
}
}
if (_.slideCount <= _.options.slidesToShow) {
_.slideOffset = 0;
verticalOffset = 0;
}
if (_.options.centerMode === true && _.options.infinite === true) {
_.slideOffset += _.slideWidth * Math.floor(_.options.slidesToShow / 2) - _.slideWidth;
} else if (_.options.centerMode === true) {
_.slideOffset = 0;
_.slideOffset += _.slideWidth * Math.floor(_.options.slidesToShow / 2);
}
if (_.options.vertical === false) {
targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
} else {
targetLeft = ((slideIndex * verticalHeight) * -1) + verticalOffset;
}
if (_.options.variableWidth === true) {
if (_.slideCount <= _.options.slidesToShow || _.options.infinite === false) {
targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex);
} else {
targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex + _.options.slidesToShow);
}
if (_.options.rtl === true) {
if (targetSlide[0]) {
targetLeft = (_.$slideTrack.width() - targetSlide[0].offsetLeft - targetSlide.width()) * -1;
} else {
targetLeft = 0;
}
} else {
targetLeft = targetSlide[0] ? targetSlide[0].offsetLeft * -1 : 0;
}
if (_.options.centerMode === true) {
if (_.slideCount <= _.options.slidesToShow || _.options.infinite === false) {
targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex);
} else {
targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex + _.options.slidesToShow + 1);
}
if (_.options.rtl === true) {
if (targetSlide[0]) {
targetLeft = (_.$slideTrack.width() - targetSlide[0].offsetLeft - targetSlide.width()) * -1;
} else {
targetLeft = 0;
}
} else {
targetLeft = targetSlide[0] ? targetSlide[0].offsetLeft * -1 : 0;
}
targetLeft += (_.$list.width() - targetSlide.outerWidth()) / 2;
}
}
return targetLeft;
};
Slick.prototype.getOption = Slick.prototype.slickGetOption = function(option) {
var _ = this;
return _.options[option];
};
Slick.prototype.getNavigableIndexes = function() {
var _ = this,
breakPoint = 0,
counter = 0,
indexes = [],
max;
if (_.options.infinite === false) {
max = _.slideCount;
} else {
breakPoint = _.options.slidesToScroll * -1;
counter = _.options.slidesToScroll * -1;
max = _.slideCount * 2;
}
while (breakPoint < max) {
indexes.push(breakPoint);
breakPoint = counter + _.options.slidesToScroll;
counter += _.options.slidesToScroll <= _.options.slidesToShow ? _.options.slidesToScroll : _.options.slidesToShow;
}
return indexes;
};
Slick.prototype.getSlick = function() {
return this;
};
Slick.prototype.getSlideCount = function() {
var _ = this,
slidesTraversed, swipedSlide, centerOffset;
centerOffset = _.options.centerMode === true ? _.slideWidth * Math.floor(_.options.slidesToShow / 2) : 0;
if (_.options.swipeToSlide === true) {
_.$slideTrack.find('.slick-slide').each(function(index, slide) {
if (slide.offsetLeft - centerOffset + ($(slide).outerWidth() / 2) > (_.swipeLeft * -1)) {
swipedSlide = slide;
return false;
}
});
slidesTraversed = Math.abs($(swipedSlide).attr('data-slick-index') - _.currentSlide) || 1;
return slidesTraversed;
} else {
return _.options.slidesToScroll;
}
};
Slick.prototype.goTo = Slick.prototype.slickGoTo = function(slide, dontAnimate) {
var _ = this;
_.changeSlide({
data: {
message: 'index',
index: parseInt(slide)
}
}, dontAnimate);
};
Slick.prototype.init = function(creation) {
var _ = this;
if (!$(_.$slider).hasClass('slick-initialized')) {
$(_.$slider).addClass('slick-initialized');
_.buildRows();
_.buildOut();
_.setProps();
_.startLoad();
_.loadSlider();
_.initializeEvents();
_.updateArrows();
_.updateDots();
_.checkResponsive(true);
_.focusHandler();
}
if (creation) {
_.$slider.trigger('init', [_]);
}
if (_.options.accessibility === true) {
_.initADA();
}
if ( _.options.autoplay ) {
_.paused = false;
_.autoPlay();
}
};
Slick.prototype.initADA = function() {
var _ = this;
_.$slides.add(_.$slideTrack.find('.slick-cloned')).attr({
'aria-hidden': 'true',
'tabindex': '-1'
}).find('a, input, button, select').attr({
'tabindex': '-1'
});
_.$slideTrack.attr('role', 'listbox');
_.$slides.not(_.$slideTrack.find('.slick-cloned')).each(function(i) {
$(this).attr({
'role': 'option',
'aria-describedby': 'slick-slide' + _.instanceUid + i + ''
});
});
if (_.$dots !== null) {
_.$dots.attr('role', 'tablist').find('li').each(function(i) {
$(this).attr({
'role': 'presentation',
'aria-selected': 'false',
'aria-controls': 'navigation' + _.instanceUid + i + '',
'id': 'slick-slide' + _.instanceUid + i + ''
});
})
.first().attr('aria-selected', 'true').end()
.find('button').attr('role', 'button').end()
.closest('div').attr('role', 'toolbar');
}
_.activateADA();
};
Slick.prototype.initArrowEvents = function() {
var _ = this;
if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {
_.$prevArrow
.off('click.slick')
.on('click.slick', {
message: 'previous'
}, _.changeSlide);
_.$nextArrow
.off('click.slick')
.on('click.slick', {
message: 'next'
}, _.changeSlide);
}
};
Slick.prototype.initDotEvents = function() {
var _ = this;
if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
$('li', _.$dots).on('click.slick', {
message: 'index'
}, _.changeSlide);
}
if ( _.options.dots === true && _.options.pauseOnDotsHover === true ) {
$('li', _.$dots)
.on('mouseenter.slick', $.proxy(_.interrupt, _, true))
.on('mouseleave.slick', $.proxy(_.interrupt, _, false));
}
};
Slick.prototype.initSlideEvents = function() {
var _ = this;
if ( _.options.pauseOnHover ) {
_.$list.on('mouseenter.slick', $.proxy(_.interrupt, _, true));
_.$list.on('mouseleave.slick', $.proxy(_.interrupt, _, false));
}
};
Slick.prototype.initializeEvents = function() {
var _ = this;
_.initArrowEvents();
_.initDotEvents();
_.initSlideEvents();
_.$list.on('touchstart.slick mousedown.slick', {
action: 'start'
}, _.swipeHandler);
_.$list.on('touchmove.slick mousemove.slick', {
action: 'move'
}, _.swipeHandler);
_.$list.on('touchend.slick mouseup.slick', {
action: 'end'
}, _.swipeHandler);
_.$list.on('touchcancel.slick mouseleave.slick', {
action: 'end'
}, _.swipeHandler);
_.$list.on('click.slick', _.clickHandler);
$(document).on(_.visibilityChange, $.proxy(_.visibility, _));
if (_.options.accessibility === true) {
_.$list.on('keydown.slick', _.keyHandler);
}
if (_.options.focusOnSelect === true) {
$(_.$slideTrack).children().on('click.slick', _.selectHandler);
}
$(window).on('orientationchange.slick.slick-' + _.instanceUid, $.proxy(_.orientationChange, _));
$(window).on('resize.slick.slick-' + _.instanceUid, $.proxy(_.resize, _));
$('[draggable!=true]', _.$slideTrack).on('dragstart', _.preventDefault);
$(window).on('load.slick.slick-' + _.instanceUid, _.setPosition);
// $(document).on('ready.slick.slick-' + _.instanceUid, _.setPosition);
};
Slick.prototype.initUI = function() {
var _ = this;
if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {
_.$prevArrow.show();
_.$nextArrow.show();
}
if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
_.$dots.show();
}
};
Slick.prototype.keyHandler = function(event) {
var _ = this;
//Dont slide if the cursor is inside the form fields and arrow keys are pressed
if(!event.target.tagName.match('TEXTAREA|INPUT|SELECT')) {
if (event.keyCode === 37 && _.options.accessibility === true) {
_.changeSlide({
data: {
message: _.options.rtl === true ? 'next' : 'previous'
}
});
} else if (event.keyCode === 39 && _.options.accessibility === true) {
_.changeSlide({
data: {
message: _.options.rtl === true ? 'previous' : 'next'
}
});
}
}
};
Slick.prototype.lazyLoad = function() {
var _ = this,
loadRange, cloneRange, rangeStart, rangeEnd;
function loadImages(imagesScope) {
$('img[data-lazy]', imagesScope).each(function() {
var image = $(this),
imageSource = $(this).attr('data-lazy'),
imageToLoad = document.createElement('img');
imageToLoad.onload = function() {
image
.animate({ opacity: 0 }, 100, function() {
image
.attr('src', imageSource)
.animate({ opacity: 1 }, 200, function() {
image
.removeAttr('data-lazy')
.removeClass('slick-loading');
});
_.$slider.trigger('lazyLoaded', [_, image, imageSource]);
});
};
imageToLoad.onerror = function() {
image
.removeAttr( 'data-lazy' )
.removeClass( 'slick-loading' )
.addClass( 'slick-lazyload-error' );
_.$slider.trigger('lazyLoadError', [ _, image, imageSource ]);
};
imageToLoad.src = imageSource;
});
}
if (_.options.centerMode === true) {
if (_.options.infinite === true) {
rangeStart = _.currentSlide + (_.options.slidesToShow / 2 + 1);
rangeEnd = rangeStart + _.options.slidesToShow + 2;
} else {
rangeStart = Math.max(0, _.currentSlide - (_.options.slidesToShow / 2 + 1));
rangeEnd = 2 + (_.options.slidesToShow / 2 + 1) + _.currentSlide;
}
} else {
rangeStart = _.options.infinite ? _.options.slidesToShow + _.currentSlide : _.currentSlide;
rangeEnd = Math.ceil(rangeStart + _.options.slidesToShow);
if (_.options.fade === true) {
if (rangeStart > 0) rangeStart--;
if (rangeEnd <= _.slideCount) rangeEnd++;
}
}
loadRange = _.$slider.find('.slick-slide').slice(rangeStart, rangeEnd);
loadImages(loadRange);
if (_.slideCount <= _.options.slidesToShow) {
cloneRange = _.$slider.find('.slick-slide');
loadImages(cloneRange);
} else
if (_.currentSlide >= _.slideCount - _.options.slidesToShow) {
cloneRange = _.$slider.find('.slick-cloned').slice(0, _.options.slidesToShow);
loadImages(cloneRange);
} else if (_.currentSlide === 0) {
cloneRange = _.$slider.find('.slick-cloned').slice(_.options.slidesToShow * -1);
loadImages(cloneRange);
}
};
Slick.prototype.loadSlider = function() {
var _ = this;
_.setPosition();
_.$slideTrack.css({
opacity: 1
});
_.$slider.removeClass('slick-loading');
_.initUI();
if (_.options.lazyLoad === 'progressive') {
_.progressiveLazyLoad();
}
};
Slick.prototype.next = Slick.prototype.slickNext = function() {
var _ = this;
_.changeSlide({
data: {
message: 'next'
}
});
};
Slick.prototype.orientationChange = function() {
var _ = this;
_.checkResponsive();
_.setPosition();
};
Slick.prototype.pause = Slick.prototype.slickPause = function() {
var _ = this;
_.autoPlayClear();
_.paused = true;
};
Slick.prototype.play = Slick.prototype.slickPlay = function() {
var _ = this;
_.autoPlay();
_.options.autoplay = true;
_.paused = false;
_.focussed = false;
_.interrupted = false;
};
Slick.prototype.postSlide = function(index) {
var _ = this;
if( !_.unslicked ) {
_.$slider.trigger('afterChange', [_, index]);
_.animating = false;
_.setPosition();
_.swipeLeft = null;
if ( _.options.autoplay ) {
_.autoPlay();
}
if (_.options.accessibility === true) {
_.initADA();
}
}
};
Slick.prototype.prev = Slick.prototype.slickPrev = function() {
var _ = this;
_.changeSlide({
data: {
message: 'previous'
}
});
};
Slick.prototype.preventDefault = function(event) {
event.preventDefault();
};
Slick.prototype.progressiveLazyLoad = function( tryCount ) {
tryCount = tryCount || 1;
var _ = this,
$imgsToLoad = $( 'img[data-lazy]', _.$slider ),
image,
imageSource,
imageToLoad;
if ( $imgsToLoad.length ) {
image = $imgsToLoad.first();
imageSource = image.attr('data-lazy');
imageToLoad = document.createElement('img');
imageToLoad.onload = function() {
image
.attr( 'src', imageSource )
.removeAttr('data-lazy')
.removeClass('slick-loading');
if ( _.options.adaptiveHeight === true ) {
_.setPosition();
}
_.$slider.trigger('lazyLoaded', [ _, image, imageSource ]);
_.progressiveLazyLoad();
};
imageToLoad.onerror = function() {
if ( tryCount < 3 ) {
/**
* try to load the image 3 times,
* leave a slight delay so we don't get
* servers blocking the request.
*/
setTimeout( function() {
_.progressiveLazyLoad( tryCount + 1 );
}, 500 );
} else {
image
.removeAttr( 'data-lazy' )
.removeClass( 'slick-loading' )
.addClass( 'slick-lazyload-error' );
_.$slider.trigger('lazyLoadError', [ _, image, imageSource ]);
_.progressiveLazyLoad();
}
};
imageToLoad.src = imageSource;
} else {
_.$slider.trigger('allImagesLoaded', [ _ ]);
}
};
Slick.prototype.refresh = function( initializing ) {
var _ = this, currentSlide, lastVisibleIndex;
lastVisibleIndex = _.slideCount - _.options.slidesToShow;
// in non-infinite sliders, we don't want to go past the
// last visible index.
if( !_.options.infinite && ( _.currentSlide > lastVisibleIndex )) {
_.currentSlide = lastVisibleIndex;
}
// if less slides than to show, go to start.
if ( _.slideCount <= _.options.slidesToShow ) {
_.currentSlide = 0;
}
currentSlide = _.currentSlide;
_.destroy(true);
$.extend(_, _.initials, { currentSlide: currentSlide });
_.init();
if( !initializing ) {
_.changeSlide({
data: {
message: 'index',
index: currentSlide
}
}, false);
}
};
Slick.prototype.registerBreakpoints = function() {
var _ = this, breakpoint, currentBreakpoint, l,
responsiveSettings = _.options.responsive || null;
if ( typeof responsiveSettings === 'array' && responsiveSettings.length ) {
_.respondTo = _.options.respondTo || 'window';
for ( breakpoint in responsiveSettings ) {
l = _.breakpoints.length-1;
currentBreakpoint = responsiveSettings[breakpoint].breakpoint;
if (responsiveSettings.hasOwnProperty(breakpoint)) {
// loop through the breakpoints and cut out any existing
// ones with the same breakpoint number, we don't want dupes.
while( l >= 0 ) {
if( _.breakpoints[l] && _.breakpoints[l] === currentBreakpoint ) {
_.breakpoints.splice(l,1);
}
l--;
}
_.breakpoints.push(currentBreakpoint);
_.breakpointSettings[currentBreakpoint] = responsiveSettings[breakpoint].settings;
}
}
_.breakpoints.sort(function(a, b) {
return ( _.options.mobileFirst ) ? a-b : b-a;
});
}
};
Slick.prototype.reinit = function() {
var _ = this;
_.$slides =
_.$slideTrack
.children(_.options.slide)
.addClass('slick-slide');
_.slideCount = _.$slides.length;
if (_.currentSlide >= _.slideCount && _.currentSlide !== 0) {
_.currentSlide = _.currentSlide - _.options.slidesToScroll;
}
if (_.slideCount <= _.options.slidesToShow) {
_.currentSlide = 0;
}
_.registerBreakpoints();
_.setProps();
_.setupInfinite();
_.buildArrows();
_.updateArrows();
_.initArrowEvents();
_.buildDots();
_.updateDots();
_.initDotEvents();
_.cleanUpSlideEvents();
_.initSlideEvents();
_.checkResponsive(false, true);
if (_.options.focusOnSelect === true) {
$(_.$slideTrack).children().on('click.slick', _.selectHandler);
}
_.setSlideClasses(typeof _.currentSlide === 'number' ? _.currentSlide : 0);
_.setPosition();
_.focusHandler();
_.paused = !_.options.autoplay;
_.autoPlay();
_.$slider.trigger('reInit', [_]);
};
Slick.prototype.resize = function() {
var _ = this;
if ($(window).width() !== _.windowWidth) {
clearTimeout(_.windowDelay);
_.windowDelay = window.setTimeout(function() {
_.windowWidth = $(window).width();
_.checkResponsive();
if( !_.unslicked ) { _.setPosition(); }
}, 50);
}
};
Slick.prototype.removeSlide = Slick.prototype.slickRemove = function(index, removeBefore, removeAll) {
var _ = this;
if (typeof(index) === 'boolean') {
removeBefore = index;
index = removeBefore === true ? 0 : _.slideCount - 1;
} else {
index = removeBefore === true ? --index : index;
}
if (_.slideCount < 1 || index < 0 || index > _.slideCount - 1) {
return false;
}
_.unload();
if (removeAll === true) {
_.$slideTrack.children().remove();
} else {
_.$slideTrack.children(this.options.slide).eq(index).remove();
}
_.$slides = _.$slideTrack.children(this.options.slide);
_.$slideTrack.children(this.options.slide).detach();
_.$slideTrack.append(_.$slides);
_.$slidesCache = _.$slides;
_.reinit();
};
Slick.prototype.setCSS = function(position) {
var _ = this,
positionProps = {},
x, y;
if (_.options.rtl === true) {
position = -position;
}
x = _.positionProp == 'left' ? Math.ceil(position) + 'px' : '0px';
y = _.positionProp == 'top' ? Math.ceil(position) + 'px' : '0px';
positionProps[_.positionProp] = position;
if (_.transformsEnabled === false) {
_.$slideTrack.css(positionProps);
} else {
positionProps = {};
if (_.cssTransitions === false) {
positionProps[_.animType] = 'translate(' + x + ', ' + y + ')';
_.$slideTrack.css(positionProps);
} else {
positionProps[_.animType] = 'translate3d(' + x + ', ' + y + ', 0px)';
_.$slideTrack.css(positionProps);
}
}
};
Slick.prototype.setDimensions = function() {
var _ = this;
if (_.options.vertical === false) {
if (_.options.centerMode === true) {
_.$list.css({
padding: ('0px ' + _.options.centerPadding)
});
}
} else {
_.$list.height(_.$slides.first().outerHeight(true) * _.options.slidesToShow);
if (_.options.centerMode === true) {
_.$list.css({
padding: (_.options.centerPadding + ' 0px')
});
}
}
_.listWidth = _.$list.width();
_.listHeight = _.$list.height();
if (_.options.vertical === false && _.options.variableWidth === false) {
_.slideWidth = Math.ceil(_.listWidth / _.options.slidesToShow);
_.$slideTrack.width(Math.ceil((_.slideWidth * _.$slideTrack.children('.slick-slide').length)));
} else if (_.options.variableWidth === true) {
_.$slideTrack.width(5000 * _.slideCount);
} else {
_.slideWidth = Math.ceil(_.listWidth);
_.$slideTrack.height(Math.ceil((_.$slides.first().outerHeight(true) * _.$slideTrack.children('.slick-slide').length)));
}
var offset = _.$slides.first().outerWidth(true) - _.$slides.first().width();
if (_.options.variableWidth === false) _.$slideTrack.children('.slick-slide').width(_.slideWidth - offset);
};
Slick.prototype.setFade = function() {
var _ = this,
targetLeft;
_.$slides.each(function(index, element) {
targetLeft = (_.slideWidth * index) * -1;
if (_.options.rtl === true) {
$(element).css({
position: 'relative',
right: targetLeft,
top: 0,
zIndex: _.options.zIndex - 2,
opacity: 0
});
} else {
$(element).css({
position: 'relative',
left: targetLeft,
top: 0,
zIndex: _.options.zIndex - 2,
opacity: 0
});
}
});
_.$slides.eq(_.currentSlide).css({
zIndex: _.options.zIndex - 1,
opacity: 1
});
};
Slick.prototype.setHeight = function() {
var _ = this;
if (_.options.slidesToShow === 1 && _.options.adaptiveHeight === true && _.options.vertical === false) {
var targetHeight = _.$slides.eq(_.currentSlide).outerHeight(true);
_.$list.css('height', targetHeight);
}
};
Slick.prototype.setOption =
Slick.prototype.slickSetOption = function() {
/**
* accepts arguments in format of:
*
* - for changing a single option's value:
* .slick("setOption", option, value, refresh )
*
* - for changing a set of responsive options:
* .slick("setOption", 'responsive', [{}, ...], refresh )
*
* - for updating multiple values at once (not responsive)
* .slick("setOption", { 'option': value, ... }, refresh )
*/
var _ = this, l, item, option, value, refresh = false, type;
if( typeof arguments[0] === 'object' ) {
option = arguments[0];
refresh = arguments[1];
type = 'multiple';
} else if ( typeof arguments[0] === 'string' ) {
option = arguments[0];
value = arguments[1];
refresh = arguments[2];
if ( arguments[0] === 'responsive' && typeof arguments[1] === 'array' ) {
type = 'responsive';
} else if ( typeof arguments[1] !== 'undefined' ) {
type = 'single';
}
}
if ( type === 'single' ) {
_.options[option] = value;
} else if ( type === 'multiple' ) {
$.each( option , function( opt, val ) {
_.options[opt] = val;
});
} else if ( type === 'responsive' ) {
for ( item in value ) {
if( typeof _.options.responsive !== 'array' ) {
_.options.responsive = [ value[item] ];
} else {
l = _.options.responsive.length-1;
// loop through the responsive object and splice out duplicates.
while( l >= 0 ) {
if( _.options.responsive[l].breakpoint === value[item].breakpoint ) {
_.options.responsive.splice(l,1);
}
l--;
}
_.options.responsive.push( value[item] );
}
}
}
if ( refresh ) {
_.unload();
_.reinit();
}
};
Slick.prototype.setPosition = function() {
var _ = this;
_.setDimensions();
_.setHeight();
if (_.options.fade === false) {
_.setCSS(_.getLeft(_.currentSlide));
} else {
_.setFade();
}
_.$slider.trigger('setPosition', [_]);
};
Slick.prototype.setProps = function() {
var _ = this,
bodyStyle = document.body.style;
_.positionProp = _.options.vertical === true ? 'top' : 'left';
if (_.positionProp === 'top') {
_.$slider.addClass('slick-vertical');
} else {
_.$slider.removeClass('slick-vertical');
}
if (bodyStyle.WebkitTransition !== undefined ||
bodyStyle.MozTransition !== undefined ||
bodyStyle.msTransition !== undefined) {
if (_.options.useCSS === true) {
_.cssTransitions = true;
}
}
if ( _.options.fade ) {
if ( typeof _.options.zIndex === 'number' ) {
if( _.options.zIndex < 3 ) {
_.options.zIndex = 3;
}
} else {
_.options.zIndex = _.defaults.zIndex;
}
}
if (bodyStyle.OTransform !== undefined) {
_.animType = 'OTransform';
_.transformType = '-o-transform';
_.transitionType = 'OTransition';
if (bodyStyle.perspectiveProperty === undefined && bodyStyle.webkitPerspective === undefined) _.animType = false;
}
if (bodyStyle.MozTransform !== undefined) {
_.animType = 'MozTransform';
_.transformType = '-moz-transform';
_.transitionType = 'MozTransition';
if (bodyStyle.perspectiveProperty === undefined && bodyStyle.MozPerspective === undefined) _.animType = false;
}
if (bodyStyle.webkitTransform !== undefined) {
_.animType = 'webkitTransform';
_.transformType = '-webkit-transform';
_.transitionType = 'webkitTransition';
if (bodyStyle.perspectiveProperty === undefined && bodyStyle.webkitPerspective === undefined) _.animType = false;
}
if (bodyStyle.msTransform !== undefined) {
_.animType = 'msTransform';
_.transformType = '-ms-transform';
_.transitionType = 'msTransition';
if (bodyStyle.msTransform === undefined) _.animType = false;
}
if (bodyStyle.transform !== undefined && _.animType !== false) {
_.animType = 'transform';
_.transformType = 'transform';
_.transitionType = 'transition';
}
_.transformsEnabled = _.options.useTransform && (_.animType !== null && _.animType !== false);
};
Slick.prototype.setSlideClasses = function(index) {
var _ = this,
centerOffset, allSlides, indexOffset, remainder;
allSlides = _.$slider
.find('.slick-slide')
.removeClass('slick-active slick-center slick-current')
.attr('aria-hidden', 'true');
_.$slides
.eq(index)
.addClass('slick-current');
if (_.options.centerMode === true) {
centerOffset = Math.floor(_.options.slidesToShow / 2);
if (_.options.infinite === true) {
if (index >= centerOffset && index <= (_.slideCount - 1) - centerOffset) {
_.$slides
.slice(index - centerOffset, index + centerOffset + 1)
.addClass('slick-active')
.attr('aria-hidden', 'false');
} else {
indexOffset = _.options.slidesToShow + index;
allSlides
.slice(indexOffset - centerOffset + 1, indexOffset + centerOffset + 2)
.addClass('slick-active')
.attr('aria-hidden', 'false');
}
if (index === 0) {
allSlides
.eq(allSlides.length - 1 - _.options.slidesToShow)
.addClass('slick-center');
} else if (index === _.slideCount - 1) {
allSlides
.eq(_.options.slidesToShow)
.addClass('slick-center');
}
}
_.$slides
.eq(index)
.addClass('slick-center');
} else {
if (index >= 0 && index <= (_.slideCount - _.options.slidesToShow)) {
_.$slides
.slice(index, index + _.options.slidesToShow)
.addClass('slick-active')
.attr('aria-hidden', 'false');
} else if (allSlides.length <= _.options.slidesToShow) {
allSlides
.addClass('slick-active')
.attr('aria-hidden', 'false');
} else {
remainder = _.slideCount % _.options.slidesToShow;
indexOffset = _.options.infinite === true ? _.options.slidesToShow + index : index;
if (_.options.slidesToShow == _.options.slidesToScroll && (_.slideCount - index) < _.options.slidesToShow) {
allSlides
.slice(indexOffset - (_.options.slidesToShow - remainder), indexOffset + remainder)
.addClass('slick-active')
.attr('aria-hidden', 'false');
} else {
allSlides
.slice(indexOffset, indexOffset + _.options.slidesToShow)
.addClass('slick-active')
.attr('aria-hidden', 'false');
}
}
}
if (_.options.lazyLoad === 'ondemand') {
_.lazyLoad();
}
};
Slick.prototype.setupInfinite = function() {
var _ = this,
i, slideIndex, infiniteCount;
if (_.options.fade === true) {
_.options.centerMode = false;
}
if (_.options.infinite === true && _.options.fade === false) {
slideIndex = null;
if (_.slideCount > _.options.slidesToShow) {
if (_.options.centerMode === true) {
infiniteCount = _.options.slidesToShow + 1;
} else {
infiniteCount = _.options.slidesToShow;
}
for (i = _.slideCount; i > (_.slideCount -
infiniteCount); i -= 1) {
slideIndex = i - 1;
$(_.$slides[slideIndex]).clone(true).attr('id', '')
.attr('data-slick-index', slideIndex - _.slideCount)
.prependTo(_.$slideTrack).addClass('slick-cloned');
}
for (i = 0; i < infiniteCount; i += 1) {
slideIndex = i;
$(_.$slides[slideIndex]).clone(true).attr('id', '')
.attr('data-slick-index', slideIndex + _.slideCount)
.appendTo(_.$slideTrack).addClass('slick-cloned');
}
_.$slideTrack.find('.slick-cloned').find('[id]').each(function() {
$(this).attr('id', '');
});
}
}
};
Slick.prototype.interrupt = function( toggle ) {
var _ = this;
if( !toggle ) {
_.autoPlay();
}
_.interrupted = toggle;
};
Slick.prototype.selectHandler = function(event) {
var _ = this;
var targetElement =
$(event.target).is('.slick-slide') ?
$(event.target) :
$(event.target).parents('.slick-slide');
var index = parseInt(targetElement.attr('data-slick-index'));
if (!index) index = 0;
if (_.slideCount <= _.options.slidesToShow) {
_.setSlideClasses(index);
_.asNavFor(index);
return;
}
_.slideHandler(index);
};
Slick.prototype.slideHandler = function(index, sync, dontAnimate) {
var targetSlide, animSlide, oldSlide, slideLeft, targetLeft = null,
_ = this, navTarget;
sync = sync || false;
if (_.animating === true && _.options.waitForAnimate === true) {
return;
}
if (_.options.fade === true && _.currentSlide === index) {
return;
}
if (_.slideCount <= _.options.slidesToShow) {
return;
}
if (sync === false) {
_.asNavFor(index);
}
targetSlide = index;
targetLeft = _.getLeft(targetSlide);
slideLeft = _.getLeft(_.currentSlide);
_.currentLeft = _.swipeLeft === null ? slideLeft : _.swipeLeft;
if (_.options.infinite === false && _.options.centerMode === false && (index < 0 || index > _.getDotCount() * _.options.slidesToScroll)) {
if (_.options.fade === false) {
targetSlide = _.currentSlide;
if (dontAnimate !== true) {
_.animateSlide(slideLeft, function() {
_.postSlide(targetSlide);
});
} else {
_.postSlide(targetSlide);
}
}
return;
} else if (_.options.infinite === false && _.options.centerMode === true && (index < 0 || index > (_.slideCount - _.options.slidesToScroll))) {
if (_.options.fade === false) {
targetSlide = _.currentSlide;
if (dontAnimate !== true) {
_.animateSlide(slideLeft, function() {
_.postSlide(targetSlide);
});
} else {
_.postSlide(targetSlide);
}
}
return;
}
if ( _.options.autoplay ) {
clearInterval(_.autoPlayTimer);
}
if (targetSlide < 0) {
if (_.slideCount % _.options.slidesToScroll !== 0) {
animSlide = _.slideCount - (_.slideCount % _.options.slidesToScroll);
} else {
animSlide = _.slideCount + targetSlide;
}
} else if (targetSlide >= _.slideCount) {
if (_.slideCount % _.options.slidesToScroll !== 0) {
animSlide = 0;
} else {
animSlide = targetSlide - _.slideCount;
}
} else {
animSlide = targetSlide;
}
_.animating = true;
_.$slider.trigger('beforeChange', [_, _.currentSlide, animSlide]);
oldSlide = _.currentSlide;
_.currentSlide = animSlide;
_.setSlideClasses(_.currentSlide);
if ( _.options.asNavFor ) {
navTarget = _.getNavTarget();
navTarget = navTarget.slick('getSlick');
if ( navTarget.slideCount <= navTarget.options.slidesToShow ) {
navTarget.setSlideClasses(_.currentSlide);
}
}
_.updateDots();
_.updateArrows();
if (_.options.fade === true) {
if (dontAnimate !== true) {
_.fadeSlideOut(oldSlide);
_.fadeSlide(animSlide, function() {
_.postSlide(animSlide);
});
} else {
_.postSlide(animSlide);
}
_.animateHeight();
return;
}
if (dontAnimate !== true) {
_.animateSlide(targetLeft, function() {
_.postSlide(animSlide);
});
} else {
_.postSlide(animSlide);
}
};
Slick.prototype.startLoad = function() {
var _ = this;
if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {
_.$prevArrow.hide();
_.$nextArrow.hide();
}
if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
_.$dots.hide();
}
_.$slider.addClass('slick-loading');
};
Slick.prototype.swipeDirection = function() {
var xDist, yDist, r, swipeAngle, _ = this;
xDist = _.touchObject.startX - _.touchObject.curX;
yDist = _.touchObject.startY - _.touchObject.curY;
r = Math.atan2(yDist, xDist);
swipeAngle = Math.round(r * 180 / Math.PI);
if (swipeAngle < 0) {
swipeAngle = 360 - Math.abs(swipeAngle);
}
if ((swipeAngle <= 45) && (swipeAngle >= 0)) {
return (_.options.rtl === false ? 'left' : 'right');
}
if ((swipeAngle <= 360) && (swipeAngle >= 315)) {
return (_.options.rtl === false ? 'left' : 'right');
}
if ((swipeAngle >= 135) && (swipeAngle <= 225)) {
return (_.options.rtl === false ? 'right' : 'left');
}
if (_.options.verticalSwiping === true) {
if ((swipeAngle >= 35) && (swipeAngle <= 135)) {
return 'down';
} else {
return 'up';
}
}
return 'vertical';
};
Slick.prototype.swipeEnd = function(event) {
var _ = this,
slideCount,
direction;
_.dragging = false;
_.interrupted = false;
_.shouldClick = ( _.touchObject.swipeLength > 10 ) ? false : true;
if ( _.touchObject.curX === undefined ) {
return false;
}
if ( _.touchObject.edgeHit === true ) {
_.$slider.trigger('edge', [_, _.swipeDirection() ]);
}
if ( _.touchObject.swipeLength >= _.touchObject.minSwipe ) {
direction = _.swipeDirection();
switch ( direction ) {
case 'left':
case 'down':
slideCount =
_.options.swipeToSlide ?
_.checkNavigable( _.currentSlide + _.getSlideCount() ) :
_.currentSlide + _.getSlideCount();
_.currentDirection = 0;
break;
case 'right':
case 'up':
slideCount =
_.options.swipeToSlide ?
_.checkNavigable( _.currentSlide - _.getSlideCount() ) :
_.currentSlide - _.getSlideCount();
_.currentDirection = 1;
break;
default:
}
if( direction != 'vertical' ) {
_.slideHandler( slideCount );
_.touchObject = {};
_.$slider.trigger('swipe', [_, direction ]);
}
} else {
if ( _.touchObject.startX !== _.touchObject.curX ) {
_.slideHandler( _.currentSlide );
_.touchObject = {};
}
}
};
Slick.prototype.swipeHandler = function(event) {
var _ = this;
if ((_.options.swipe === false) || ('ontouchend' in document && _.options.swipe === false)) {
return;
} else if (_.options.draggable === false && event.type.indexOf('mouse') !== -1) {
return;
}
_.touchObject.fingerCount = event.originalEvent && event.originalEvent.touches !== undefined ?
event.originalEvent.touches.length : 1;
_.touchObject.minSwipe = _.listWidth / _.options
.touchThreshold;
if (_.options.verticalSwiping === true) {
_.touchObject.minSwipe = _.listHeight / _.options
.touchThreshold;
}
switch (event.data.action) {
case 'start':
_.swipeStart(event);
break;
case 'move':
_.swipeMove(event);
break;
case 'end':
_.swipeEnd(event);
break;
}
};
Slick.prototype.swipeMove = function(event) {
var _ = this,
edgeWasHit = false,
curLeft, swipeDirection, swipeLength, positionOffset, touches;
touches = event.originalEvent !== undefined ? event.originalEvent.touches : null;
if (!_.dragging || touches && touches.length !== 1) {
return false;
}
curLeft = _.getLeft(_.currentSlide);
_.touchObject.curX = touches !== undefined ? touches[0].pageX : event.clientX;
_.touchObject.curY = touches !== undefined ? touches[0].pageY : event.clientY;
_.touchObject.swipeLength = Math.round(Math.sqrt(
Math.pow(_.touchObject.curX - _.touchObject.startX, 2)));
if (_.options.verticalSwiping === true) {
_.touchObject.swipeLength = Math.round(Math.sqrt(
Math.pow(_.touchObject.curY - _.touchObject.startY, 2)));
}
swipeDirection = _.swipeDirection();
if (swipeDirection === 'vertical') {
return;
}
if (event.originalEvent !== undefined && _.touchObject.swipeLength > 4) {
event.preventDefault();
}
positionOffset = (_.options.rtl === false ? 1 : -1) * (_.touchObject.curX > _.touchObject.startX ? 1 : -1);
if (_.options.verticalSwiping === true) {
positionOffset = _.touchObject.curY > _.touchObject.startY ? 1 : -1;
}
swipeLength = _.touchObject.swipeLength;
_.touchObject.edgeHit = false;
if (_.options.infinite === false) {
if ((_.currentSlide === 0 && swipeDirection === 'right') || (_.currentSlide >= _.getDotCount() && swipeDirection === 'left')) {
swipeLength = _.touchObject.swipeLength * _.options.edgeFriction;
_.touchObject.edgeHit = true;
}
}
if (_.options.vertical === false) {
_.swipeLeft = curLeft + swipeLength * positionOffset;
} else {
_.swipeLeft = curLeft + (swipeLength * (_.$list.height() / _.listWidth)) * positionOffset;
}
if (_.options.verticalSwiping === true) {
_.swipeLeft = curLeft + swipeLength * positionOffset;
}
if (_.options.fade === true || _.options.touchMove === false) {
return false;
}
if (_.animating === true) {
_.swipeLeft = null;
return false;
}
_.setCSS(_.swipeLeft);
};
Slick.prototype.swipeStart = function(event) {
var _ = this,
touches;
_.interrupted = true;
if (_.touchObject.fingerCount !== 1 || _.slideCount <= _.options.slidesToShow) {
_.touchObject = {};
return false;
}
if (event.originalEvent !== undefined && event.originalEvent.touches !== undefined) {
touches = event.originalEvent.touches[0];
}
_.touchObject.startX = _.touchObject.curX = touches !== undefined ? touches.pageX : event.clientX;
_.touchObject.startY = _.touchObject.curY = touches !== undefined ? touches.pageY : event.clientY;
_.dragging = true;
};
Slick.prototype.unfilterSlides = Slick.prototype.slickUnfilter = function() {
var _ = this;
if (_.$slidesCache !== null) {
_.unload();
_.$slideTrack.children(this.options.slide).detach();
_.$slidesCache.appendTo(_.$slideTrack);
_.reinit();
}
};
Slick.prototype.unload = function() {
var _ = this;
$('.slick-cloned', _.$slider).remove();
if (_.$dots) {
_.$dots.remove();
}
if (_.$prevArrow && _.htmlExpr.test(_.options.prevArrow)) {
_.$prevArrow.remove();
}
if (_.$nextArrow && _.htmlExpr.test(_.options.nextArrow)) {
_.$nextArrow.remove();
}
_.$slides
.removeClass('slick-slide slick-active slick-visible slick-current')
.attr('aria-hidden', 'true')
.css('width', '');
};
Slick.prototype.unslick = function(fromBreakpoint) {
var _ = this;
_.$slider.trigger('unslick', [_, fromBreakpoint]);
_.destroy();
};
Slick.prototype.updateArrows = function() {
var _ = this,
centerOffset;
centerOffset = Math.floor(_.options.slidesToShow / 2);
if ( _.options.arrows === true &&
_.slideCount > _.options.slidesToShow &&
!_.options.infinite ) {
_.$prevArrow.removeClass('slick-disabled').attr('aria-disabled', 'false');
_.$nextArrow.removeClass('slick-disabled').attr('aria-disabled', 'false');
if (_.currentSlide === 0) {
_.$prevArrow.addClass('slick-disabled').attr('aria-disabled', 'true');
_.$nextArrow.removeClass('slick-disabled').attr('aria-disabled', 'false');
} else if (_.currentSlide >= _.slideCount - _.options.slidesToShow && _.options.centerMode === false) {
_.$nextArrow.addClass('slick-disabled').attr('aria-disabled', 'true');
_.$prevArrow.removeClass('slick-disabled').attr('aria-disabled', 'false');
} else if (_.currentSlide >= _.slideCount - 1 && _.options.centerMode === true) {
_.$nextArrow.addClass('slick-disabled').attr('aria-disabled', 'true');
_.$prevArrow.removeClass('slick-disabled').attr('aria-disabled', 'false');
}
}
};
Slick.prototype.updateDots = function() {
var _ = this;
if (_.$dots !== null) {
_.$dots
.find('li')
.removeClass('slick-active')
.attr('aria-hidden', 'true');
_.$dots
.find('li')
.eq(Math.floor(_.currentSlide / _.options.slidesToScroll))
.addClass('slick-active')
.attr('aria-hidden', 'false');
}
};
Slick.prototype.visibility = function() {
var _ = this;
if ( _.options.autoplay ) {
if ( document[_.hidden] ) {
_.interrupted = true;
} else {
_.interrupted = false;
}
}
};
$.fn.slick = function() {
var _ = this,
opt = arguments[0],
args = Array.prototype.slice.call(arguments, 1),
l = _.length,
i,
ret;
for (i = 0; i < l; i++) {
if (typeof opt == 'object' || typeof opt == 'undefined')
_[i].slick = new Slick(_[i], opt);
else
ret = _[i].slick[opt].apply(_[i].slick, args);
if (typeof ret != 'undefined') return ret;
}
return _;
};
}));
// Generated by CoffeeScript 1.10.0
/**
@license Sticky-kit v1.1.3 | MIT | Leaf Corcoran 2015 | http://leafo.net
*/
(function() {
var $, win;
$ = window.jQuery;
win = $(window);
$.fn.stick_in_parent = function(opts) {
var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class;
if (opts == null) {
opts = {};
}
sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming;
if (offset_top == null) {
offset_top = 0;
}
if (parent_selector == null) {
parent_selector = void 0;
}
if (inner_scrolling == null) {
inner_scrolling = true;
}
if (sticky_class == null) {
sticky_class = "is_stuck";
}
doc = $(document);
if (enable_bottoming == null) {
enable_bottoming = true;
}
outer_width = function(el) {
var _el, computed, w;
if (window.getComputedStyle) {
_el = el[0];
computed = window.getComputedStyle(el[0]);
w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"));
if (computed.getPropertyValue("box-sizing") !== "border-box") {
w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"));
}
return w;
} else {
return el.outerWidth(true);
}
};
fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) {
var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick;
if (elm.data("sticky_kit")) {
return;
}
elm.data("sticky_kit", true);
last_scroll_height = doc.height();
parent = elm.parent();
if (parent_selector != null) {
parent = parent.closest(parent_selector);
}
if (!parent.length) {
throw "failed to find stick parent";
}
fixed = false;
bottomed = false;
spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("<div />");
if (spacer) {
spacer.css('position', elm.css('position'));
}
recalc = function() {
var border_top, padding_top, restore;
if (detached) {
return;
}
last_scroll_height = doc.height();
border_top = parseInt(parent.css("border-top-width"), 10);
padding_top = parseInt(parent.css("padding-top"), 10);
padding_bottom = parseInt(parent.css("padding-bottom"), 10);
parent_top = parent.offset().top + border_top + padding_top;
parent_height = parent.height();
if (fixed) {
fixed = false;
bottomed = false;
if (manual_spacer == null) {
elm.insertAfter(spacer);
spacer.detach();
}
elm.css({
position: "",
top: "",
width: "",
bottom: ""
}).removeClass(sticky_class);
restore = true;
}
top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top;
height = elm.outerHeight(true);
el_float = elm.css("float");
if (spacer) {
spacer.css({
width: outer_width(elm),
height: height,
display: elm.css("display"),
"vertical-align": elm.css("vertical-align"),
"float": el_float
});
}
if (restore) {
return tick();
}
};
recalc();
if (height === parent_height) {
return;
}
last_pos = void 0;
offset = offset_top;
recalc_counter = recalc_every;
tick = function() {
var css, delta, recalced, scroll, will_bottom, win_height;
if (detached) {
return;
}
recalced = false;
if (recalc_counter != null) {
recalc_counter -= 1;
if (recalc_counter <= 0) {
recalc_counter = recalc_every;
recalc();
recalced = true;
}
}
if (!recalced && doc.height() !== last_scroll_height) {
recalc();
recalced = true;
}
scroll = win.scrollTop();
if (last_pos != null) {
delta = scroll - last_pos;
}
last_pos = scroll;
if (fixed) {
if (enable_bottoming) {
will_bottom = scroll + height + offset > parent_height + parent_top;
if (bottomed && !will_bottom) {
bottomed = false;
elm.css({
position: "fixed",
bottom: "",
top: offset
}).trigger("sticky_kit:unbottom");
}
}
if (scroll < top) {
fixed = false;
offset = offset_top;
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.detach();
}
css = {
position: "",
width: "",
top: ""
};
elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick");
}
if (inner_scrolling) {
win_height = win.height();
if (height + offset_top > win_height) {
if (!bottomed) {
offset -= delta;
offset = Math.max(win_height - height, offset);
offset = Math.min(offset_top, offset);
if (fixed) {
elm.css({
top: offset + "px"
});
}
}
}
}
} else {
if (scroll > top) {
fixed = true;
css = {
position: "fixed",
top: offset
};
css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px";
elm.css(css).addClass(sticky_class);
if (manual_spacer == null) {
elm.after(spacer);
if (el_float === "left" || el_float === "right") {
spacer.append(elm);
}
}
elm.trigger("sticky_kit:stick");
}
}
if (fixed && enable_bottoming) {
if (will_bottom == null) {
will_bottom = scroll + height + offset > parent_height + parent_top;
}
if (!bottomed && will_bottom) {
bottomed = true;
if (parent.css("position") === "static") {
parent.css({
position: "relative"
});
}
return elm.css({
position: "absolute",
bottom: padding_bottom,
top: "auto"
}).trigger("sticky_kit:bottom");
}
}
};
recalc_and_tick = function() {
recalc();
return tick();
};
detach = function() {
detached = true;
win.off("touchmove", tick);
win.off("scroll", tick);
win.off("resize", recalc_and_tick);
$(document.body).off("sticky_kit:recalc", recalc_and_tick);
elm.off("sticky_kit:detach", detach);
elm.removeData("sticky_kit");
elm.css({
position: "",
bottom: "",
top: "",
width: ""
});
parent.position("position", "");
if (fixed) {
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.remove();
}
return elm.removeClass(sticky_class);
}
};
win.on("touchmove", tick);
win.on("scroll", tick);
win.on("resize", recalc_and_tick);
$(document.body).on("sticky_kit:recalc", recalc_and_tick);
elm.on("sticky_kit:detach", detach);
return setTimeout(tick, 0);
};
for (i = 0, len = this.length; i < len; i++) {
elm = this[i];
fn($(elm));
}
return this;
};
}).call(this);
!function(e){"use strict";e.ThreeSixty=function(r,t){var a,n=this,o=[];n.$el=e(r),n.el=r,n.$el.data("ThreeSixty",n),n.init=function(){(a=e.extend({},e.ThreeSixty.defaultOptions,t)).disableSpin&&(a.currentFrame=1,a.endFrame=1),n.initProgress(),n.loadImages()},n.resize=function(){},n.initProgress=function(){n.$el.css({width:a.width+"px",height:a.height+"px","background-image":"none !important"}),a.styles&&n.$el.css(a.styles),n.responsive(),n.$el.find(a.progress).css({marginTop:a.height/2-15+"px"}),n.$el.find(a.progress).fadeIn("slow"),n.$el.find(a.imgList).hide()},n.loadImages=function(){var r,t,i,s;r=document.createElement("li"),s=a.zeroBased?0:1,t=a.imgArray?a.imgArray[a.loadedImages]:a.domain+a.imagePath+a.filePrefix+n.zeroPad(a.loadedImages+s)+a.ext+(n.browser.isIE()?"?"+(new Date).getTime():""),i=e("<img>").attr("src",t).addClass("previous-image").appendTo(r),o.push(i),n.$el.find(a.imgList).append(r),e(i).on("load",function(){n.imageLoaded()})},n.imageLoaded=function(){a.loadedImages+=1,e(a.progress+" span").text(Math.floor(a.loadedImages/a.totalFrames*100)+"%"),a.loadedImages>=a.totalFrames?(a.disableSpin&&o[0].removeClass("previous-image").addClass("current-image"),e(a.progress).fadeOut("slow",function(){e(this).hide(),n.showImages(),n.showNavigation()})):n.loadImages()},n.showImages=function(){n.$el.find(".txtC").fadeIn(),n.$el.find(a.imgList).fadeIn(),n.ready=!0,a.ready=!0,a.drag&&n.initEvents(),n.refresh(),n.initPlugins(),a.onReady(),setTimeout(function(){n.responsive()},50)},n.initPlugins=function(){e.each(a.plugins,function(r,t){if("function"!=typeof e[t])throw new Error(t+" not available.");e[t].call(n,n.$el,a)})},n.showNavigation=function(){var r,t,o,i;a.navigation&&!a.navigation_init&&(r=e("<div/>").attr("class","nav_bar"),t=e("<a/>").attr({href:"#",class:"nav_bar_next"}).html("next"),o=e("<a/>").attr({href:"#",class:"nav_bar_previous"}).html("previous"),i=e("<a/>").attr({href:"#",class:"nav_bar_play"}).html("play"),r.append(o),r.append(i),r.append(t),n.$el.prepend(r),t.bind("mousedown touchstart",n.next),o.bind("mousedown touchstart",n.previous),i.bind("mousedown touchstart",n.play_stop),a.navigation_init=!0)},n.play_stop=function(r){r.preventDefault(),a.autoplay?(a.autoplay=!1,e(r.currentTarget).removeClass("nav_bar_stop").addClass("nav_bar_play"),clearInterval(a.play),a.play=null):(a.autoplay=!0,a.play=setInterval(n.moveToNextFrame,a.playSpeed),e(r.currentTarget).removeClass("nav_bar_play").addClass("nav_bar_stop"))},n.next=function(e){e&&e.preventDefault(),a.endFrame-=5,n.refresh()},n.previous=function(e){e&&e.preventDefault(),a.endFrame+=5,n.refresh()},n.play=function(e,r){var t=e||a.playSpeed,o=r||a.autoplayDirection;a.autoplayDirection=o,a.autoplay||(a.autoplay=!0,a.play=setInterval(n.moveToNextFrame,t))},n.stop=function(){a.autoplay&&(a.autoplay=!1,clearInterval(a.play),a.play=null)},n.moveToNextFrame=function(){1===a.autoplayDirection?a.endFrame-=1:a.endFrame+=1,n.refresh()},n.gotoAndPlay=function(e){if(a.disableWrap)a.endFrame=e,n.refresh();else{var r=Math.ceil(a.endFrame/a.totalFrames);0===r&&(r=1);var t,o=r>1?a.endFrame-(r-1)*a.totalFrames:a.endFrame,i=a.totalFrames-o;t=e-o>0?e-o<o+(a.totalFrames-e)?a.endFrame+(e-o):a.endFrame-(o+(a.totalFrames-e)):i+e>o-e?a.endFrame-(o-e):a.endFrame+(i+e),o!==e&&(a.endFrame=t,n.refresh())}},n.initEvents=function(){n.$el.bind("mousedown touchstart touchmove touchend mousemove click",function(e){e.preventDefault(),"mousedown"===e.type&&1===e.which||"touchstart"===e.type?(a.pointerStartPosX=n.getPointerEvent(e).pageX,a.dragging=!0,a.onDragStart(a.currentFrame)):"touchmove"===e.type?n.trackPointer(e):"touchend"===e.type&&(a.dragging=!1,a.onDragStop(a.endFrame))}),e(document).bind("mouseup",function(r){a.dragging=!1,a.onDragStop(a.endFrame),e(this).css("cursor","none")}),e(window).bind("resize",function(e){n.responsive()}),e(document).bind("mousemove",function(e){a.dragging?(e.preventDefault(),!n.browser.isIE&&a.showCursor&&n.$el.css("cursor","url(assets/images/hand_closed.png), auto")):!n.browser.isIE&&a.showCursor&&n.$el.css("cursor","url(assets/images/hand_open.png), auto"),n.trackPointer(e)}),e(window).on('resize',function(){n.resize()})},n.getPointerEvent=function(e){return e.originalEvent.targetTouches?e.originalEvent.targetTouches[0]:e},n.trackPointer=function(e){a.ready&&a.dragging&&(a.pointerEndPosX=n.getPointerEvent(e).pageX,a.monitorStartTime<(new Date).getTime()-a.monitorInt&&(a.pointerDistance=a.pointerEndPosX-a.pointerStartPosX,a.pointerDistance>0?a.endFrame=a.currentFrame+Math.ceil((a.totalFrames-1)*a.speedMultiplier*(a.pointerDistance/n.$el.width())):a.endFrame=a.currentFrame+Math.floor((a.totalFrames-1)*a.speedMultiplier*(a.pointerDistance/n.$el.width())),a.disableWrap&&(a.endFrame=Math.min(a.totalFrames-(a.zeroBased?1:0),a.endFrame),a.endFrame=Math.max(a.zeroBased?0:1,a.endFrame)),n.refresh(),a.monitorStartTime=(new Date).getTime(),a.pointerStartPosX=n.getPointerEvent(e).pageX))},n.refresh=function(){0===a.ticker&&(a.ticker=setInterval(n.render,Math.round(1e3/a.framerate)))},n.render=function(){var e;a.currentFrame!==a.endFrame?(e=a.endFrame<a.currentFrame?Math.floor(.1*(a.endFrame-a.currentFrame)):Math.ceil(.1*(a.endFrame-a.currentFrame)),n.hidePreviousFrame(),a.currentFrame+=e,n.showCurrentFrame(),n.$el.trigger("frameIndexChanged",[n.getNormalizedCurrentFrame(),a.totalFrames])):(window.clearInterval(a.ticker),a.ticker=0)},n.hidePreviousFrame=function(){o[n.getNormalizedCurrentFrame()].removeClass("current-image").addClass("previous-image")},n.showCurrentFrame=function(){o[n.getNormalizedCurrentFrame()].removeClass("previous-image").addClass("current-image")},n.getNormalizedCurrentFrame=function(){var e,r;return a.disableWrap?(e=Math.min(a.currentFrame,a.totalFrames-(a.zeroBased?1:0)),r=Math.min(a.endFrame,a.totalFrames-(a.zeroBased?1:0)),e=Math.max(e,a.zeroBased?0:1),r=Math.max(r,a.zeroBased?0:1),a.currentFrame=e,a.endFrame=r):0>(e=Math.ceil(a.currentFrame%a.totalFrames))&&(e+=a.totalFrames-(a.zeroBased?1:0)),e},n.getCurrentFrame=function(){return a.currentFrame},n.responsive=function(){a.responsive&&n.$el.css({height:n.$el.find(".current-image").first().css("height"),width:"100%"})},n.zeroPad=function(e){var r=Math.log(a.totalFrames)/Math.LN10,t=Math.round(1e3*r)/1e3;return function(e,r){var t=e.toString();if(a.zeroPadding)for(;t.length<r;)t="0"+t;return t}(e,Math.floor(t)+1)},n.browser={},n.browser.isIE=function(){var e=-1;if("Microsoft Internet Explorer"===navigator.appName){var r=navigator.userAgent;null!==new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})").exec(r)&&(e=parseFloat(RegExp.$1))}return-1!==e},n.getConfig=function(){return a},e.ThreeSixty.defaultOptions={dragging:!1,ready:!1,pointerStartPosX:0,pointerEndPosX:0,pointerDistance:0,monitorStartTime:0,monitorInt:10,ticker:0,speedMultiplier:7,totalFrames:180,currentFrame:0,endFrame:0,loadedImages:0,framerate:60,domains:null,domain:"",parallel:!1,queueAmount:8,idle:0,filePrefix:"",ext:"png",height:300,width:300,styles:{},navigation:!1,autoplay:!1,autoplayDirection:1,disableSpin:!1,disableWrap:!1,responsive:!1,zeroPadding:!1,zeroBased:!1,plugins:[],showCursor:!1,drag:!0,onReady:function(){},onDragStart:function(){},onDragStop:function(){},imgList:".threesixty_images",imgArray:null,playSpeed:100},n.init()},e.fn.ThreeSixty=function(r){return Object.create(new e.ThreeSixty(this,r))}}(jQuery),"function"!=typeof Object.create&&(Object.create=function(e){"use strict";function r(){}return r.prototype=e,new r});
/*!
* Bootstrap v4.0.0 (https://getbootstrap.com)
* Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery')) :
typeof define === 'function' && define.amd ? define([
'exports',
'jquery'
], factory) :
(factory((global.bootstrap = {}), global.jQuery));
}(this, (function(exports, $) {
'use strict';
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) {
descriptor.writable = true;
}
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) {
_defineProperties(Constructor.prototype, protoProps);
}
if (staticProps) {
_defineProperties(Constructor, staticProps);
}
return Constructor;
}
function _extends() {
_extends = Object.assign || function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0): util.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
var Util = function($$$1) {
/**
* ------------------------------------------------------------------------
* Private TransitionEnd Helpers
* ------------------------------------------------------------------------
*/
var transition = false;
var MAX_UID = 1000000; // Shoutout AngusCroll (https://goo.gl/pxwQGp)
function toType(obj) {
return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}
function getSpecialTransitionEndEvent() {
return {
bindType : transition.end,
delegateType: transition.end,
handle : function handle(event) {
if ($$$1(event.target).is(this)) {
return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params
}
return undefined; // eslint-disable-line no-undefined
}
};
}
function transitionEndTest() {
if (typeof window !== 'undefined' && window.QUnit) {
return false;
}
return {
end: 'transitionend'
};
}
function transitionEndEmulator(duration) {
var _this = this;
var called = false;
$$$1(this).one(Util.TRANSITION_END, function() {
called = true;
});
setTimeout(function() {
if (!called) {
Util.triggerTransitionEnd(_this);
}
}, duration);
return this;
}
function setTransitionEndSupport() {
transition = transitionEndTest();
$$$1.fn.emulateTransitionEnd = transitionEndEmulator;
if (Util.supportsTransitionEnd()) {
$$$1.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent();
}
}
function escapeId(selector) {
// We escape IDs in case of special selectors (selector = '#myId:something')
// $.escapeSelector does not exist in jQuery < 3
selector = typeof $$$1.escapeSelector === 'function' ? $$$1.escapeSelector(selector).substr(1) : selector.replace(/(:|\.|\[|\]|,|=|@)/g, '\\$1');
return selector;
}
/**
* --------------------------------------------------------------------------
* Public Util Api
* --------------------------------------------------------------------------
*/
var Util = {
TRANSITION_END : 'bsTransitionEnd',
getUID : function getUID(prefix) {
do {
// eslint-disable-next-line no-bitwise
prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here
} while (document.getElementById(prefix));
return prefix;
},
getSelectorFromElement: function getSelectorFromElement(element) {
var selector = element.getAttribute('data-target');
if (!selector || selector === '#') {
selector = element.getAttribute('href') || '';
} // If it's an ID
if (selector.charAt(0) === '#') {
selector = escapeId(selector);
}
try {
var $selector = $$$1(document).find(selector);
return $selector.length > 0 ? selector : null;
}
catch (err) {
return null;
}
},
reflow : function reflow(element) {
return element.offsetHeight;
},
triggerTransitionEnd : function triggerTransitionEnd(element) {
$$$1(element).trigger(transition.end);
},
supportsTransitionEnd : function supportsTransitionEnd() {
return Boolean(transition);
},
isElement : function isElement(obj) {
return (obj[0] || obj).nodeType;
},
typeCheckConfig : function typeCheckConfig(componentName, config, configTypes) {
for (var property in configTypes) {
if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
var expectedTypes = configTypes[property];
var value = config[property];
var valueType = value && Util.isElement(value) ? 'element' : toType(value);
if (!new RegExp(expectedTypes).test(valueType)) {
throw new Error(componentName.toUpperCase() + ': ' + ('Option "' + property + '" provided type "' + valueType + '" ') + ('but expected type "' + expectedTypes + '".'));
}
}
}
}
};
setTransitionEndSupport();
return Util;
}($);
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.12.9
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
var longerTimeoutBrowsers = [
'Edge',
'Trident',
'Firefox'
];
var timeoutDuration = 0;
for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
timeoutDuration = 1;
break;
}
}
function microtaskDebounce(fn) {
var called = false;
return function() {
if (called) {
return;
}
called = true;
window.Promise.resolve().then(function() {
called = false;
fn();
});
};
}
function taskDebounce(fn) {
var scheduled = false;
return function() {
if (!scheduled) {
scheduled = true;
setTimeout(function() {
scheduled = false;
fn();
}, timeoutDuration);
}
};
}
var supportsMicroTasks = isBrowser && window.Promise;
/**
* Create a debounced version of a method, that's asynchronously deferred
* but called in the minimum time possible.
*
* @method
* @memberof Popper.Utils
* @argument {Function} fn
* @returns {Function}
*/
var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
/**
* Check if the given variable is a function
* @method
* @memberof Popper.Utils
* @argument {Any} functionToCheck - variable to check
* @returns {Boolean} answer to: is a function?
*/
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
/**
* Get CSS computed property of the given element
* @method
* @memberof Popper.Utils
* @argument {Eement} element
* @argument {String} property
*/
function getStyleComputedProperty(element, property) {
if (element.nodeType !== 1) {
return [];
}
// NOTE: 1 DOM access here
var css = getComputedStyle(element, null);
return property ? css[property] : css;
}
/**
* Returns the parentNode or the host of the element
* @method
* @memberof Popper.Utils
* @argument {Element} element
* @returns {Element} parent
*/
function getParentNode(element) {
if (element.nodeName === 'HTML') {
return element;
}
return element.parentNode || element.host;
}
/**
* Returns the scrolling parent of the given element
* @method
* @memberof Popper.Utils
* @argument {Element} element
* @returns {Element} scroll parent
*/
function getScrollParent(element) {
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
if (!element) {
return document.body;
}
switch (element.nodeName) {
case 'HTML':
case 'BODY':
return element.ownerDocument.body;
case '#document':
return element.body;
}
// Firefox want us to check `-x` and `-y` variations as well
var _getStyleComputedProp = getStyleComputedProperty(element),
overflow = _getStyleComputedProp.overflow,
overflowX = _getStyleComputedProp.overflowX,
overflowY = _getStyleComputedProp.overflowY;
if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) {
return element;
}
return getScrollParent(getParentNode(element));
}
/**
* Returns the offset parent of the given element
* @method
* @memberof Popper.Utils
* @argument {Element} element
* @returns {Element} offset parent
*/
function getOffsetParent(element) {
// NOTE: 1 DOM access here
var offsetParent = element && element.offsetParent;
var nodeName = offsetParent && offsetParent.nodeName;
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
if (element) {
return element.ownerDocument.documentElement;
}
return document.documentElement;
}
// .offsetParent will return the closest TD or TABLE in case
// no offsetParent is present, I hate this job...
if ([
'TD',
'TABLE'
].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
return getOffsetParent(offsetParent);
}
return offsetParent;
}
function isOffsetContainer(element) {
var nodeName = element.nodeName;
if (nodeName === 'BODY') {
return false;
}
return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
}
/**
* Finds the root node (document, shadowDOM root) of the given element
* @method
* @memberof Popper.Utils
* @argument {Element} node
* @returns {Element} root node
*/
function getRoot(node) {
if (node.parentNode !== null) {
return getRoot(node.parentNode);
}
return node;
}
/**
* Finds the offset parent common to the two provided nodes
* @method
* @memberof Popper.Utils
* @argument {Element} element1
* @argument {Element} element2
* @returns {Element} common offset parent
*/
function findCommonOffsetParent(element1, element2) {
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
return document.documentElement;
}
// Here we make sure to give as "start" the element that comes first in the DOM
var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
var start = order ? element1 : element2;
var end = order ? element2 : element1;
// Get common ancestor container
var range = document.createRange();
range.setStart(start, 0);
range.setEnd(end, 0);
var commonAncestorContainer = range.commonAncestorContainer;
// Both nodes are inside #document
if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
if (isOffsetContainer(commonAncestorContainer)) {
return commonAncestorContainer;
}
return getOffsetParent(commonAncestorContainer);
}
// one of the nodes is inside shadowDOM, find which one
var element1root = getRoot(element1);
if (element1root.host) {
return findCommonOffsetParent(element1root.host, element2);
} else {
return findCommonOffsetParent(element1, getRoot(element2).host);
}
}
/**
* Gets the scroll value of the given element in the given side (top and left)
* @method
* @memberof Popper.Utils
* @argument {Element} element
* @argument {String} side `top` or `left`
* @returns {number} amount of scrolled pixels
*/
function getScroll(element) {
var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
var nodeName = element.nodeName;
if (nodeName === 'BODY' || nodeName === 'HTML') {
var html = element.ownerDocument.documentElement;
var scrollingElement = element.ownerDocument.scrollingElement || html;
return scrollingElement[upperSide];
}
return element[upperSide];
}
/*
* Sum or subtract the element scroll values (left and top) from a given rect object
* @method
* @memberof Popper.Utils
* @param {Object} rect - Rect object you want to change
* @param {HTMLElement} element - The element from the function reads the scroll values
* @param {Boolean} subtract - set to true if you want to subtract the scroll values
* @return {Object} rect - The modifier rect object
*/
function includeScroll(rect, element) {
var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var scrollTop = getScroll(element, 'top');
var scrollLeft = getScroll(element, 'left');
var modifier = subtract ? -1 : 1;
rect.top += scrollTop * modifier;
rect.bottom += scrollTop * modifier;
rect.left += scrollLeft * modifier;
rect.right += scrollLeft * modifier;
return rect;
}
/*
* Helper to detect borders of a given element
* @method
* @memberof Popper.Utils
* @param {CSSStyleDeclaration} styles
* Result of `getStyleComputedProperty` on the given element
* @param {String} axis - `x` or `y`
* @return {number} borders - The borders size of the given axis
*/
function getBordersSize(styles, axis) {
var sideA = axis === 'x' ? 'Left' : 'Top';
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10);
}
/**
* Tells if you are running Internet Explorer 10
* @method
* @memberof Popper.Utils
* @returns {Boolean} isIE10
*/
var isIE10 = undefined;
var isIE10$1 = function() {
if (isIE10 === undefined) {
isIE10 = navigator.appVersion.indexOf('MSIE 10') !== -1;
}
return isIE10;
};
function getSize(axis, body, html, computedStyle) {
return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE10$1() ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0);
}
function getWindowSizes() {
var body = document.body;
var html = document.documentElement;
var computedStyle = isIE10$1() && getComputedStyle(html);
return {
height: getSize('Height', body, html, computedStyle),
width : getSize('Width', body, html, computedStyle)
};
}
var classCallCheck = function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
};
var createClass = function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) {
descriptor.writable = true;
}
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if (protoProps) {
defineProperties(Constructor.prototype, protoProps);
}
if (staticProps) {
defineProperties(Constructor, staticProps);
}
return Constructor;
};
}();
var defineProperty = function(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value : value,
enumerable : true,
configurable: true,
writable : true
});
} else {
obj[key] = value;
}
return obj;
};
var _extends$1 = Object.assign || function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
/**
* Given element offsets, generate an output similar to getBoundingClientRect
* @method
* @memberof Popper.Utils
* @argument {Object} offsets
* @returns {Object} ClientRect like output
*/
function getClientRect(offsets) {
return _extends$1({}, offsets, {
right : offsets.left + offsets.width,
bottom: offsets.top + offsets.height
});
}
/**
* Get bounding client rect of given element
* @method
* @memberof Popper.Utils
* @param {HTMLElement} element
* @return {Object} client rect
*/
function getBoundingClientRect(element) {
var rect = {};
// IE10 10 FIX: Please, don't ask, the element isn't
// considered in DOM in some circumstances...
// This isn't reproducible in IE10 compatibility mode of IE11
if (isIE10$1()) {
try {
rect = element.getBoundingClientRect();
var scrollTop = getScroll(element, 'top');
var scrollLeft = getScroll(element, 'left');
rect.top += scrollTop;
rect.left += scrollLeft;
rect.bottom += scrollTop;
rect.right += scrollLeft;
}
catch (err) {
}
} else {
rect = element.getBoundingClientRect();
}
var result = {
left : rect.left,
top : rect.top,
width : rect.right - rect.left,
height: rect.bottom - rect.top
};
// subtract scrollbar size from sizes
var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {};
var width = sizes.width || element.clientWidth || result.right - result.left;
var height = sizes.height || element.clientHeight || result.bottom - result.top;
var horizScrollbar = element.offsetWidth - width;
var vertScrollbar = element.offsetHeight - height;
// if an hypothetical scrollbar is detected, we must be sure it's not a `border`
// we make this check conditional for performance reasons
if (horizScrollbar || vertScrollbar) {
var styles = getStyleComputedProperty(element);
horizScrollbar -= getBordersSize(styles, 'x');
vertScrollbar -= getBordersSize(styles, 'y');
result.width -= horizScrollbar;
result.height -= vertScrollbar;
}
return getClientRect(result);
}
function getOffsetRectRelativeToArbitraryNode(children, parent) {
var isIE10 = isIE10$1();
var isHTML = parent.nodeName === 'HTML';
var childrenRect = getBoundingClientRect(children);
var parentRect = getBoundingClientRect(parent);
var scrollParent = getScrollParent(children);
var styles = getStyleComputedProperty(parent);
var borderTopWidth = parseFloat(styles.borderTopWidth, 10);
var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
var offsets = getClientRect({
top : childrenRect.top - parentRect.top - borderTopWidth,
left : childrenRect.left - parentRect.left - borderLeftWidth,
width : childrenRect.width,
height: childrenRect.height
});
offsets.marginTop = 0;
offsets.marginLeft = 0;
// Subtract margins of documentElement in case it's being used as parent
// we do this only on HTML because it's the only element that behaves
// differently when margins are applied to it. The margins are included in
// the box of the documentElement, in the other cases not.
if (!isIE10 && isHTML) {
var marginTop = parseFloat(styles.marginTop, 10);
var marginLeft = parseFloat(styles.marginLeft, 10);
offsets.top -= borderTopWidth - marginTop;
offsets.bottom -= borderTopWidth - marginTop;
offsets.left -= borderLeftWidth - marginLeft;
offsets.right -= borderLeftWidth - marginLeft;
// Attach marginTop and marginLeft because in some circumstances we may need them
offsets.marginTop = marginTop;
offsets.marginLeft = marginLeft;
}
if (isIE10 ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
offsets = includeScroll(offsets, parent);
}
return offsets;
}
function getViewportOffsetRectRelativeToArtbitraryNode(element) {
var html = element.ownerDocument.documentElement;
var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
var width = Math.max(html.clientWidth, window.innerWidth || 0);
var height = Math.max(html.clientHeight, window.innerHeight || 0);
var scrollTop = getScroll(html);
var scrollLeft = getScroll(html, 'left');
var offset = {
top : scrollTop - relativeOffset.top + relativeOffset.marginTop,
left : scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
width : width,
height: height
};
return getClientRect(offset);
}
/**
* Check if the given element is fixed or is inside a fixed parent
* @method
* @memberof Popper.Utils
* @argument {Element} element
* @argument {Element} customContainer
* @returns {Boolean} answer to "isFixed?"
*/
function isFixed(element) {
var nodeName = element.nodeName;
if (nodeName === 'BODY' || nodeName === 'HTML') {
return false;
}
if (getStyleComputedProperty(element, 'position') === 'fixed') {
return true;
}
return isFixed(getParentNode(element));
}
/**
* Computed the boundaries limits and return them
* @method
* @memberof Popper.Utils
* @param {HTMLElement} popper
* @param {HTMLElement} reference
* @param {number} padding
* @param {HTMLElement} boundariesElement - Element used to define the boundaries
* @returns {Object} Coordinates of the boundaries
*/
function getBoundaries(popper, reference, padding, boundariesElement) {
// NOTE: 1 DOM access here
var boundaries = {
top : 0,
left: 0
};
var offsetParent = findCommonOffsetParent(popper, reference);
// Handle viewport case
if (boundariesElement === 'viewport') {
boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent);
} else {
// Handle other cases based on DOM element used as boundaries
var boundariesNode = void 0;
if (boundariesElement === 'scrollParent') {
boundariesNode = getScrollParent(getParentNode(reference));
if (boundariesNode.nodeName === 'BODY') {
boundariesNode = popper.ownerDocument.documentElement;
}
} else if (boundariesElement === 'window') {
boundariesNode = popper.ownerDocument.documentElement;
} else {
boundariesNode = boundariesElement;
}
var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent);
// In case of HTML, we need a different computation
if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
var _getWindowSizes = getWindowSizes(),
height = _getWindowSizes.height,
width = _getWindowSizes.width;
boundaries.top += offsets.top - offsets.marginTop;
boundaries.bottom = height + offsets.top;
boundaries.left += offsets.left - offsets.marginLeft;
boundaries.right = width + offsets.left;
} else {
// for all the other DOM elements, this one is good
boundaries = offsets;
}
}
// Add paddings
boundaries.left += padding;
boundaries.top += padding;
boundaries.right -= padding;
boundaries.bottom -= padding;
return boundaries;
}
function getArea(_ref) {
var width = _ref.width,
height = _ref.height;
return width * height;
}
/**
* Utility used to transform the `auto` placement to the placement with more
* available space.
* @method
* @memberof Popper.Utils
* @argument {Object} data - The data object generated by update method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) {
var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
if (placement.indexOf('auto') === -1) {
return placement;
}
var boundaries = getBoundaries(popper, reference, padding, boundariesElement);
var rects = {
top : {
width : boundaries.width,
height: refRect.top - boundaries.top
},
right : {
width : boundaries.right - refRect.right,
height: boundaries.height
},
bottom: {
width : boundaries.width,
height: boundaries.bottom - refRect.bottom
},
left : {
width : refRect.left - boundaries.left,
height: boundaries.height
}
};
var sortedAreas = Object.keys(rects).map(function(key) {
return _extends$1({
key: key
}, rects[key], {
area: getArea(rects[key])
});
}).sort(function(a, b) {
return b.area - a.area;
});
var filteredAreas = sortedAreas.filter(function(_ref2) {
var width = _ref2.width,
height = _ref2.height;
return width >= popper.clientWidth && height >= popper.clientHeight;
});
var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
var variation = placement.split('-')[1];
return computedPlacement + (variation ? '-' + variation : '');
}
/**
* Get offsets to the reference element
* @method
* @memberof Popper.Utils
* @param {Object} state
* @param {Element} popper - the popper element
* @param {Element} reference - the reference element (the popper will be relative to this)
* @returns {Object} An object containing the offsets which will be applied to the popper
*/
function getReferenceOffsets(state, popper, reference) {
var commonOffsetParent = findCommonOffsetParent(popper, reference);
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent);
}
/**
* Get the outer sizes of the given element (offset size + margins)
* @method
* @memberof Popper.Utils
* @argument {Element} element
* @returns {Object} object containing width and height properties
*/
function getOuterSizes(element) {
var styles = getComputedStyle(element);
var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
var result = {
width : element.offsetWidth + y,
height: element.offsetHeight + x
};
return result;
}
/**
* Get the opposite placement of the given one
* @method
* @memberof Popper.Utils
* @argument {String} placement
* @returns {String} flipped placement
*/
function getOppositePlacement(placement) {
var hash = {
left : 'right',
right : 'left',
bottom: 'top',
top : 'bottom'
};
return placement.replace(/left|right|bottom|top/g, function(matched) {
return hash[matched];
});
}
/**
* Get offsets to the popper
* @method
* @memberof Popper.Utils
* @param {Object} position - CSS position the Popper will get applied
* @param {HTMLElement} popper - the popper element
* @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
* @param {String} placement - one of the valid placement options
* @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
*/
function getPopperOffsets(popper, referenceOffsets, placement) {
placement = placement.split('-')[0];
// Get popper node sizes
var popperRect = getOuterSizes(popper);
// Add position, width and height to our offsets object
var popperOffsets = {
width : popperRect.width,
height: popperRect.height
};
// depending by the popper placement we have to compute its offsets slightly differently
var isHoriz = [
'right',
'left'
].indexOf(placement) !== -1;
var mainSide = isHoriz ? 'top' : 'left';
var secondarySide = isHoriz ? 'left' : 'top';
var measurement = isHoriz ? 'height' : 'width';
var secondaryMeasurement = !isHoriz ? 'height' : 'width';
popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
if (placement === secondarySide) {
popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
} else {
popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
}
return popperOffsets;
}
/**
* Mimics the `find` method of Array
* @method
* @memberof Popper.Utils
* @argument {Array} arr
* @argument prop
* @argument value
* @returns index or -1
*/
function find(arr, check) {
// use native find if supported
if (Array.prototype.find) {
return arr.find(check);
}
// use `filter` to obtain the same behavior of `find`
return arr.filter(check)[0];
}
/**
* Return the index of the matching object
* @method
* @memberof Popper.Utils
* @argument {Array} arr
* @argument prop
* @argument value
* @returns index or -1
*/
function findIndex(arr, prop, value) {
// use native findIndex if supported
if (Array.prototype.findIndex) {
return arr.findIndex(function(cur) {
return cur[prop] === value;
});
}
// use `find` + `indexOf` if `findIndex` isn't supported
var match = find(arr, function(obj) {
return obj[prop] === value;
});
return arr.indexOf(match);
}
/**
* Loop trough the list of modifiers and run them in order,
* each of them will then edit the data object.
* @method
* @memberof Popper.Utils
* @param {dataObject} data
* @param {Array} modifiers
* @param {String} ends - Optional modifier name used as stopper
* @returns {dataObject}
*/
function runModifiers(modifiers, data, ends) {
var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
modifiersToRun.forEach(function(modifier) {
if (modifier['function']) {
// eslint-disable-line dot-notation
console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
}
var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
if (modifier.enabled && isFunction(fn)) {
// Add properties to offsets to make them a complete clientRect object
// we do this before each modifier to make sure the previous one doesn't
// mess with these values
data.offsets.popper = getClientRect(data.offsets.popper);
data.offsets.reference = getClientRect(data.offsets.reference);
data = fn(data, modifier);
}
});
return data;
}
/**
* Updates the position of the popper, computing the new offsets and applying
* the new style.<br />
* Prefer `scheduleUpdate` over `update` because of performance reasons.
* @method
* @memberof Popper
*/
function update() {
// if popper is destroyed, don't perform any further update
if (this.state.isDestroyed) {
return;
}
var data = {
instance : this,
styles : {},
arrowStyles: {},
attributes : {},
flipped : false,
offsets : {}
};
// compute reference element offsets
data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference);
// compute auto placement, store placement inside the data object,
// modifiers will be able to edit `placement` if needed
// and refer to originalPlacement to know the original value
data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding);
// store the computed placement inside `originalPlacement`
data.originalPlacement = data.placement;
// compute the popper offsets
data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement);
data.offsets.popper.position = 'absolute';
// run the modifiers
data = runModifiers(this.modifiers, data);
// the first `update` will call `onCreate` callback
// the other ones will call `onUpdate` callback
if (!this.state.isCreated) {
this.state.isCreated = true;
this.options.onCreate(data);
} else {
this.options.onUpdate(data);
}
}
/**
* Helper used to know if the given modifier is enabled.
* @method
* @memberof Popper.Utils
* @returns {Boolean}
*/
function isModifierEnabled(modifiers, modifierName) {
return modifiers.some(function(_ref) {
var name = _ref.name,
enabled = _ref.enabled;
return enabled && name === modifierName;
});
}
/**
* Get the prefixed supported property name
* @method
* @memberof Popper.Utils
* @argument {String} property (camelCase)
* @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
*/
function getSupportedPropertyName(property) {
var prefixes = [
false,
'ms',
'Webkit',
'Moz',
'O'
];
var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
for (var i = 0; i < prefixes.length - 1; i++) {
var prefix = prefixes[i];
var toCheck = prefix ? '' + prefix + upperProp : property;
if (typeof document.body.style[toCheck] !== 'undefined') {
return toCheck;
}
}
return null;
}
/**
* Destroy the popper
* @method
* @memberof Popper
*/
function destroy() {
this.state.isDestroyed = true;
// touch DOM only if `applyStyle` modifier is enabled
if (isModifierEnabled(this.modifiers, 'applyStyle')) {
this.popper.removeAttribute('x-placement');
this.popper.style.left = '';
this.popper.style.position = '';
this.popper.style.top = '';
this.popper.style[getSupportedPropertyName('transform')] = '';
}
this.disableEventListeners();
// remove the popper if user explicity asked for the deletion on destroy
// do not use `remove` because IE11 doesn't support it
if (this.options.removeOnDestroy) {
this.popper.parentNode.removeChild(this.popper);
}
return this;
}
/**
* Get the window associated with the element
* @argument {Element} element
* @returns {Window}
*/
function getWindow(element) {
var ownerDocument = element.ownerDocument;
return ownerDocument ? ownerDocument.defaultView : window;
}
function attachToScrollParents(scrollParent, event, callback, scrollParents) {
var isBody = scrollParent.nodeName === 'BODY';
var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
target.addEventListener(event, callback, {passive: true});
if (!isBody) {
attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
}
scrollParents.push(target);
}
/**
* Setup needed event listeners used to update the popper position
* @method
* @memberof Popper.Utils
* @private
*/
function setupEventListeners(reference, options, state, updateBound) {
// Resize event listener on window
state.updateBound = updateBound;
getWindow(reference).addEventListener('resize', state.updateBound, {passive: true});
// Scroll event listener on scroll parents
var scrollElement = getScrollParent(reference);
attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
state.scrollElement = scrollElement;
state.eventsEnabled = true;
return state;
}
/**
* It will add resize/scroll events and start recalculating
* position of the popper element when they are triggered.
* @method
* @memberof Popper
*/
function enableEventListeners() {
if (!this.state.eventsEnabled) {
this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate);
}
}
/**
* Remove event listeners used to update the popper position
* @method
* @memberof Popper.Utils
* @private
*/
function removeEventListeners(reference, state) {
// Remove resize event listener on window
getWindow(reference).removeEventListener('resize', state.updateBound);
// Remove scroll event listener on scroll parents
state.scrollParents.forEach(function(target) {
target.removeEventListener('scroll', state.updateBound);
});
// Reset state
state.updateBound = null;
state.scrollParents = [];
state.scrollElement = null;
state.eventsEnabled = false;
return state;
}
/**
* It will remove resize/scroll events and won't recalculate popper position
* when they are triggered. It also won't trigger onUpdate callback anymore,
* unless you call `update` method manually.
* @method
* @memberof Popper
*/
function disableEventListeners() {
if (this.state.eventsEnabled) {
cancelAnimationFrame(this.scheduleUpdate);
this.state = removeEventListeners(this.reference, this.state);
}
}
/**
* Tells if a given input is a number
* @method
* @memberof Popper.Utils
* @param {*} input to check
* @return {Boolean}
*/
function isNumeric(n) {
return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
}
/**
* Set the style to the given popper
* @method
* @memberof Popper.Utils
* @argument {Element} element - Element to apply the style to
* @argument {Object} styles
* Object with a list of properties and values which will be applied to the element
*/
function setStyles(element, styles) {
Object.keys(styles).forEach(function(prop) {
var unit = '';
// add unit if the value is numeric and is one of the following
if ([
'width',
'height',
'top',
'right',
'bottom',
'left'
].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
unit = 'px';
}
element.style[prop] = styles[prop] + unit;
});
}
/**
* Set the attributes to the given popper
* @method
* @memberof Popper.Utils
* @argument {Element} element - Element to apply the attributes to
* @argument {Object} styles
* Object with a list of properties and values which will be applied to the element
*/
function setAttributes(element, attributes) {
Object.keys(attributes).forEach(function(prop) {
var value = attributes[prop];
if (value !== false) {
element.setAttribute(prop, attributes[prop]);
} else {
element.removeAttribute(prop);
}
});
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by `update` method
* @argument {Object} data.styles - List of style properties - values to apply to popper element
* @argument {Object} data.attributes - List of attribute properties - values to apply to popper element
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The same data object
*/
function applyStyle(data) {
// any property present in `data.styles` will be applied to the popper,
// in this way we can make the 3rd party modifiers add custom styles to it
// Be aware, modifiers could override the properties defined in the previous
// lines of this modifier!
setStyles(data.instance.popper, data.styles);
// any property present in `data.attributes` will be applied to the popper,
// they will be set as HTML attributes of the element
setAttributes(data.instance.popper, data.attributes);
// if arrowElement is defined and arrowStyles has some properties
if (data.arrowElement && Object.keys(data.arrowStyles).length) {
setStyles(data.arrowElement, data.arrowStyles);
}
return data;
}
/**
* Set the x-placement attribute before everything else because it could be used
* to add margins to the popper margins needs to be calculated to get the
* correct popper offsets.
* @method
* @memberof Popper.modifiers
* @param {HTMLElement} reference - The reference element used to position the popper
* @param {HTMLElement} popper - The HTML element used as popper.
* @param {Object} options - Popper.js options
*/
function applyStyleOnLoad(reference, popper, options, modifierOptions, state) {
// compute reference element offsets
var referenceOffsets = getReferenceOffsets(state, popper, reference);
// compute auto placement, store placement inside the data object,
// modifiers will be able to edit `placement` if needed
// and refer to originalPlacement to know the original value
var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding);
popper.setAttribute('x-placement', placement);
// Apply `position` to popper before anything else because
// without the position applied we can't guarantee correct computations
setStyles(popper, {position: 'absolute'});
return options;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by `update` method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function computeStyle(data, options) {
var x = options.x,
y = options.y;
var popper = data.offsets.popper;
// Remove this legacy support in Popper.js v2
var legacyGpuAccelerationOption = find(data.instance.modifiers, function(modifier) {
return modifier.name === 'applyStyle';
}).gpuAcceleration;
if (legacyGpuAccelerationOption !== undefined) {
console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');
}
var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration;
var offsetParent = getOffsetParent(data.instance.popper);
var offsetParentRect = getBoundingClientRect(offsetParent);
// Styles
var styles = {
position: popper.position
};
// floor sides to avoid blurry text
var offsets = {
left : Math.floor(popper.left),
top : Math.floor(popper.top),
bottom: Math.floor(popper.bottom),
right : Math.floor(popper.right)
};
var sideA = x === 'bottom' ? 'top' : 'bottom';
var sideB = y === 'right' ? 'left' : 'right';
// if gpuAcceleration is set to `true` and transform is supported,
// we use `translate3d` to apply the position to the popper we
// automatically use the supported prefixed version if needed
var prefixedProperty = getSupportedPropertyName('transform');
// now, let's make a step back and look at this code closely (wtf?)
// If the content of the popper grows once it's been positioned, it
// may happen that the popper gets misplaced because of the new content
// overflowing its reference element
// To avoid this problem, we provide two options (x and y), which allow
// the consumer to define the offset origin.
// If we position a popper on top of a reference element, we can set
// `x` to `top` to make the popper grow towards its top instead of
// its bottom.
var left = void 0,
top = void 0;
if (sideA === 'bottom') {
top = -offsetParentRect.height + offsets.bottom;
} else {
top = offsets.top;
}
if (sideB === 'right') {
left = -offsetParentRect.width + offsets.right;
} else {
left = offsets.left;
}
if (gpuAcceleration && prefixedProperty) {
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
styles[sideA] = 0;
styles[sideB] = 0;
styles.willChange = 'transform';
} else {
// othwerise, we use the standard `top`, `left`, `bottom` and `right` properties
var invertTop = sideA === 'bottom' ? -1 : 1;
var invertLeft = sideB === 'right' ? -1 : 1;
styles[sideA] = top * invertTop;
styles[sideB] = left * invertLeft;
styles.willChange = sideA + ', ' + sideB;
}
// Attributes
var attributes = {
'x-placement': data.placement
};
// Update `data` attributes, styles and arrowStyles
data.attributes = _extends$1({}, attributes, data.attributes);
data.styles = _extends$1({}, styles, data.styles);
data.arrowStyles = _extends$1({}, data.offsets.arrow, data.arrowStyles);
return data;
}
/**
* Helper used to know if the given modifier depends from another one.<br />
* It checks if the needed modifier is listed and enabled.
* @method
* @memberof Popper.Utils
* @param {Array} modifiers - list of modifiers
* @param {String} requestingName - name of requesting modifier
* @param {String} requestedName - name of requested modifier
* @returns {Boolean}
*/
function isModifierRequired(modifiers, requestingName, requestedName) {
var requesting = find(modifiers, function(_ref) {
var name = _ref.name;
return name === requestingName;
});
var isRequired = !!requesting && modifiers.some(function(modifier) {
return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
});
if (!isRequired) {
var _requesting = '`' + requestingName + '`';
var requested = '`' + requestedName + '`';
console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!');
}
return isRequired;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by update method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function arrow(data, options) {
var _data$offsets$arrow;
// arrow depends on keepTogether in order to work
if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) {
return data;
}
var arrowElement = options.element;
// if arrowElement is a string, suppose it's a CSS selector
if (typeof arrowElement === 'string') {
arrowElement = data.instance.popper.querySelector(arrowElement);
// if arrowElement is not found, don't run the modifier
if (!arrowElement) {
return data;
}
} else {
// if the arrowElement isn't a query selector we must check that the
// provided DOM node is child of its popper node
if (!data.instance.popper.contains(arrowElement)) {
console.warn('WARNING: `arrow.element` must be child of its popper element!');
return data;
}
}
var placement = data.placement.split('-')[0];
var _data$offsets = data.offsets,
popper = _data$offsets.popper,
reference = _data$offsets.reference;
var isVertical = [
'left',
'right'
].indexOf(placement) !== -1;
var len = isVertical ? 'height' : 'width';
var sideCapitalized = isVertical ? 'Top' : 'Left';
var side = sideCapitalized.toLowerCase();
var altSide = isVertical ? 'left' : 'top';
var opSide = isVertical ? 'bottom' : 'right';
var arrowElementSize = getOuterSizes(arrowElement)[len];
//
// extends keepTogether behavior making sure the popper and its
// reference have enough pixels in conjuction
//
// top/left side
if (reference[opSide] - arrowElementSize < popper[side]) {
data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize);
}
// bottom/right side
if (reference[side] + arrowElementSize > popper[opSide]) {
data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide];
}
data.offsets.popper = getClientRect(data.offsets.popper);
// compute center of the popper
var center = reference[side] + reference[len] / 2 - arrowElementSize / 2;
// Compute the sideValue using the updated popper offsets
// take popper margin in account because we don't have this info available
var css = getStyleComputedProperty(data.instance.popper);
var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10);
var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10);
var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide;
// prevent arrowElement from being placed not contiguously to its popper
sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0);
data.arrowElement = arrowElement;
data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow);
return data;
}
/**
* Get the opposite placement variation of the given one
* @method
* @memberof Popper.Utils
* @argument {String} placement variation
* @returns {String} flipped placement variation
*/
function getOppositeVariation(variation) {
if (variation === 'end') {
return 'start';
} else if (variation === 'start') {
return 'end';
}
return variation;
}
/**
* List of accepted placements to use as values of the `placement` option.<br />
* Valid placements are:
* - `auto`
* - `top`
* - `right`
* - `bottom`
* - `left`
*
* Each placement can have a variation from this list:
* - `-start`
* - `-end`
*
* Variations are interpreted easily if you think of them as the left to right
* written languages. Horizontally (`top` and `bottom`), `start` is left and `end`
* is right.<br />
* Vertically (`left` and `right`), `start` is top and `end` is bottom.
*
* Some valid examples are:
* - `top-end` (on top of reference, right aligned)
* - `right-start` (on right of reference, top aligned)
* - `bottom` (on bottom, centered)
* - `auto-right` (on the side with more space available, alignment depends by placement)
*
* @static
* @type {Array}
* @enum {String}
* @readonly
* @method placements
* @memberof Popper
*/
var placements = [
'auto-start',
'auto',
'auto-end',
'top-start',
'top',
'top-end',
'right-start',
'right',
'right-end',
'bottom-end',
'bottom',
'bottom-start',
'left-end',
'left',
'left-start'
];
// Get rid of `auto` `auto-start` and `auto-end`
var validPlacements = placements.slice(3);
/**
* Given an initial placement, returns all the subsequent placements
* clockwise (or counter-clockwise).
*
* @method
* @memberof Popper.Utils
* @argument {String} placement - A valid placement (it accepts variations)
* @argument {Boolean} counter - Set to true to walk the placements counterclockwise
* @returns {Array} placements including their variations
*/
function clockwise(placement) {
var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var index = validPlacements.indexOf(placement);
var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index));
return counter ? arr.reverse() : arr;
}
var BEHAVIORS = {
FLIP : 'flip',
CLOCKWISE : 'clockwise',
COUNTERCLOCKWISE: 'counterclockwise'
};
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by update method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function flip(data, options) {
// if `inner` modifier is enabled, we can't use the `flip` modifier
if (isModifierEnabled(data.instance.modifiers, 'inner')) {
return data;
}
if (data.flipped && data.placement === data.originalPlacement) {
// seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
return data;
}
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement);
var placement = data.placement.split('-')[0];
var placementOpposite = getOppositePlacement(placement);
var variation = data.placement.split('-')[1] || '';
var flipOrder = [];
switch (options.behavior) {
case BEHAVIORS.FLIP:
flipOrder = [
placement,
placementOpposite
];
break;
case BEHAVIORS.CLOCKWISE:
flipOrder = clockwise(placement);
break;
case BEHAVIORS.COUNTERCLOCKWISE:
flipOrder = clockwise(placement, true);
break;
default:
flipOrder = options.behavior;
}
flipOrder.forEach(function(step, index) {
if (placement !== step || flipOrder.length === index + 1) {
return data;
}
placement = data.placement.split('-')[0];
placementOpposite = getOppositePlacement(placement);
var popperOffsets = data.offsets.popper;
var refOffsets = data.offsets.reference;
// using floor because the reference offsets may contain decimals we are not going to consider here
var floor = Math.floor;
var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom);
var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left);
var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right);
var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top);
var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom);
var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom;
// flip the variation if required
var isVertical = [
'top',
'bottom'
].indexOf(placement) !== -1;
var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom);
if (overlapsRef || overflowsBoundaries || flippedVariation) {
// this boolean to detect any flip loop
data.flipped = true;
if (overlapsRef || overflowsBoundaries) {
placement = flipOrder[index + 1];
}
if (flippedVariation) {
variation = getOppositeVariation(variation);
}
data.placement = placement + (variation ? '-' + variation : '');
// this object contains `position`, we want to preserve it along with
// any additional property we may add in the future
data.offsets.popper = _extends$1({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement));
data = runModifiers(data.instance.modifiers, data, 'flip');
}
});
return data;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by update method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function keepTogether(data) {
var _data$offsets = data.offsets,
popper = _data$offsets.popper,
reference = _data$offsets.reference;
var placement = data.placement.split('-')[0];
var floor = Math.floor;
var isVertical = [
'top',
'bottom'
].indexOf(placement) !== -1;
var side = isVertical ? 'right' : 'bottom';
var opSide = isVertical ? 'left' : 'top';
var measurement = isVertical ? 'width' : 'height';
if (popper[side] < floor(reference[opSide])) {
data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement];
}
if (popper[opSide] > floor(reference[side])) {
data.offsets.popper[opSide] = floor(reference[side]);
}
return data;
}
/**
* Converts a string containing value + unit into a px value number
* @function
* @memberof {modifiers~offset}
* @private
* @argument {String} str - Value + unit string
* @argument {String} measurement - `height` or `width`
* @argument {Object} popperOffsets
* @argument {Object} referenceOffsets
* @returns {Number|String}
* Value in pixels, or original string if no values were extracted
*/
function toValue(str, measurement, popperOffsets, referenceOffsets) {
// separate value from unit
var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/);
var value = +split[1];
var unit = split[2];
// If it's not a number it's an operator, I guess
if (!value) {
return str;
}
if (unit.indexOf('%') === 0) {
var element = void 0;
switch (unit) {
case '%p':
element = popperOffsets;
break;
case '%':
case '%r':
default:
element = referenceOffsets;
}
var rect = getClientRect(element);
return rect[measurement] / 100 * value;
} else if (unit === 'vh' || unit === 'vw') {
// if is a vh or vw, we calculate the size based on the viewport
var size = void 0;
if (unit === 'vh') {
size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
} else {
size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
}
return size / 100 * value;
} else {
// if is an explicit pixel unit, we get rid of the unit and keep the value
// if is an implicit unit, it's px, and we return just the value
return value;
}
}
/**
* Parse an `offset` string to extrapolate `x` and `y` numeric offsets.
* @function
* @memberof {modifiers~offset}
* @private
* @argument {String} offset
* @argument {Object} popperOffsets
* @argument {Object} referenceOffsets
* @argument {String} basePlacement
* @returns {Array} a two cells array with x and y offsets in numbers
*/
function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) {
var offsets = [
0,
0
];
// Use height if placement is left or right and index is 0 otherwise use width
// in this way the first offset will use an axis and the second one
// will use the other one
var useHeight = [
'right',
'left'
].indexOf(basePlacement) !== -1;
// Split the offset string to obtain a list of values and operands
// The regex addresses values with the plus or minus sign in front (+10, -20, etc)
var fragments = offset.split(/(\+|\-)/).map(function(frag) {
return frag.trim();
});
// Detect if the offset string contains a pair of values or a single one
// they could be separated by comma or space
var divider = fragments.indexOf(find(fragments, function(frag) {
return frag.search(/,|\s/) !== -1;
}));
if (fragments[divider] && fragments[divider].indexOf(',') === -1) {
console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');
}
// If divider is found, we divide the list of values and operands to divide
// them by ofset X and Y.
var splitRegex = /\s*,\s*|\s+/;
var ops = divider !== -1 ? [
fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]),
[fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))
] : [fragments];
// Convert the values with units to absolute pixels to allow our computations
ops = ops.map(function(op, index) {
// Most of the units rely on the orientation of the popper
var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width';
var mergeWithPrevious = false;
return op
// This aggregates any `+` or `-` sign that aren't considered operators
// e.g.: 10 + +5 => [10, +, +5]
.reduce(function(a, b) {
if (a[a.length - 1] === '' && [
'+',
'-'
].indexOf(b) !== -1) {
a[a.length - 1] = b;
mergeWithPrevious = true;
return a;
} else if (mergeWithPrevious) {
a[a.length - 1] += b;
mergeWithPrevious = false;
return a;
} else {
return a.concat(b);
}
}, [])
// Here we convert the string values into number values (in px)
.map(function(str) {
return toValue(str, measurement, popperOffsets, referenceOffsets);
});
});
// Loop trough the offsets arrays and execute the operations
ops.forEach(function(op, index) {
op.forEach(function(frag, index2) {
if (isNumeric(frag)) {
offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1);
}
});
});
return offsets;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by update method
* @argument {Object} options - Modifiers configuration and options
* @argument {Number|String} options.offset=0
* The offset value as described in the modifier description
* @returns {Object} The data object, properly modified
*/
function offset(data, _ref) {
var offset = _ref.offset;
var placement = data.placement,
_data$offsets = data.offsets,
popper = _data$offsets.popper,
reference = _data$offsets.reference;
var basePlacement = placement.split('-')[0];
var offsets = void 0;
if (isNumeric(+offset)) {
offsets = [
+offset,
0
];
} else {
offsets = parseOffset(offset, popper, reference, basePlacement);
}
if (basePlacement === 'left') {
popper.top += offsets[0];
popper.left -= offsets[1];
} else if (basePlacement === 'right') {
popper.top += offsets[0];
popper.left += offsets[1];
} else if (basePlacement === 'top') {
popper.left += offsets[0];
popper.top -= offsets[1];
} else if (basePlacement === 'bottom') {
popper.left += offsets[0];
popper.top += offsets[1];
}
data.popper = popper;
return data;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by `update` method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function preventOverflow(data, options) {
var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper);
// If offsetParent is the reference element, we really want to
// go one step up and use the next offsetParent as reference to
// avoid to make this modifier completely useless and look like broken
if (data.instance.reference === boundariesElement) {
boundariesElement = getOffsetParent(boundariesElement);
}
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement);
options.boundaries = boundaries;
var order = options.priority;
var popper = data.offsets.popper;
var check = {
primary : function primary(placement) {
var value = popper[placement];
if (popper[placement] < boundaries[placement] && !options.escapeWithReference) {
value = Math.max(popper[placement], boundaries[placement]);
}
return defineProperty({}, placement, value);
},
secondary: function secondary(placement) {
var mainSide = placement === 'right' ? 'left' : 'top';
var value = popper[mainSide];
if (popper[placement] > boundaries[placement] && !options.escapeWithReference) {
value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height));
}
return defineProperty({}, mainSide, value);
}
};
order.forEach(function(placement) {
var side = [
'left',
'top'
].indexOf(placement) !== -1 ? 'primary' : 'secondary';
popper = _extends$1({}, popper, check[side](placement));
});
data.offsets.popper = popper;
return data;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by `update` method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function shift(data) {
var placement = data.placement;
var basePlacement = placement.split('-')[0];
var shiftvariation = placement.split('-')[1];
// if shift shiftvariation is specified, run the modifier
if (shiftvariation) {
var _data$offsets = data.offsets,
reference = _data$offsets.reference,
popper = _data$offsets.popper;
var isVertical = [
'bottom',
'top'
].indexOf(basePlacement) !== -1;
var side = isVertical ? 'left' : 'top';
var measurement = isVertical ? 'width' : 'height';
var shiftOffsets = {
start: defineProperty({}, side, reference[side]),
end : defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement])
};
data.offsets.popper = _extends$1({}, popper, shiftOffsets[shiftvariation]);
}
return data;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by update method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function hide(data) {
if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) {
return data;
}
var refRect = data.offsets.reference;
var bound = find(data.instance.modifiers, function(modifier) {
return modifier.name === 'preventOverflow';
}).boundaries;
if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) {
// Avoid unnecessary DOM access if visibility hasn't changed
if (data.hide === true) {
return data;
}
data.hide = true;
data.attributes['x-out-of-boundaries'] = '';
} else {
// Avoid unnecessary DOM access if visibility hasn't changed
if (data.hide === false) {
return data;
}
data.hide = false;
data.attributes['x-out-of-boundaries'] = false;
}
return data;
}
/**
* @function
* @memberof Modifiers
* @argument {Object} data - The data object generated by `update` method
* @argument {Object} options - Modifiers configuration and options
* @returns {Object} The data object, properly modified
*/
function inner(data) {
var placement = data.placement;
var basePlacement = placement.split('-')[0];
var _data$offsets = data.offsets,
popper = _data$offsets.popper,
reference = _data$offsets.reference;
var isHoriz = [
'left',
'right'
].indexOf(basePlacement) !== -1;
var subtractLength = [
'top',
'left'
].indexOf(basePlacement) === -1;
popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0);
data.placement = getOppositePlacement(placement);
data.offsets.popper = getClientRect(popper);
return data;
}
/**
* Modifier function, each modifier can have a function of this type assigned
* to its `fn` property.<br />
* These functions will be called on each update, this means that you must
* make sure they are performant enough to avoid performance bottlenecks.
*
* @function ModifierFn
* @argument {dataObject} data - The data object generated by `update` method
* @argument {Object} options - Modifiers configuration and options
* @returns {dataObject} The data object, properly modified
*/
/**
* Modifiers are plugins used to alter the behavior of your poppers.<br />
* Popper.js uses a set of 9 modifiers to provide all the basic functionalities
* needed by the library.
*
* Usually you don't want to override the `order`, `fn` and `onLoad` props.
* All the other properties are configurations that could be tweaked.
* @namespace modifiers
*/
var modifiers = {
/**
* Modifier used to shift the popper on the start or end of its reference
* element.<br />
* It will read the variation of the `placement` property.<br />
* It can be one either `-end` or `-start`.
* @memberof modifiers
* @inner
*/
shift: {
/** @prop {number} order=100 - Index used to define the order of execution */
order : 100,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled: true,
/** @prop {ModifierFn} */
fn : shift
},
/**
* The `offset` modifier can shift your popper on both its axis.
*
* It accepts the following units:
* - `px` or unitless, interpreted as pixels
* - `%` or `%r`, percentage relative to the length of the reference element
* - `%p`, percentage relative to the length of the popper element
* - `vw`, CSS viewport width unit
* - `vh`, CSS viewport height unit
*
* For length is intended the main axis relative to the placement of the popper.<br />
* This means that if the placement is `top` or `bottom`, the length will be the
* `width`. In case of `left` or `right`, it will be the height.
*
* You can provide a single value (as `Number` or `String`), or a pair of values
* as `String` divided by a comma or one (or more) white spaces.<br />
* The latter is a deprecated method because it leads to confusion and will be
* removed in v2.<br />
* Additionally, it accepts additions and subtractions between different units.
* Note that multiplications and divisions aren't supported.
*
* Valid examples are:
* ```
* 10
* '10%'
* '10, 10'
* '10%, 10'
* '10 + 10%'
* '10 - 5vh + 3%'
* '-10px + 5vh, 5px - 6%'
* ```
* > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap
* > with their reference element, unfortunately, you will have to disable the `flip` modifier.
* > More on this [reading this issue](https://github.com/FezVrasta/popper.js/issues/373)
*
* @memberof modifiers
* @inner
*/
offset: {
/** @prop {number} order=200 - Index used to define the order of execution */
order : 200,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled: true,
/** @prop {ModifierFn} */
fn : offset,
/** @prop {Number|String} offset=0
* The offset value as described in the modifier description
*/
offset : 0
},
/**
* Modifier used to prevent the popper from being positioned outside the boundary.
*
* An scenario exists where the reference itself is not within the boundaries.<br />
* We can say it has "escaped the boundaries" — or just "escaped".<br />
* In this case we need to decide whether the popper should either:
*
* - detach from the reference and remain "trapped" in the boundaries, or
* - if it should ignore the boundary and "escape with its reference"
*
* When `escapeWithReference` is set to`true` and reference is completely
* outside its boundaries, the popper will overflow (or completely leave)
* the boundaries in order to remain attached to the edge of the reference.
*
* @memberof modifiers
* @inner
*/
preventOverflow: {
/** @prop {number} order=300 - Index used to define the order of execution */
order : 300,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled : true,
/** @prop {ModifierFn} */
fn : preventOverflow,
/**
* @prop {Array} [priority=['left','right','top','bottom']]
* Popper will try to prevent overflow following these priorities by default,
* then, it could overflow on the left and on top of the `boundariesElement`
*/
priority : [
'left',
'right',
'top',
'bottom'
],
/**
* @prop {number} padding=5
* Amount of pixel used to define a minimum distance between the boundaries
* and the popper this makes sure the popper has always a little padding
* between the edges of its container
*/
padding : 5,
/**
* @prop {String|HTMLElement} boundariesElement='scrollParent'
* Boundaries used by the modifier, can be `scrollParent`, `window`,
* `viewport` or any DOM element.
*/
boundariesElement: 'scrollParent'
},
/**
* Modifier used to make sure the reference and its popper stay near eachothers
* without leaving any gap between the two. Expecially useful when the arrow is
* enabled and you want to assure it to point to its reference element.
* It cares only about the first axis, you can still have poppers with margin
* between the popper and its reference element.
* @memberof modifiers
* @inner
*/
keepTogether: {
/** @prop {number} order=400 - Index used to define the order of execution */
order : 400,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled: true,
/** @prop {ModifierFn} */
fn : keepTogether
},
/**
* This modifier is used to move the `arrowElement` of the popper to make
* sure it is positioned between the reference element and its popper element.
* It will read the outer size of the `arrowElement` node to detect how many
* pixels of conjuction are needed.
*
* It has no effect if no `arrowElement` is provided.
* @memberof modifiers
* @inner
*/
arrow: {
/** @prop {number} order=500 - Index used to define the order of execution */
order : 500,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled: true,
/** @prop {ModifierFn} */
fn : arrow,
/** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */
element: '[x-arrow]'
},
/**
* Modifier used to flip the popper's placement when it starts to overlap its
* reference element.
*
* Requires the `preventOverflow` modifier before it in order to work.
*
* **NOTE:** this modifier will interrupt the current update cycle and will
* restart it if it detects the need to flip the placement.
* @memberof modifiers
* @inner
*/
flip: {
/** @prop {number} order=600 - Index used to define the order of execution */
order : 600,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled : true,
/** @prop {ModifierFn} */
fn : flip,
/**
* @prop {String|Array} behavior='flip'
* The behavior used to change the popper's placement. It can be one of
* `flip`, `clockwise`, `counterclockwise` or an array with a list of valid
* placements (with optional variations).
*/
behavior : 'flip',
/**
* @prop {number} padding=5
* The popper will flip if it hits the edges of the `boundariesElement`
*/
padding : 5,
/**
* @prop {String|HTMLElement} boundariesElement='viewport'
* The element which will define the boundaries of the popper position,
* the popper will never be placed outside of the defined boundaries
* (except if keepTogether is enabled)
*/
boundariesElement: 'viewport'
},
/**
* Modifier used to make the popper flow toward the inner of the reference element.
* By default, when this modifier is disabled, the popper will be placed outside
* the reference element.
* @memberof modifiers
* @inner
*/
inner: {
/** @prop {number} order=700 - Index used to define the order of execution */
order : 700,
/** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */
enabled: false,
/** @prop {ModifierFn} */
fn : inner
},
/**
* Modifier used to hide the popper when its reference element is outside of the
* popper boundaries. It will set a `x-out-of-boundaries` attribute which can
* be used to hide with a CSS selector the popper when its reference is
* out of boundaries.
*
* Requires the `preventOverflow` modifier before it in order to work.
* @memberof modifiers
* @inner
*/
hide: {
/** @prop {number} order=800 - Index used to define the order of execution */
order : 800,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled: true,
/** @prop {ModifierFn} */
fn : hide
},
/**
* Computes the style that will be applied to the popper element to gets
* properly positioned.
*
* Note that this modifier will not touch the DOM, it just prepares the styles
* so that `applyStyle` modifier can apply it. This separation is useful
* in case you need to replace `applyStyle` with a custom implementation.
*
* This modifier has `850` as `order` value to maintain backward compatibility
* with previous versions of Popper.js. Expect the modifiers ordering method
* to change in future major versions of the library.
*
* @memberof modifiers
* @inner
*/
computeStyle: {
/** @prop {number} order=850 - Index used to define the order of execution */
order : 850,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled : true,
/** @prop {ModifierFn} */
fn : computeStyle,
/**
* @prop {Boolean} gpuAcceleration=true
* If true, it uses the CSS 3d transformation to position the popper.
* Otherwise, it will use the `top` and `left` properties.
*/
gpuAcceleration: true,
/**
* @prop {string} [x='bottom']
* Where to anchor the X axis (`bottom` or `top`). AKA X offset origin.
* Change this if your popper should grow in a direction different from `bottom`
*/
x : 'bottom',
/**
* @prop {string} [x='left']
* Where to anchor the Y axis (`left` or `right`). AKA Y offset origin.
* Change this if your popper should grow in a direction different from `right`
*/
y : 'right'
},
/**
* Applies the computed styles to the popper element.
*
* All the DOM manipulations are limited to this modifier. This is useful in case
* you want to integrate Popper.js inside a framework or view library and you
* want to delegate all the DOM manipulations to it.
*
* Note that if you disable this modifier, you must make sure the popper element
* has its position set to `absolute` before Popper.js can do its work!
*
* Just disable this modifier and define you own to achieve the desired effect.
*
* @memberof modifiers
* @inner
*/
applyStyle: {
/** @prop {number} order=900 - Index used to define the order of execution */
order : 900,
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
enabled : true,
/** @prop {ModifierFn} */
fn : applyStyle,
/** @prop {Function} */
onLoad : applyStyleOnLoad,
/**
* @deprecated since version 1.10.0, the property moved to `computeStyle` modifier
* @prop {Boolean} gpuAcceleration=true
* If true, it uses the CSS 3d transformation to position the popper.
* Otherwise, it will use the `top` and `left` properties.
*/
gpuAcceleration: undefined
}
};
/**
* The `dataObject` is an object containing all the informations used by Popper.js
* this object get passed to modifiers and to the `onCreate` and `onUpdate` callbacks.
* @name dataObject
* @property {Object} data.instance The Popper.js instance
* @property {String} data.placement Placement applied to popper
* @property {String} data.originalPlacement Placement originally defined on init
* @property {Boolean} data.flipped True if popper has been flipped by flip modifier
* @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper.
* @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier
* @property {Object} data.styles Any CSS property defined here will be applied to the popper, it expects the JavaScript nomenclature (eg. `marginBottom`)
* @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow, it expects the JavaScript nomenclature (eg. `marginBottom`)
* @property {Object} data.boundaries Offsets of the popper boundaries
* @property {Object} data.offsets The measurements of popper, reference and arrow elements.
* @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values
* @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values
* @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0
*/
/**
* Default options provided to Popper.js constructor.<br />
* These can be overriden using the `options` argument of Popper.js.<br />
* To override an option, simply pass as 3rd argument an object with the same
* structure of this object, example:
* ```
* new Popper(ref, pop, {
* modifiers: {
* preventOverflow: { enabled: false }
* }
* })
* ```
* @type {Object}
* @static
* @memberof Popper
*/
var Defaults = {
/**
* Popper's placement
* @prop {Popper.placements} placement='bottom'
*/
placement: 'bottom',
/**
* Whether events (resize, scroll) are initially enabled
* @prop {Boolean} eventsEnabled=true
*/
eventsEnabled: true,
/**
* Set to true if you want to automatically remove the popper when
* you call the `destroy` method.
* @prop {Boolean} removeOnDestroy=false
*/
removeOnDestroy: false,
/**
* Callback called when the popper is created.<br />
* By default, is set to no-op.<br />
* Access Popper.js instance with `data.instance`.
* @prop {onCreate}
*/
onCreate: function onCreate() {},
/**
* Callback called when the popper is updated, this callback is not called
* on the initialization/creation of the popper, but only on subsequent
* updates.<br />
* By default, is set to no-op.<br />
* Access Popper.js instance with `data.instance`.
* @prop {onUpdate}
*/
onUpdate: function onUpdate() {},
/**
* List of modifiers used to modify the offsets before they are applied to the popper.
* They provide most of the functionalities of Popper.js
* @prop {modifiers}
*/
modifiers: modifiers
};
/**
* @callback onCreate
* @param {dataObject} data
*/
/**
* @callback onUpdate
* @param {dataObject} data
*/
// Utils
// Methods
var Popper = function() {
/**
* Create a new Popper.js instance
* @class Popper
* @param {HTMLElement|referenceObject} reference - The reference element used to position the popper
* @param {HTMLElement} popper - The HTML element used as popper.
* @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults)
* @return {Object} instance - The generated Popper.js instance
*/
function Popper(reference, popper) {
var _this = this;
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
classCallCheck(this, Popper);
this.scheduleUpdate = function() {
return requestAnimationFrame(_this.update);
};
// make update() debounced, so that it only runs at most once-per-tick
this.update = debounce(this.update.bind(this));
// with {} we create a new object with the options inside it
this.options = _extends$1({}, Popper.Defaults, options);
// init state
this.state = {
isDestroyed : false,
isCreated : false,
scrollParents: []
};
// get reference and popper elements (allow jQuery wrappers)
this.reference = reference && reference.jquery ? reference[0] : reference;
this.popper = popper && popper.jquery ? popper[0] : popper;
// Deep merge modifiers options
this.options.modifiers = {};
Object.keys(_extends$1({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function(name) {
_this.options.modifiers[name] = _extends$1({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {});
});
// Refactoring modifiers' list (Object => Array)
this.modifiers = Object.keys(this.options.modifiers).map(function(name) {
return _extends$1({
name: name
}, _this.options.modifiers[name]);
})
// sort the modifiers by order
.sort(function(a, b) {
return a.order - b.order;
});
// modifiers have the ability to execute arbitrary code when Popper.js get inited
// such code is executed in the same order of its modifier
// they could add new properties to their options configuration
// BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`!
this.modifiers.forEach(function(modifierOptions) {
if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) {
modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state);
}
});
// fire the first update to position the popper in the right place
this.update();
var eventsEnabled = this.options.eventsEnabled;
if (eventsEnabled) {
// setup event listeners, they will take care of update the position in specific situations
this.enableEventListeners();
}
this.state.eventsEnabled = eventsEnabled;
}
// We can't use class properties because they don't get listed in the
// class prototype and break stuff like Sinon stubs
createClass(Popper, [
{
key : 'update',
value: function update$$1() {
return update.call(this);
}
},
{
key : 'destroy',
value: function destroy$$1() {
return destroy.call(this);
}
},
{
key : 'enableEventListeners',
value: function enableEventListeners$$1() {
return enableEventListeners.call(this);
}
},
{
key : 'disableEventListeners',
value: function disableEventListeners$$1() {
return disableEventListeners.call(this);
}
/**
* Schedule an update, it will run on the next UI update available
* @method scheduleUpdate
* @memberof Popper
*/
/**
* Collection of utilities useful when writing custom modifiers.
* Starting from version 1.7, this method is available only if you
* include `popper-utils.js` before `popper.js`.
*
* **DEPRECATION**: This way to access PopperUtils is deprecated
* and will be removed in v2! Use the PopperUtils module directly instead.
* Due to the high instability of the methods contained in Utils, we can't
* guarantee them to follow semver. Use them at your own risk!
* @static
* @private
* @type {Object}
* @deprecated since version 1.8
* @member Utils
* @memberof Popper
*/
}
]);
return Popper;
}();
/**
* The `referenceObject` is an object that provides an interface compatible with Popper.js
* and lets you use it as replacement of a real DOM node.<br />
* You can use this method to position a popper relatively to a set of coordinates
* in case you don't have a DOM node to use as reference.
*
* ```
* new Popper(referenceObject, popperNode);
* ```
*
* NB: This feature isn't supported in Internet Explorer 10
* @name referenceObject
* @property {Function} data.getBoundingClientRect
* A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method.
* @property {number} data.clientWidth
* An ES6 getter that will return the width of the virtual reference element.
* @property {number} data.clientHeight
* An ES6 getter that will return the height of the virtual reference element.
*/
Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils;
Popper.placements = placements;
Popper.Defaults = Defaults;
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0): tooltip.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
var Tooltip = function($$$1) {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
var NAME = 'tooltip';
var VERSION = '4.0.0';
var DATA_KEY = 'bs.tooltip';
var EVENT_KEY = '.' + DATA_KEY;
var JQUERY_NO_CONFLICT = $$$1.fn[NAME];
var TRANSITION_DURATION = 150;
var CLASS_PREFIX = 'bs-tooltip';
var BSCLS_PREFIX_REGEX = new RegExp('(^|\\s)' + CLASS_PREFIX + '\\S+', 'g');
var DefaultType = {
animation : 'boolean',
template : 'string',
title : '(string|element|function)',
trigger : 'string',
delay : '(number|object)',
html : 'boolean',
selector : '(string|boolean)',
placement : '(string|function)',
offset : '(number|string)',
container : '(string|element|boolean)',
fallbackPlacement: '(string|array)',
boundary : '(string|element)'
};
var AttachmentMap = {
AUTO : 'auto',
TOP : 'top',
RIGHT : 'right',
BOTTOM: 'bottom',
LEFT : 'left'
};
var Default = {
animation : true,
template : '<div class="tooltip" role="tooltip">' + '<div class="arrow"></div>' + '<div class="tooltip-inner"></div></div>',
trigger : 'hover focus',
title : '',
delay : 0,
html : false,
selector : false,
placement : 'top',
offset : 0,
container : false,
fallbackPlacement: 'flip',
boundary : 'scrollParent'
};
var HoverState = {
SHOW: 'show',
OUT : 'out'
};
var Event = {
HIDE : 'hide' + EVENT_KEY,
HIDDEN : 'hidden' + EVENT_KEY,
SHOW : 'show' + EVENT_KEY,
SHOWN : 'shown' + EVENT_KEY,
INSERTED : 'inserted' + EVENT_KEY,
CLICK : 'click' + EVENT_KEY,
FOCUSIN : 'focusin' + EVENT_KEY,
FOCUSOUT : 'focusout' + EVENT_KEY,
MOUSEENTER: 'mouseenter' + EVENT_KEY,
MOUSELEAVE: 'mouseleave' + EVENT_KEY
};
var ClassName = {
FADE: 'fade',
SHOW: 'show'
};
var Selector = {
TOOLTIP : '.tooltip',
TOOLTIP_INNER: '.tooltip-inner',
ARROW : '.arrow'
};
var Trigger = {
HOVER : 'hover',
FOCUS : 'focus',
CLICK : 'click',
MANUAL: 'manual'
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
};
var Tooltip =
/*#__PURE__*/
function() {
function Tooltip(element, config) {
/**
* Check for Popper dependency
* Popper - https://popper.js.org
*/
if (typeof Popper === 'undefined') {
throw new TypeError('Bootstrap tooltips require Popper.js (https://popper.js.org)');
} // private
this._isEnabled = true;
this._timeout = 0;
this._hoverState = '';
this._activeTrigger = {};
this._popper = null; // Protected
this.element = element;
this.config = this._getConfig(config);
this.tip = null;
this._setListeners();
} // Getters
var _proto = Tooltip.prototype;
// Public
_proto.enable = function enable() {
this._isEnabled = true;
};
_proto.disable = function disable() {
this._isEnabled = false;
};
_proto.toggleEnabled = function toggleEnabled() {
this._isEnabled = !this._isEnabled;
};
_proto.toggle = function toggle(event) {
if (!this._isEnabled) {
return;
}
if (event) {
var dataKey = this.constructor.DATA_KEY;
var context = $$$1(event.currentTarget).data(dataKey);
if (!context) {
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
$$$1(event.currentTarget).data(dataKey, context);
}
context._activeTrigger.click = !context._activeTrigger.click;
if (context._isWithActiveTrigger()) {
context._enter(null, context);
} else {
context._leave(null, context);
}
} else {
if ($$$1(this.getTipElement()).hasClass(ClassName.SHOW)) {
this._leave(null, this);
return;
}
this._enter(null, this);
}
};
_proto.dispose = function dispose() {
clearTimeout(this._timeout);
$$$1.removeData(this.element, this.constructor.DATA_KEY);
$$$1(this.element).off(this.constructor.EVENT_KEY);
$$$1(this.element).closest('.modal').off('hide.bs.modal');
if (this.tip) {
$$$1(this.tip).remove();
}
this._isEnabled = null;
this._timeout = null;
this._hoverState = null;
this._activeTrigger = null;
if (this._popper !== null) {
this._popper.destroy();
}
this._popper = null;
this.element = null;
this.config = null;
this.tip = null;
};
_proto.show = function show() {
var _this = this;
if ($$$1(this.element).css('display') === 'none') {
throw new Error('Please use show on visible elements');
}
var showEvent = $$$1.Event(this.constructor.Event.SHOW);
if (this.isWithContent() && this._isEnabled) {
$$$1(this.element).trigger(showEvent);
var isInTheDom = $$$1.contains(this.element.ownerDocument.documentElement, this.element);
if (showEvent.isDefaultPrevented() || !isInTheDom) {
return;
}
var tip = this.getTipElement();
var tipId = Util.getUID(this.constructor.NAME);
tip.setAttribute('id', tipId);
this.element.setAttribute('aria-describedby', tipId);
this.setContent();
if (this.config.animation) {
$$$1(tip).addClass(ClassName.FADE);
}
var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement;
var attachment = this._getAttachment(placement);
this.addAttachmentClass(attachment);
var container = this.config.container === false ? document.body : $$$1(this.config.container);
$$$1(tip).data(this.constructor.DATA_KEY, this);
if (!$$$1.contains(this.element.ownerDocument.documentElement, this.tip)) {
$$$1(tip).appendTo(container);
}
$$$1(this.element).trigger(this.constructor.Event.INSERTED);
this._popper = new Popper(this.element, tip, {
placement: attachment,
modifiers: {
offset : {
offset: this.config.offset
},
flip : {
behavior: this.config.fallbackPlacement
},
arrow : {
element: Selector.ARROW
},
preventOverflow: {
boundariesElement: this.config.boundary
}
},
onCreate : function onCreate(data) {
if (data.originalPlacement !== data.placement) {
_this._handlePopperPlacementChange(data);
}
},
onUpdate : function onUpdate(data) {
_this._handlePopperPlacementChange(data);
}
});
$$$1(tip).addClass(ClassName.SHOW); // If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement) {
$$$1('body').children().on('mouseover', null, $$$1.noop);
}
var complete = function complete() {
if (_this.config.animation) {
_this._fixTransition();
}
var prevHoverState = _this._hoverState;
_this._hoverState = null;
$$$1(_this.element).trigger(_this.constructor.Event.SHOWN);
if (prevHoverState === HoverState.OUT) {
_this._leave(null, _this);
}
};
if (Util.supportsTransitionEnd() && $$$1(this.tip).hasClass(ClassName.FADE)) {
$$$1(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(Tooltip._TRANSITION_DURATION);
} else {
complete();
}
}
};
_proto.hide = function hide(callback) {
var _this2 = this;
var tip = this.getTipElement();
var hideEvent = $$$1.Event(this.constructor.Event.HIDE);
var complete = function complete() {
if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) {
tip.parentNode.removeChild(tip);
}
_this2._cleanTipClass();
_this2.element.removeAttribute('aria-describedby');
$$$1(_this2.element).trigger(_this2.constructor.Event.HIDDEN);
if (_this2._popper !== null) {
_this2._popper.destroy();
}
if (callback) {
callback();
}
};
$$$1(this.element).trigger(hideEvent);
if (hideEvent.isDefaultPrevented()) {
return;
}
$$$1(tip).removeClass(ClassName.SHOW); // If this is a touch-enabled device we remove the extra
// empty mouseover listeners we added for iOS support
if ('ontouchstart' in document.documentElement) {
$$$1('body').children().off('mouseover', null, $$$1.noop);
}
this._activeTrigger[Trigger.CLICK] = false;
this._activeTrigger[Trigger.FOCUS] = false;
this._activeTrigger[Trigger.HOVER] = false;
if (Util.supportsTransitionEnd() && $$$1(this.tip).hasClass(ClassName.FADE)) {
$$$1(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(TRANSITION_DURATION);
} else {
complete();
}
this._hoverState = '';
};
_proto.update = function update() {
if (this._popper !== null) {
this._popper.scheduleUpdate();
}
}; // Protected
_proto.isWithContent = function isWithContent() {
return Boolean(this.getTitle());
};
_proto.addAttachmentClass = function addAttachmentClass(attachment) {
$$$1(this.getTipElement()).addClass(CLASS_PREFIX + '-' + attachment);
};
_proto.getTipElement = function getTipElement() {
this.tip = this.tip || $$$1(this.config.template)[0];
return this.tip;
};
_proto.setContent = function setContent() {
var $tip = $$$1(this.getTipElement());
this.setElementContent($tip.find(Selector.TOOLTIP_INNER), this.getTitle());
$tip.removeClass(ClassName.FADE + ' ' + ClassName.SHOW);
};
_proto.setElementContent = function setElementContent($element, content) {
var html = this.config.html;
if (typeof content === 'object' && (content.nodeType || content.jquery)) {
// Content is a DOM node or a jQuery
if (html) {
if (!$$$1(content).parent().is($element)) {
$element.empty().append(content);
}
} else {
$element.text($$$1(content).text());
}
} else {
$element[html ? 'html' : 'text'](content);
}
};
_proto.getTitle = function getTitle() {
var title = this.element.getAttribute('data-original-title');
if (!title) {
title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title;
}
return title;
}; // Private
_proto._getAttachment = function _getAttachment(placement) {
return AttachmentMap[placement.toUpperCase()];
};
_proto._setListeners = function _setListeners() {
var _this3 = this;
var triggers = this.config.trigger.split(' ');
triggers.forEach(function(trigger) {
if (trigger === 'click') {
$$$1(_this3.element).on(_this3.constructor.Event.CLICK, _this3.config.selector, function(event) {
return _this3.toggle(event);
});
} else if (trigger !== Trigger.MANUAL) {
var eventIn = trigger === Trigger.HOVER ? _this3.constructor.Event.MOUSEENTER : _this3.constructor.Event.FOCUSIN;
var eventOut = trigger === Trigger.HOVER ? _this3.constructor.Event.MOUSELEAVE : _this3.constructor.Event.FOCUSOUT;
$$$1(_this3.element).on(eventIn, _this3.config.selector, function(event) {
return _this3._enter(event);
}).on(eventOut, _this3.config.selector, function(event) {
return _this3._leave(event);
});
}
$$$1(_this3.element).closest('.modal').on('hide.bs.modal', function() {
return _this3.hide();
});
});
if (this.config.selector) {
this.config = _extends({}, this.config, {
trigger : 'manual',
selector: ''
});
} else {
this._fixTitle();
}
};
_proto._fixTitle = function _fixTitle() {
var titleType = typeof this.element.getAttribute('data-original-title');
if (this.element.getAttribute('title') || titleType !== 'string') {
this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '');
this.element.setAttribute('title', '');
}
};
_proto._enter = function _enter(event, context) {
var dataKey = this.constructor.DATA_KEY;
context = context || $$$1(event.currentTarget).data(dataKey);
if (!context) {
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
$$$1(event.currentTarget).data(dataKey, context);
}
if (event) {
context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true;
}
if ($$$1(context.getTipElement()).hasClass(ClassName.SHOW) || context._hoverState === HoverState.SHOW) {
context._hoverState = HoverState.SHOW;
return;
}
clearTimeout(context._timeout);
context._hoverState = HoverState.SHOW;
if (!context.config.delay || !context.config.delay.show) {
context.show();
return;
}
context._timeout = setTimeout(function() {
if (context._hoverState === HoverState.SHOW) {
context.show();
}
}, context.config.delay.show);
};
_proto._leave = function _leave(event, context) {
var dataKey = this.constructor.DATA_KEY;
context = context || $$$1(event.currentTarget).data(dataKey);
if (!context) {
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
$$$1(event.currentTarget).data(dataKey, context);
}
if (event) {
context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false;
}
if (context._isWithActiveTrigger()) {
return;
}
clearTimeout(context._timeout);
context._hoverState = HoverState.OUT;
if (!context.config.delay || !context.config.delay.hide) {
context.hide();
return;
}
context._timeout = setTimeout(function() {
if (context._hoverState === HoverState.OUT) {
context.hide();
}
}, context.config.delay.hide);
};
_proto._isWithActiveTrigger = function _isWithActiveTrigger() {
for (var trigger in this._activeTrigger) {
if (this._activeTrigger[trigger]) {
return true;
}
}
return false;
};
_proto._getConfig = function _getConfig(config) {
config = _extends({}, this.constructor.Default, $$$1(this.element).data(), config);
if (typeof config.delay === 'number') {
config.delay = {
show: config.delay,
hide: config.delay
};
}
if (typeof config.title === 'number') {
config.title = config.title.toString();
}
if (typeof config.content === 'number') {
config.content = config.content.toString();
}
Util.typeCheckConfig(NAME, config, this.constructor.DefaultType);
return config;
};
_proto._getDelegateConfig = function _getDelegateConfig() {
var config = {};
if (this.config) {
for (var key in this.config) {
if (this.constructor.Default[key] !== this.config[key]) {
config[key] = this.config[key];
}
}
}
return config;
};
_proto._cleanTipClass = function _cleanTipClass() {
var $tip = $$$1(this.getTipElement());
var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX);
if (tabClass !== null && tabClass.length > 0) {
$tip.removeClass(tabClass.join(''));
}
};
_proto._handlePopperPlacementChange = function _handlePopperPlacementChange(data) {
this._cleanTipClass();
this.addAttachmentClass(this._getAttachment(data.placement));
};
_proto._fixTransition = function _fixTransition() {
var tip = this.getTipElement();
var initConfigAnimation = this.config.animation;
if (tip.getAttribute('x-placement') !== null) {
return;
}
$$$1(tip).removeClass(ClassName.FADE);
this.config.animation = false;
this.hide();
this.show();
this.config.animation = initConfigAnimation;
}; // Static
Tooltip._jQueryInterface = function _jQueryInterface(config) {
return this.each(function() {
var data = $$$1(this).data(DATA_KEY);
var _config = typeof config === 'object' && config;
if (!data && /dispose|hide/.test(config)) {
return;
}
if (!data) {
data = new Tooltip(this, _config);
$$$1(this).data(DATA_KEY, data);
}
if (typeof config === 'string') {
if (typeof data[config] === 'undefined') {
throw new TypeError('No method named "' + config + '"');
}
data[config]();
}
});
};
_createClass(Tooltip, null, [
{
key: 'VERSION',
get: function get() {
return VERSION;
}
},
{
key: 'Default',
get: function get() {
return Default;
}
},
{
key: 'NAME',
get: function get() {
return NAME;
}
},
{
key: 'DATA_KEY',
get: function get() {
return DATA_KEY;
}
},
{
key: 'Event',
get: function get() {
return Event;
}
},
{
key: 'EVENT_KEY',
get: function get() {
return EVENT_KEY;
}
},
{
key: 'DefaultType',
get: function get() {
return DefaultType;
}
}
]);
return Tooltip;
}();
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$$$1.fn[NAME] = Tooltip._jQueryInterface;
$$$1.fn[NAME].Constructor = Tooltip;
$$$1.fn[NAME].noConflict = function() {
$$$1.fn[NAME] = JQUERY_NO_CONFLICT;
return Tooltip._jQueryInterface;
};
return Tooltip;
}($, Popper);
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0-alpha.6): index.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
(function($$$1) {
if (typeof $$$1 === 'undefined') {
throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.');
}
var version = $$$1.fn.jquery.split(' ')[0].split('.');
var minMajor = 1;
var ltMajor = 2;
var minMinor = 9;
var minPatch = 1;
var maxMajor = 4;
if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) {
throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0');
}
})($);
exports.Util = Util;
exports.Tooltip = Tooltip;
Object.defineProperty(exports, '__esModule', {value: true});
})));
/**
* vivus - JavaScript library to make drawing animation on SVG
* @version v0.4.0
* @link https://github.com/maxwellito/vivus
* @license MIT
*/
'use strict';
(function (window, document) {
'use strict';
/**
* Pathformer
* Beta version
*
* Take any SVG version 1.1 and transform
* child elements to 'path' elements
*
* This code is purely forked from
* https://github.com/Waest/SVGPathConverter
*/
/**
* Class constructor
*
* @param {DOM|String} element Dom element of the SVG or id of it
*/
function Pathformer(element) {
// Test params
if (typeof element === 'undefined') {
throw new Error('Pathformer [constructor]: "element" parameter is required');
}
// Set the element
if (element.constructor === String) {
element = document.getElementById(element);
if (!element) {
throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID');
}
}
if (element.constructor instanceof window.SVGElement || /^svg$/i.test(element.nodeName)) {
this.el = element;
} else {
throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement');
}
// Start
this.scan(element);
}
/**
* List of tags which can be transformed
* to path elements
*
* @type {Array}
*/
Pathformer.prototype.TYPES = ['line', 'ellipse', 'circle', 'polygon', 'polyline', 'rect'];
/**
* List of attribute names which contain
* data. This array list them to check if
* they contain bad values, like percentage.
*
* @type {Array}
*/
Pathformer.prototype.ATTR_WATCH = ['cx', 'cy', 'points', 'r', 'rx', 'ry', 'x', 'x1', 'x2', 'y', 'y1', 'y2'];
/**
* Finds the elements compatible for transform
* and apply the liked method
*
* @param {object} options Object from the constructor
*/
Pathformer.prototype.scan = function (svg) {
var fn, element, pathData, pathDom,
elements = svg.querySelectorAll(this.TYPES.join(','));
for (var i = 0; i < elements.length; i++) {
element = elements[i];
fn = this[element.tagName.toLowerCase() + 'ToPath'];
pathData = fn(this.parseAttr(element.attributes));
pathDom = this.pathMaker(element, pathData);
element.parentNode.replaceChild(pathDom, element);
}
};
/**
* Read `line` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element Line element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.lineToPath = function (element) {
var newElement = {},
x1 = element.x1 || 0,
y1 = element.y1 || 0,
x2 = element.x2 || 0,
y2 = element.y2 || 0;
newElement.d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
return newElement;
};
/**
* Read `rect` element to extract and transform
* data, to make it ready for a `path` object.
* The radius-border is not taken in charge yet.
* (your help is more than welcomed)
*
* @param {DOMelement} element Rect element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.rectToPath = function (element) {
var newElement = {},
x = parseFloat(element.x) || 0,
y = parseFloat(element.y) || 0,
width = parseFloat(element.width) || 0,
height = parseFloat(element.height) || 0;
newElement.d = 'M' + x + ' ' + y + ' ';
newElement.d += 'L' + (x + width) + ' ' + y + ' ';
newElement.d += 'L' + (x + width) + ' ' + (y + height) + ' ';
newElement.d += 'L' + x + ' ' + (y + height) + ' Z';
return newElement;
};
/**
* Read `polyline` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element Polyline element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.polylineToPath = function (element) {
var newElement = {},
points = element.points.trim().split(' '),
i, path;
// Reformatting if points are defined without commas
if (element.points.indexOf(',') === -1) {
var formattedPoints = [];
for (i = 0; i < points.length; i+=2) {
formattedPoints.push(points[i] + ',' + points[i+1]);
}
points = formattedPoints;
}
// Generate the path.d value
path = 'M' + points[0];
for(i = 1; i < points.length; i++) {
if (points[i].indexOf(',') !== -1) {
path += 'L' + points[i];
}
}
newElement.d = path;
return newElement;
};
/**
* Read `polygon` element to extract and transform
* data, to make it ready for a `path` object.
* This method rely on polylineToPath, because the
* logic is similar. The path created is just closed,
* so it needs an 'Z' at the end.
*
* @param {DOMelement} element Polygon element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.polygonToPath = function (element) {
var newElement = Pathformer.prototype.polylineToPath(element);
newElement.d += 'Z';
return newElement;
};
/**
* Read `ellipse` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element ellipse element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.ellipseToPath = function (element) {
var newElement = {},
rx = parseFloat(element.rx) || 0,
ry = parseFloat(element.ry) || 0,
cx = parseFloat(element.cx) || 0,
cy = parseFloat(element.cy) || 0,
startX = cx - rx,
startY = cy,
endX = parseFloat(cx) + parseFloat(rx),
endY = cy;
newElement.d = 'M' + startX + ',' + startY +
'A' + rx + ',' + ry + ' 0,1,1 ' + endX + ',' + endY +
'A' + rx + ',' + ry + ' 0,1,1 ' + startX + ',' + endY;
return newElement;
};
/**
* Read `circle` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element Circle element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.circleToPath = function (element) {
var newElement = {},
r = parseFloat(element.r) || 0,
cx = parseFloat(element.cx) || 0,
cy = parseFloat(element.cy) || 0,
startX = cx - r,
startY = cy,
endX = parseFloat(cx) + parseFloat(r),
endY = cy;
newElement.d = 'M' + startX + ',' + startY +
'A' + r + ',' + r + ' 0,1,1 ' + endX + ',' + endY +
'A' + r + ',' + r + ' 0,1,1 ' + startX + ',' + endY;
return newElement;
};
/**
* Create `path` elements form original element
* and prepared objects
*
* @param {DOMelement} element Original element to transform
* @param {object} pathData Path data (from `toPath` methods)
* @return {DOMelement} Path element
*/
Pathformer.prototype.pathMaker = function (element, pathData) {
var i, attr, pathTag = document.createElementNS('http://www.w3.org/2000/svg','path');
for(i = 0; i < element.attributes.length; i++) {
attr = element.attributes[i];
if (this.ATTR_WATCH.indexOf(attr.name) === -1) {
pathTag.setAttribute(attr.name, attr.value);
}
}
for(i in pathData) {
pathTag.setAttribute(i, pathData[i]);
}
return pathTag;
};
/**
* Parse attributes of a DOM element to
* get an object of attribute => value
*
* @param {NamedNodeMap} attributes Attributes object from DOM element to parse
* @return {object} Object of attributes
*/
Pathformer.prototype.parseAttr = function (element) {
var attr, output = {};
for (var i = 0; i < element.length; i++) {
attr = element[i];
// Check if no data attribute contains '%', or the transformation is impossible
if (this.ATTR_WATCH.indexOf(attr.name) !== -1 && attr.value.indexOf('%') !== -1) {
throw new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.');
}
output[attr.name] = attr.value;
}
return output;
};
'use strict';
var requestAnimFrame, cancelAnimFrame, parsePositiveInt;
/**
* Vivus
* Beta version
*
* Take any SVG and make the animation
* to give give the impression of live drawing
*
* This in more than just inspired from codrops
* At that point, it's a pure fork.
*/
/**
* Class constructor
* option structure
* type: 'delayed'|'sync'|'oneByOne'|'script' (to know if the items must be drawn synchronously or not, default: delayed)
* duration: <int> (in frames)
* start: 'inViewport'|'manual'|'autostart' (start automatically the animation, default: inViewport)
* delay: <int> (delay between the drawing of first and last path)
* dashGap <integer> whitespace extra margin between dashes
* pathTimingFunction <function> timing animation function for each path element of the SVG
* animTimingFunction <function> timing animation function for the complete SVG
* forceRender <boolean> force the browser to re-render all updated path items
* selfDestroy <boolean> removes all extra styling on the SVG, and leaves it as original
*
* The attribute 'type' is by default on 'delayed'.
* - 'delayed'
* all paths are draw at the same time but with a
* little delay between them before start
* - 'sync'
* all path are start and finish at the same time
* - 'oneByOne'
* only one path is draw at the time
* the end of the first one will trigger the draw
* of the next one
*
* All these values can be overwritten individually
* for each path item in the SVG
* The value of frames will always take the advantage of
* the duration value.
* If you fail somewhere, an error will be thrown.
* Good luck.
*
* @constructor
* @this {Vivus}
* @param {DOM|String} element Dom element of the SVG or id of it
* @param {Object} options Options about the animation
* @param {Function} callback Callback for the end of the animation
*/
function Vivus (element, options, callback) {
// Setup
this.isReady = false;
this.setElement(element, options);
this.setOptions(options);
this.setCallback(callback);
if (this.isReady) {
this.init();
}
}
/**
* Timing functions
**************************************
*
* Default functions to help developers.
* It always take a number as parameter (between 0 to 1) then
* return a number (between 0 and 1)
*/
Vivus.LINEAR = function (x) {return x;};
Vivus.EASE = function (x) {return -Math.cos(x * Math.PI) / 2 + 0.5;};
Vivus.EASE_OUT = function (x) {return 1 - Math.pow(1-x, 3);};
Vivus.EASE_IN = function (x) {return Math.pow(x, 3);};
Vivus.EASE_OUT_BOUNCE = function (x) {
var base = -Math.cos(x * (0.5 * Math.PI)) + 1,
rate = Math.pow(base,1.5),
rateR = Math.pow(1 - x, 2),
progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI) )) + 1;
return (1- rateR) + (progress * rateR);
};
/**
* Setters
**************************************
*/
/**
* Check and set the element in the instance
* The method will not return anything, but will throw an
* error if the parameter is invalid
*
* @param {DOM|String} element SVG Dom element or id of it
*/
Vivus.prototype.setElement = function (element, options) {
// Basic check
if (typeof element === 'undefined') {
throw new Error('Vivus [constructor]: "element" parameter is required');
}
// Set the element
if (element.constructor === String) {
element = document.getElementById(element);
if (!element) {
throw new Error('Vivus [constructor]: "element" parameter is not related to an existing ID');
}
}
this.parentEl = element;
// Create the object element if the property `file` exists in the options object
if (options && options.file) {
var objElm = document.createElement('object');
objElm.setAttribute('type', 'image/svg+xml');
objElm.setAttribute('data', options.file);
objElm.setAttribute('built-by-vivus', 'true');
element.appendChild(objElm);
element = objElm;
}
switch (element.constructor) {
case window.SVGSVGElement:
case window.SVGElement:
this.el = element;
this.isReady = true;
break;
case window.HTMLObjectElement:
// If we have to wait for it
var onLoad, self;
self = this;
onLoad = function (e) {
if (self.isReady) {
return;
}
self.el = element.contentDocument && element.contentDocument.querySelector('svg');
if (!self.el && e) {
throw new Error('Vivus [constructor]: object loaded does not contain any SVG');
}
else if (self.el) {
if (element.getAttribute('built-by-vivus')) {
self.parentEl.insertBefore(self.el, element);
self.parentEl.removeChild(element);
self.el.setAttribute('width', '100%');
self.el.setAttribute('height', '100%');
}
self.isReady = true;
self.init();
return true;
}
};
if (!onLoad()) {
element.addEventListener('load', onLoad);
}
break;
default:
if (!element){
throw new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)');
}
}
};
/**
* Set up user option to the instance
* The method will not return anything, but will throw an
* error if the parameter is invalid
*
* @param {object} options Object from the constructor
*/
Vivus.prototype.setOptions = function (options) {
var allowedTypes = ['delayed', 'sync', 'async', 'nsync', 'oneByOne', 'scenario', 'scenario-sync'];
var allowedStarts = ['inViewport', 'manual', 'autostart'];
// Basic check
if (options !== undefined && options.constructor !== Object) {
throw new Error('Vivus [constructor]: "options" parameter must be an object');
}
else {
options = options || {};
}
// Set the animation type
if (options.type && allowedTypes.indexOf(options.type) === -1) {
throw new Error('Vivus [constructor]: ' + options.type + ' is not an existing animation `type`');
}
else {
this.type = options.type || allowedTypes[0];
}
// Set the start type
if (options.start && allowedStarts.indexOf(options.start) === -1) {
throw new Error('Vivus [constructor]: ' + options.start + ' is not an existing `start` option');
}
else {
this.start = options.start || allowedStarts[0];
}
this.isIE = (window.navigator.userAgent.indexOf('MSIE') !== -1 || window.navigator.userAgent.indexOf('Trident/') !== -1 || window.navigator.userAgent.indexOf('Edge/') !== -1 );
this.duration = parsePositiveInt(options.duration, 120);
this.delay = parsePositiveInt(options.delay, null);
this.dashGap = parsePositiveInt(options.dashGap, 1);
this.forceRender = options.hasOwnProperty('forceRender') ? !!options.forceRender : this.isIE;
this.reverseStack = !!options.reverseStack;
this.selfDestroy = !!options.selfDestroy;
this.onReady = options.onReady;
this.map = [];
this.frameLength = this.currentFrame = this.delayUnit = this.speed = this.handle = null;
this.ignoreInvisible = options.hasOwnProperty('ignoreInvisible') ? !!options.ignoreInvisible : false;
this.animTimingFunction = options.animTimingFunction || Vivus.LINEAR;
this.pathTimingFunction = options.pathTimingFunction || Vivus.LINEAR;
if (this.delay >= this.duration) {
throw new Error('Vivus [constructor]: delay must be shorter than duration');
}
};
/**
* Set up callback to the instance
* The method will not return enything, but will throw an
* error if the parameter is invalid
*
* @param {Function} callback Callback for the animation end
*/
Vivus.prototype.setCallback = function (callback) {
// Basic check
if (!!callback && callback.constructor !== Function) {
throw new Error('Vivus [constructor]: "callback" parameter must be a function');
}
this.callback = callback || function () {};
};
/**
* Core
**************************************
*/
/**
* Map the svg, path by path.
* The method return nothing, it just fill the
* `map` array. Each item in this array represent
* a path element from the SVG, with informations for
* the animation.
*
* ```
* [
* {
* el: <DOMobj> the path element
* length: <number> length of the path line
* startAt: <number> time start of the path animation (in frames)
* duration: <number> path animation duration (in frames)
* },
* ...
* ]
* ```
*
*/
Vivus.prototype.mapping = function () {
var i, paths, path, pAttrs, pathObj, totalLength, lengthMeter, timePoint;
timePoint = totalLength = lengthMeter = 0;
paths = this.el.querySelectorAll('path');
for (i = 0; i < paths.length; i++) {
path = paths[i];
if (this.isInvisible(path)) {
continue;
}
pathObj = {
el: path,
length: Math.ceil(path.getTotalLength())
};
// Test if the path length is correct
if (isNaN(pathObj.length)) {
if (window.console && console.warn) {
console.warn('Vivus [mapping]: cannot retrieve a path element length', path);
}
continue;
}
this.map.push(pathObj);
path.style.strokeDasharray = pathObj.length + ' ' + (pathObj.length + this.dashGap * 2);
path.style.strokeDashoffset = pathObj.length + this.dashGap;
pathObj.length += this.dashGap;
totalLength += pathObj.length;
this.renderPath(i);
}
totalLength = totalLength === 0 ? 1 : totalLength;
this.delay = this.delay === null ? this.duration / 3 : this.delay;
this.delayUnit = this.delay / (paths.length > 1 ? paths.length - 1 : 1);
// Reverse stack if asked
if (this.reverseStack) {
this.map.reverse();
}
for (i = 0; i < this.map.length; i++) {
pathObj = this.map[i];
switch (this.type) {
case 'delayed':
pathObj.startAt = this.delayUnit * i;
pathObj.duration = this.duration - this.delay;
break;
case 'oneByOne':
pathObj.startAt = lengthMeter / totalLength * this.duration;
pathObj.duration = pathObj.length / totalLength * this.duration;
break;
case 'sync':
case 'async':
case 'nsync':
pathObj.startAt = 0;
pathObj.duration = this.duration;
break;
case 'scenario-sync':
path = pathObj.el;
pAttrs = this.parseAttr(path);
pathObj.startAt = timePoint + (parsePositiveInt(pAttrs['data-delay'], this.delayUnit) || 0);
pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration);
timePoint = pAttrs['data-async'] !== undefined ? pathObj.startAt : pathObj.startAt + pathObj.duration;
this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration));
break;
case 'scenario':
path = pathObj.el;
pAttrs = this.parseAttr(path);
pathObj.startAt = parsePositiveInt(pAttrs['data-start'], this.delayUnit) || 0;
pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration);
this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration));
break;
}
lengthMeter += pathObj.length;
this.frameLength = this.frameLength || this.duration;
}
};
/**
* Interval method to draw the SVG from current
* position of the animation. It update the value of
* `currentFrame` and re-trace the SVG.
*
* It use this.handle to store the requestAnimationFrame
* and clear it one the animation is stopped. So this
* attribute can be used to know if the animation is
* playing.
*
* Once the animation at the end, this method will
* trigger the Vivus callback.
*
*/
Vivus.prototype.drawer = function () {
var self = this;
this.currentFrame += this.speed;
if (this.currentFrame <= 0) {
this.stop();
this.reset();
} else if (this.currentFrame >= this.frameLength) {
this.stop();
this.currentFrame = this.frameLength;
this.trace();
if (this.selfDestroy) {
this.destroy();
}
} else {
this.trace();
this.handle = requestAnimFrame(function () {
self.drawer();
});
return;
}
this.callback(this);
if (this.instanceCallback) {
this.instanceCallback(this);
this.instanceCallback = null;
}
};
/**
* Draw the SVG at the current instant from the
* `currentFrame` value. Here is where most of the magic is.
* The trick is to use the `strokeDashoffset` style property.
*
* For optimisation reasons, a new property called `progress`
* is added in each item of `map`. This one contain the current
* progress of the path element. Only if the new value is different
* the new value will be applied to the DOM element. This
* method save a lot of resources to re-render the SVG. And could
* be improved if the animation couldn't be played forward.
*
*/
Vivus.prototype.trace = function () {
var i, progress, path, currentFrame;
currentFrame = this.animTimingFunction(this.currentFrame / this.frameLength) * this.frameLength;
for (i = 0; i < this.map.length; i++) {
path = this.map[i];
progress = (currentFrame - path.startAt) / path.duration;
progress = this.pathTimingFunction(Math.max(0, Math.min(1, progress)));
if (path.progress !== progress) {
path.progress = progress;
path.el.style.strokeDashoffset = Math.floor(path.length * (1 - progress));
this.renderPath(i);
}
}
};
/**
* Method forcing the browser to re-render a path element
* from it's index in the map. Depending on the `forceRender`
* value.
* The trick is to replace the path element by it's clone.
* This practice is not recommended because it's asking more
* ressources, too much DOM manupulation..
* but it's the only way to let the magic happen on IE.
* By default, this fallback is only applied on IE.
*
* @param {Number} index Path index
*/
Vivus.prototype.renderPath = function (index) {
if (this.forceRender && this.map && this.map[index]) {
var pathObj = this.map[index],
newPath = pathObj.el.cloneNode(true);
pathObj.el.parentNode.replaceChild(newPath, pathObj.el);
pathObj.el = newPath;
}
};
/**
* When the SVG object is loaded and ready,
* this method will continue the initialisation.
*
* This this mainly due to the case of passing an
* object tag in the constructor. It will wait
* the end of the loading to initialise.
*
*/
Vivus.prototype.init = function () {
// Set object variables
this.frameLength = 0;
this.currentFrame = 0;
this.map = [];
// Start
new Pathformer(this.el);
this.mapping();
this.starter();
if (this.onReady) {
this.onReady(this);
}
};
/**
* Trigger to start of the animation.
* Depending on the `start` value, a different script
* will be applied.
*
* If the `start` value is not valid, an error will be thrown.
* Even if technically, this is impossible.
*
*/
Vivus.prototype.starter = function () {
switch (this.start) {
case 'manual':
return;
case 'autostart':
this.play();
break;
case 'inViewport':
var self = this,
listener = function () {
if (self.isInViewport(self.parentEl, 1)) {
self.play();
window.removeEventListener('scroll', listener);
}
};
window.addEventListener('scroll', listener);
listener();
break;
}
};
/**
* Controls
**************************************
*/
/**
* Get the current status of the animation between
* three different states: 'start', 'progress', 'end'.
* @return {string} Instance status
*/
Vivus.prototype.getStatus = function () {
return this.currentFrame === 0 ? 'start' : this.currentFrame === this.frameLength ? 'end' : 'progress';
};
/**
* Reset the instance to the initial state : undraw
* Be careful, it just reset the animation, if you're
* playing the animation, this won't stop it. But just
* make it start from start.
*
*/
Vivus.prototype.reset = function () {
return this.setFrameProgress(0);
};
/**
* Set the instance to the final state : drawn
* Be careful, it just set the animation, if you're
* playing the animation on rewind, this won't stop it.
* But just make it start from the end.
*
*/
Vivus.prototype.finish = function () {
return this.setFrameProgress(1);
};
/**
* Set the level of progress of the drawing.
*
* @param {number} progress Level of progress to set
*/
Vivus.prototype.setFrameProgress = function (progress) {
progress = Math.min(1, Math.max(0, progress));
this.currentFrame = Math.round(this.frameLength * progress);
this.trace();
return this;
};
/**
* Play the animation at the desired speed.
* Speed must be a valid number (no zero).
* By default, the speed value is 1.
* But a negative value is accepted to go forward.
*
* And works with float too.
* But don't forget we are in JavaScript, se be nice
* with him and give him a 1/2^x value.
*
* @param {number} speed Animation speed [optional]
*/
Vivus.prototype.play = function (speed, callback) {
this.instanceCallback = null;
if (speed && typeof speed === 'function') {
this.instanceCallback = speed; // first parameter is actually the callback function
speed = null;
}
else if (speed && typeof speed !== 'number') {
throw new Error('Vivus [play]: invalid speed');
}
// if the first parameter wasn't the callback, check if the seconds was
if (callback && typeof(callback) === 'function' && !this.instanceCallback) {
this.instanceCallback = callback;
}
this.speed = speed || 1;
if (!this.handle) {
this.drawer();
}
return this;
};
/**
* Stop the current animation, if on progress.
* Should not trigger any error.
*
*/
Vivus.prototype.stop = function () {
if (this.handle) {
cancelAnimFrame(this.handle);
this.handle = null;
}
return this;
};
/**
* Destroy the instance.
* Remove all bad styling attributes on all
* path tags
*
*/
Vivus.prototype.destroy = function () {
this.stop();
var i, path;
for (i = 0; i < this.map.length; i++) {
path = this.map[i];
path.el.style.strokeDashoffset = null;
path.el.style.strokeDasharray = null;
this.renderPath(i);
}
};
/**
* Utils methods
* include methods from Codrops
**************************************
*/
/**
* Method to best guess if a path should added into
* the animation or not.
*
* 1. Use the `data-vivus-ignore` attribute if set
* 2. Check if the instance must ignore invisible paths
* 3. Check if the path is visible
*
* For now the visibility checking is unstable.
* It will be used for a beta phase.
*
* Other improvments are planned. Like detecting
* is the path got a stroke or a valid opacity.
*/
Vivus.prototype.isInvisible = function (el) {
var rect,
ignoreAttr = el.getAttribute('data-ignore');
if (ignoreAttr !== null) {
return ignoreAttr !== 'false';
}
if (this.ignoreInvisible) {
rect = el.getBoundingClientRect();
return !rect.width && !rect.height;
}
else {
return false;
}
};
/**
* Parse attributes of a DOM element to
* get an object of {attributeName => attributeValue}
*
* @param {object} element DOM element to parse
* @return {object} Object of attributes
*/
Vivus.prototype.parseAttr = function (element) {
var attr, output = {};
if (element && element.attributes) {
for (var i = 0; i < element.attributes.length; i++) {
attr = element.attributes[i];
output[attr.name] = attr.value;
}
}
return output;
};
/**
* Reply if an element is in the page viewport
*
* @param {object} el Element to observe
* @param {number} h Percentage of height
* @return {boolean}
*/
Vivus.prototype.isInViewport = function (el, h) {
var scrolled = this.scrollY(),
viewed = scrolled + this.getViewportH(),
elBCR = el.getBoundingClientRect(),
elHeight = elBCR.height,
elTop = scrolled + elBCR.top,
elBottom = elTop + elHeight;
// if 0, the element is considered in the viewport as soon as it enters.
// if 1, the element is considered in the viewport only when it's fully inside
// value in percentage (1 >= h >= 0)
h = h || 0;
return (elTop + elHeight * h) <= viewed && (elBottom) >= scrolled;
};
/**
* Alias for document element
*
* @type {DOMelement}
*/
Vivus.prototype.docElem = window.document.documentElement;
/**
* Get the viewport height in pixels
*
* @return {integer} Viewport height
*/
Vivus.prototype.getViewportH = function () {
var client = this.docElem.clientHeight,
inner = window.innerHeight;
if (client < inner) {
return inner;
}
else {
return client;
}
};
/**
* Get the page Y offset
*
* @return {integer} Page Y offset
*/
Vivus.prototype.scrollY = function () {
return window.pageYOffset || this.docElem.scrollTop;
};
/**
* Alias for `requestAnimationFrame` or
* `setTimeout` function for deprecated browsers.
*
*/
requestAnimFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback){
return window.setTimeout(callback, 1000 / 60);
}
);
})();
/**
* Alias for `cancelAnimationFrame` or
* `cancelTimeout` function for deprecated browsers.
*
*/
cancelAnimFrame = (function () {
return (
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(id){
return window.clearTimeout(id);
}
);
})();
/**
* Parse string to integer.
* If the number is not positive or null
* the method will return the default value
* or 0 if undefined
*
* @param {string} value String to parse
* @param {*} defaultValue Value to return if the result parsed is invalid
* @return {number}
*
*/
parsePositiveInt = function (value, defaultValue) {
var output = parseInt(value, 10);
return (output >= 0) ? output : defaultValue;
};
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function() {
return Vivus;
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = Vivus;
} else {
// Browser globals
window.Vivus = Vivus;
}
}(window, document));
/*!
Waypoints - 4.0.1
Copyright © 2011-2016 Caleb Troughton
Licensed under the MIT license.
https://github.com/imakewebthings/waypoints/blob/master/licenses.txt
*/
(function() {
'use strict'
var keyCounter = 0
var allWaypoints = {}
/* http://imakewebthings.com/waypoints/api/waypoint */
function Waypoint(options) {
if (!options) {
throw new Error('No options passed to Waypoint constructor')
}
if (!options.element) {
throw new Error('No element option passed to Waypoint constructor')
}
if (!options.handler) {
throw new Error('No handler option passed to Waypoint constructor')
}
this.key = 'waypoint-' + keyCounter
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
this.element = this.options.element
this.adapter = new Waypoint.Adapter(this.element)
this.callback = options.handler
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
this.enabled = this.options.enabled
this.triggerPoint = null
this.group = Waypoint.Group.findOrCreate({
name: this.options.group,
axis: this.axis
})
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
if (Waypoint.offsetAliases[this.options.offset]) {
this.options.offset = Waypoint.offsetAliases[this.options.offset]
}
this.group.add(this)
this.context.add(this)
allWaypoints[this.key] = this
keyCounter += 1
}
/* Private */
Waypoint.prototype.queueTrigger = function(direction) {
this.group.queueTrigger(this, direction)
}
/* Private */
Waypoint.prototype.trigger = function(args) {
if (!this.enabled) {
return
}
if (this.callback) {
this.callback.apply(this, args)
}
}
/* Public */
/* http://imakewebthings.com/waypoints/api/destroy */
Waypoint.prototype.destroy = function() {
this.context.remove(this)
this.group.remove(this)
delete allWaypoints[this.key]
}
/* Public */
/* http://imakewebthings.com/waypoints/api/disable */
Waypoint.prototype.disable = function() {
this.enabled = false
return this
}
/* Public */
/* http://imakewebthings.com/waypoints/api/enable */
Waypoint.prototype.enable = function() {
this.context.refresh()
this.enabled = true
return this
}
/* Public */
/* http://imakewebthings.com/waypoints/api/next */
Waypoint.prototype.next = function() {
return this.group.next(this)
}
/* Public */
/* http://imakewebthings.com/waypoints/api/previous */
Waypoint.prototype.previous = function() {
return this.group.previous(this)
}
/* Private */
Waypoint.invokeAll = function(method) {
var allWaypointsArray = []
for (var waypointKey in allWaypoints) {
allWaypointsArray.push(allWaypoints[waypointKey])
}
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
allWaypointsArray[i][method]()
}
}
/* Public */
/* http://imakewebthings.com/waypoints/api/destroy-all */
Waypoint.destroyAll = function() {
Waypoint.invokeAll('destroy')
}
/* Public */
/* http://imakewebthings.com/waypoints/api/disable-all */
Waypoint.disableAll = function() {
Waypoint.invokeAll('disable')
}
/* Public */
/* http://imakewebthings.com/waypoints/api/enable-all */
Waypoint.enableAll = function() {
Waypoint.Context.refreshAll()
for (var waypointKey in allWaypoints) {
allWaypoints[waypointKey].enabled = true
}
return this
}
/* Public */
/* http://imakewebthings.com/waypoints/api/refresh-all */
Waypoint.refreshAll = function() {
Waypoint.Context.refreshAll()
}
/* Public */
/* http://imakewebthings.com/waypoints/api/viewport-height */
Waypoint.viewportHeight = function() {
return window.innerHeight || document.documentElement.clientHeight
}
/* Public */
/* http://imakewebthings.com/waypoints/api/viewport-width */
Waypoint.viewportWidth = function() {
return document.documentElement.clientWidth
}
Waypoint.adapters = []
Waypoint.defaults = {
context: window,
continuous: true,
enabled: true,
group: 'default',
horizontal: false,
offset: 0
}
Waypoint.offsetAliases = {
'bottom-in-view': function() {
return this.context.innerHeight() - this.adapter.outerHeight()
},
'right-in-view': function() {
return this.context.innerWidth() - this.adapter.outerWidth()
}
}
window.Waypoint = Waypoint
}())
;(function() {
'use strict'
function requestAnimationFrameShim(callback) {
window.setTimeout(callback, 1000 / 60)
}
var keyCounter = 0
var contexts = {}
var Waypoint = window.Waypoint
var oldWindowLoad = window.onload
/* http://imakewebthings.com/waypoints/api/context */
function Context(element) {
this.element = element
this.Adapter = Waypoint.Adapter
this.adapter = new this.Adapter(element)
this.key = 'waypoint-context-' + keyCounter
this.didScroll = false
this.didResize = false
this.oldScroll = {
x: this.adapter.scrollLeft(),
y: this.adapter.scrollTop()
}
this.waypoints = {
vertical: {},
horizontal: {}
}
element.waypointContextKey = this.key
contexts[element.waypointContextKey] = this
keyCounter += 1
if (!Waypoint.windowContext) {
Waypoint.windowContext = true
Waypoint.windowContext = new Context(window)
}
this.createThrottledScrollHandler()
this.createThrottledResizeHandler()
}
/* Private */
Context.prototype.add = function(waypoint) {
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
this.waypoints[axis][waypoint.key] = waypoint
this.refresh()
}
/* Private */
Context.prototype.checkEmpty = function() {
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
var isWindow = this.element == this.element.window
if (horizontalEmpty && verticalEmpty && !isWindow) {
this.adapter.off('.waypoints')
delete contexts[this.key]
}
}
/* Private */
Context.prototype.createThrottledResizeHandler = function() {
var self = this
function resizeHandler() {
self.handleResize()
self.didResize = false
}
this.adapter.on('resize.waypoints', function() {
if (!self.didResize) {
self.didResize = true
Waypoint.requestAnimationFrame(resizeHandler)
}
})
}
/* Private */
Context.prototype.createThrottledScrollHandler = function() {
var self = this
function scrollHandler() {
self.handleScroll()
self.didScroll = false
}
this.adapter.on('scroll.waypoints', function() {
if (!self.didScroll || Waypoint.isTouch) {
self.didScroll = true
Waypoint.requestAnimationFrame(scrollHandler)
}
})
}
/* Private */
Context.prototype.handleResize = function() {
Waypoint.Context.refreshAll()
}
/* Private */
Context.prototype.handleScroll = function() {
var triggeredGroups = {}
var axes = {
horizontal: {
newScroll: this.adapter.scrollLeft(),
oldScroll: this.oldScroll.x,
forward: 'right',
backward: 'left'
},
vertical: {
newScroll: this.adapter.scrollTop(),
oldScroll: this.oldScroll.y,
forward: 'down',
backward: 'up'
}
}
for (var axisKey in axes) {
var axis = axes[axisKey]
var isForward = axis.newScroll > axis.oldScroll
var direction = isForward ? axis.forward : axis.backward
for (var waypointKey in this.waypoints[axisKey]) {
var waypoint = this.waypoints[axisKey][waypointKey]
if (waypoint.triggerPoint === null) {
continue
}
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
if (crossedForward || crossedBackward) {
waypoint.queueTrigger(direction)
triggeredGroups[waypoint.group.id] = waypoint.group
}
}
}
for (var groupKey in triggeredGroups) {
triggeredGroups[groupKey].flushTriggers()
}
this.oldScroll = {
x: axes.horizontal.newScroll,
y: axes.vertical.newScroll
}
}
/* Private */
Context.prototype.innerHeight = function() {
/*eslint-disable eqeqeq */
if (this.element == this.element.window) {
return Waypoint.viewportHeight()
}
/*eslint-enable eqeqeq */
return this.adapter.innerHeight()
}
/* Private */
Context.prototype.remove = function(waypoint) {
delete this.waypoints[waypoint.axis][waypoint.key]
this.checkEmpty()
}
/* Private */
Context.prototype.innerWidth = function() {
/*eslint-disable eqeqeq */
if (this.element == this.element.window) {
return Waypoint.viewportWidth()
}
/*eslint-enable eqeqeq */
return this.adapter.innerWidth()
}
/* Public */
/* http://imakewebthings.com/waypoints/api/context-destroy */
Context.prototype.destroy = function() {
var allWaypoints = []
for (var axis in this.waypoints) {
for (var waypointKey in this.waypoints[axis]) {
allWaypoints.push(this.waypoints[axis][waypointKey])
}
}
for (var i = 0, end = allWaypoints.length; i < end; i++) {
allWaypoints[i].destroy()
}
}
/* Public */
/* http://imakewebthings.com/waypoints/api/context-refresh */
Context.prototype.refresh = function() {
/*eslint-disable eqeqeq */
var isWindow = this.element == this.element.window
/*eslint-enable eqeqeq */
var contextOffset = isWindow ? undefined : this.adapter.offset()
var triggeredGroups = {}
var axes
this.handleScroll()
axes = {
horizontal: {
contextOffset: isWindow ? 0 : contextOffset.left,
contextScroll: isWindow ? 0 : this.oldScroll.x,
contextDimension: this.innerWidth(),
oldScroll: this.oldScroll.x,
forward: 'right',
backward: 'left',
offsetProp: 'left'
},
vertical: {
contextOffset: isWindow ? 0 : contextOffset.top,
contextScroll: isWindow ? 0 : this.oldScroll.y,
contextDimension: this.innerHeight(),
oldScroll: this.oldScroll.y,
forward: 'down',
backward: 'up',
offsetProp: 'top'
}
}
for (var axisKey in axes) {
var axis = axes[axisKey]
for (var waypointKey in this.waypoints[axisKey]) {
var waypoint = this.waypoints[axisKey][waypointKey]
var adjustment = waypoint.options.offset
var oldTriggerPoint = waypoint.triggerPoint
var elementOffset = 0
var freshWaypoint = oldTriggerPoint == null
var contextModifier, wasBeforeScroll, nowAfterScroll
var triggeredBackward, triggeredForward
if (waypoint.element !== waypoint.element.window) {
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
}
if (typeof adjustment === 'function') {
adjustment = adjustment.apply(waypoint)
}
else if (typeof adjustment === 'string') {
adjustment = parseFloat(adjustment)
if (waypoint.options.offset.indexOf('%') > - 1) {
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
}
}
contextModifier = axis.contextScroll - axis.contextOffset
waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment)
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
triggeredBackward = wasBeforeScroll && nowAfterScroll
triggeredForward = !wasBeforeScroll && !nowAfterScroll
if (!freshWaypoint && triggeredBackward) {
waypoint.queueTrigger(axis.backward)
triggeredGroups[waypoint.group.id] = waypoint.group
}
else if (!freshWaypoint && triggeredForward) {
waypoint.queueTrigger(axis.forward)
triggeredGroups[waypoint.group.id] = waypoint.group
}
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
waypoint.queueTrigger(axis.forward)
triggeredGroups[waypoint.group.id] = waypoint.group
}
}
}
Waypoint.requestAnimationFrame(function() {
for (var groupKey in triggeredGroups) {
triggeredGroups[groupKey].flushTriggers()
}
})
return this
}
/* Private */
Context.findOrCreateByElement = function(element) {
return Context.findByElement(element) || new Context(element)
}
/* Private */
Context.refreshAll = function() {
for (var contextId in contexts) {
contexts[contextId].refresh()
}
}
/* Public */
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
Context.findByElement = function(element) {
return contexts[element.waypointContextKey]
}
window.onload = function() {
if (oldWindowLoad) {
oldWindowLoad()
}
Context.refreshAll()
}
Waypoint.requestAnimationFrame = function(callback) {
var requestFn = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
requestAnimationFrameShim
requestFn.call(window, callback)
}
Waypoint.Context = Context
}())
;(function() {
'use strict'
function byTriggerPoint(a, b) {
return a.triggerPoint - b.triggerPoint
}
function byReverseTriggerPoint(a, b) {
return b.triggerPoint - a.triggerPoint
}
var groups = {
vertical: {},
horizontal: {}
}
var Waypoint = window.Waypoint
/* http://imakewebthings.com/waypoints/api/group */
function Group(options) {
this.name = options.name
this.axis = options.axis
this.id = this.name + '-' + this.axis
this.waypoints = []
this.clearTriggerQueues()
groups[this.axis][this.name] = this
}
/* Private */
Group.prototype.add = function(waypoint) {
this.waypoints.push(waypoint)
}
/* Private */
Group.prototype.clearTriggerQueues = function() {
this.triggerQueues = {
up: [],
down: [],
left: [],
right: []
}
}
/* Private */
Group.prototype.flushTriggers = function() {
for (var direction in this.triggerQueues) {
var waypoints = this.triggerQueues[direction]
var reverse = direction === 'up' || direction === 'left'
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
for (var i = 0, end = waypoints.length; i < end; i += 1) {
var waypoint = waypoints[i]
if (waypoint.options.continuous || i === waypoints.length - 1) {
waypoint.trigger([direction])
}
}
}
this.clearTriggerQueues()
}
/* Private */
Group.prototype.next = function(waypoint) {
this.waypoints.sort(byTriggerPoint)
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
var isLast = index === this.waypoints.length - 1
return isLast ? null : this.waypoints[index + 1]
}
/* Private */
Group.prototype.previous = function(waypoint) {
this.waypoints.sort(byTriggerPoint)
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
return index ? this.waypoints[index - 1] : null
}
/* Private */
Group.prototype.queueTrigger = function(waypoint, direction) {
this.triggerQueues[direction].push(waypoint)
}
/* Private */
Group.prototype.remove = function(waypoint) {
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
if (index > -1) {
this.waypoints.splice(index, 1)
}
}
/* Public */
/* http://imakewebthings.com/waypoints/api/first */
Group.prototype.first = function() {
return this.waypoints[0]
}
/* Public */
/* http://imakewebthings.com/waypoints/api/last */
Group.prototype.last = function() {
return this.waypoints[this.waypoints.length - 1]
}
/* Private */
Group.findOrCreate = function(options) {
return groups[options.axis][options.name] || new Group(options)
}
Waypoint.Group = Group
}())
;(function() {
'use strict'
var $ = window.jQuery
var Waypoint = window.Waypoint
function JQueryAdapter(element) {
this.$element = $(element)
}
$.each([
'innerHeight',
'innerWidth',
'off',
'offset',
'on',
'outerHeight',
'outerWidth',
'scrollLeft',
'scrollTop'
], function(i, method) {
JQueryAdapter.prototype[method] = function() {
var args = Array.prototype.slice.call(arguments)
return this.$element[method].apply(this.$element, args)
}
})
$.each([
'extend',
'inArray',
'isEmptyObject'
], function(i, method) {
JQueryAdapter[method] = $[method]
})
Waypoint.adapters.push({
name: 'jquery',
Adapter: JQueryAdapter
})
Waypoint.Adapter = JQueryAdapter
}())
;(function() {
'use strict'
var Waypoint = window.Waypoint
function createExtension(framework) {
return function() {
var waypoints = []
var overrides = arguments[0]
if (typeof arguments[0] === "function") {
overrides = framework.extend({}, arguments[1])
overrides.handler = arguments[0]
}
this.each(function() {
var options = framework.extend({}, overrides, {
element: this
})
if (typeof options.context === 'string') {
options.context = framework(this).closest(options.context)[0]
}
waypoints.push(new Waypoint(options))
})
return waypoints
}
}
if (window.jQuery) {
window.jQuery.fn.waypoint = createExtension(window.jQuery)
}
if (window.Zepto) {
window.Zepto.fn.waypoint = createExtension(window.Zepto)
}
}())
;