2204 lines
59 KiB
JavaScript
Executable File
2204 lines
59 KiB
JavaScript
Executable File
|
|
;(function ($, w, undefined) {
|
|
'use strict';
|
|
|
|
var pluginName = 'sly';
|
|
var className = 'Sly';
|
|
var namespace = pluginName;
|
|
|
|
// Local WindowAnimationTiming interface
|
|
var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame;
|
|
var rAF = w.requestAnimationFrame;
|
|
|
|
// Support indicators
|
|
var transform, gpuAcceleration;
|
|
|
|
// Other global values
|
|
var $doc = $(document);
|
|
var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace;
|
|
var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
|
|
var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace;
|
|
var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace;
|
|
var clickEvent = 'click.' + namespace;
|
|
var mouseDownEvent = 'mousedown.' + namespace;
|
|
var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'];
|
|
var tmpArray = [];
|
|
var time;
|
|
|
|
// Math shorthands
|
|
var abs = Math.abs;
|
|
var sqrt = Math.sqrt;
|
|
var pow = Math.pow;
|
|
var round = Math.round;
|
|
var max = Math.max;
|
|
var min = Math.min;
|
|
|
|
// Keep track of last fired global wheel event
|
|
var lastGlobalWheel = 0;
|
|
$doc.on(wheelEvent, function (event) {
|
|
var sly = event.originalEvent[namespace];
|
|
var time = +new Date();
|
|
// Update last global wheel time, but only when event didn't originate
|
|
// in Sly frame, or the origin was less than scrollHijack time ago
|
|
if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time;
|
|
});
|
|
|
|
/**
|
|
* Sly.
|
|
*
|
|
* @class
|
|
*
|
|
* @param {Element} frame DOM element of sly container.
|
|
* @param {Object} options Object with options.
|
|
* @param {Object} callbackMap Callbacks map.
|
|
*/
|
|
function Sly(frame, options, callbackMap) {
|
|
if (!(this instanceof Sly)) return new Sly(frame, options, callbackMap);
|
|
|
|
// Extend options
|
|
var o = $.extend({}, Sly.defaults, options);
|
|
|
|
// Private variables
|
|
var self = this;
|
|
var parallax = isNumber(frame);
|
|
|
|
// Frame
|
|
var $frame = $(frame);
|
|
var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0);
|
|
var frameSize = 0;
|
|
var slideeSize = 0;
|
|
var pos = {
|
|
start: 0,
|
|
center: 0,
|
|
end: 0,
|
|
cur: 0,
|
|
dest: 0
|
|
};
|
|
|
|
// Scrollbar
|
|
var $sb = $(o.scrollBar).eq(0);
|
|
var $handle = $sb.children().eq(0);
|
|
var sbSize = 0;
|
|
var handleSize = 0;
|
|
var hPos = {
|
|
start: 0,
|
|
end: 0,
|
|
cur: 0
|
|
};
|
|
|
|
// Pagesbar
|
|
var $pb = $(o.pagesBar);
|
|
var $pages = 0;
|
|
var pages = [];
|
|
|
|
// Items
|
|
var $items = 0;
|
|
var items = [];
|
|
var rel = {
|
|
firstItem: 0,
|
|
lastItem: 0,
|
|
centerItem: 0,
|
|
activeItem: null,
|
|
activePage: 0
|
|
};
|
|
|
|
// Styles
|
|
var frameStyles = new StyleRestorer($frame[0]);
|
|
var slideeStyles = new StyleRestorer($slidee[0]);
|
|
var sbStyles = new StyleRestorer($sb[0]);
|
|
var handleStyles = new StyleRestorer($handle[0]);
|
|
|
|
// Navigation type booleans
|
|
var basicNav = o.itemNav === 'basic';
|
|
var forceCenteredNav = o.itemNav === 'forceCentered';
|
|
var centeredNav = o.itemNav === 'centered' || forceCenteredNav;
|
|
var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav);
|
|
|
|
// Miscellaneous
|
|
var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame;
|
|
var $dragSource = o.dragSource ? $(o.dragSource) : $frame;
|
|
var $forwardButton = $(o.forward);
|
|
var $backwardButton = $(o.backward);
|
|
var $prevButton = $(o.prev);
|
|
var $nextButton = $(o.next);
|
|
var $prevPageButton = $(o.prevPage);
|
|
var $nextPageButton = $(o.nextPage);
|
|
var callbacks = {};
|
|
var last = {};
|
|
var animation = {};
|
|
var move = {};
|
|
var dragging = {
|
|
released: 1
|
|
};
|
|
var scrolling = {
|
|
last: 0,
|
|
delta: 0,
|
|
resetTime: 200
|
|
};
|
|
var renderID = 0;
|
|
var historyID = 0;
|
|
var cycleID = 0;
|
|
var continuousID = 0;
|
|
var i, l;
|
|
|
|
// Normalizing frame
|
|
if (!parallax) {
|
|
frame = $frame[0];
|
|
}
|
|
|
|
// Expose properties
|
|
self.initialized = 0;
|
|
self.frame = frame;
|
|
self.slidee = $slidee[0];
|
|
self.pos = pos;
|
|
self.rel = rel;
|
|
self.items = items;
|
|
self.pages = pages;
|
|
self.isPaused = 0;
|
|
self.options = o;
|
|
self.dragging = dragging;
|
|
|
|
/**
|
|
* Loading function.
|
|
*
|
|
* Populate arrays, set sizes, bind events, ...
|
|
*
|
|
* @param {Boolean} [isInit] Whether load is called from within self.init().
|
|
* @return {Void}
|
|
*/
|
|
function load(isInit) {
|
|
// Local variables
|
|
var lastItemsCount = 0;
|
|
var lastPagesCount = pages.length;
|
|
|
|
// Save old position
|
|
pos.old = $.extend({}, pos);
|
|
|
|
// Reset global variables
|
|
frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height']();
|
|
sbSize = $sb[o.horizontal ? 'width' : 'height']();
|
|
slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight']();
|
|
pages.length = 0;
|
|
|
|
// Set position limits & relatives
|
|
pos.start = 0;
|
|
pos.end = max(slideeSize - frameSize, 0);
|
|
|
|
// Sizes & offsets for item based navigations
|
|
if (itemNav) {
|
|
// Save the number of current items
|
|
lastItemsCount = items.length;
|
|
|
|
// Reset itemNav related variables
|
|
$items = $slidee.children(o.itemSelector);
|
|
items.length = 0;
|
|
|
|
// Needed variables
|
|
var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop');
|
|
var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom');
|
|
var borderBox = $($items).css('boxSizing') === 'border-box';
|
|
var areFloated = $items.css('float') !== 'none';
|
|
var ignoredMargin = 0;
|
|
var lastItemIndex = $items.length - 1;
|
|
var lastItem;
|
|
|
|
// Reset slideeSize
|
|
slideeSize = 0;
|
|
|
|
// Iterate through items
|
|
$items.each(function (i, element) {
|
|
// Item
|
|
var $item = $(element);
|
|
var rect = element.getBoundingClientRect();
|
|
var itemSize = round(o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top);
|
|
var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop');
|
|
var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom');
|
|
var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd;
|
|
var singleSpaced = !itemMarginStart || !itemMarginEnd;
|
|
var item = {};
|
|
item.el = element;
|
|
item.size = singleSpaced ? itemSize : itemSizeFull;
|
|
item.half = item.size / 2;
|
|
item.start = slideeSize + (singleSpaced ? itemMarginStart : 0);
|
|
item.center = item.start - round(frameSize / 2 - item.size / 2);
|
|
item.end = item.start - frameSize + item.size;
|
|
|
|
// Account for slidee padding
|
|
if (!i) {
|
|
slideeSize += paddingStart;
|
|
}
|
|
|
|
// Increment slidee size for size of the active element
|
|
slideeSize += itemSizeFull;
|
|
|
|
// Try to account for vertical margin collapsing in vertical mode
|
|
// It's not bulletproof, but should work in 99% of cases
|
|
if (!o.horizontal && !areFloated) {
|
|
// Subtract smaller margin, but only when top margin is not 0, and this is not the first element
|
|
if (itemMarginEnd && itemMarginStart && i > 0) {
|
|
slideeSize -= min(itemMarginStart, itemMarginEnd);
|
|
}
|
|
}
|
|
|
|
// Things to be done on last item
|
|
if (i === lastItemIndex) {
|
|
item.end += paddingEnd;
|
|
slideeSize += paddingEnd;
|
|
ignoredMargin = singleSpaced ? itemMarginEnd : 0;
|
|
}
|
|
|
|
// Add item object to items array
|
|
items.push(item);
|
|
lastItem = item;
|
|
});
|
|
|
|
// Resize SLIDEE to fit all items
|
|
$slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px';
|
|
|
|
// Adjust internal SLIDEE size for last margin
|
|
slideeSize -= ignoredMargin;
|
|
|
|
// Set limits
|
|
if (items.length) {
|
|
pos.start = items[0][forceCenteredNav ? 'center' : 'start'];
|
|
pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start;
|
|
} else {
|
|
pos.start = pos.end = 0;
|
|
}
|
|
}
|
|
|
|
// Calculate SLIDEE center position
|
|
pos.center = round(pos.end / 2 + pos.start / 2);
|
|
|
|
// Update relative positions
|
|
updateRelatives();
|
|
|
|
// Scrollbar
|
|
if ($handle.length && sbSize > 0) {
|
|
// Stretch scrollbar handle to represent the visible area
|
|
if (o.dynamicHandle) {
|
|
handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize);
|
|
handleSize = within(handleSize, o.minHandleSize, sbSize);
|
|
$handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px';
|
|
} else {
|
|
handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight']();
|
|
}
|
|
|
|
hPos.end = sbSize - handleSize;
|
|
|
|
if (!renderID) {
|
|
syncScrollbar();
|
|
}
|
|
}
|
|
|
|
// Pages
|
|
if (!parallax && frameSize > 0) {
|
|
var tempPagePos = pos.start;
|
|
var pagesHtml = '';
|
|
|
|
// Populate pages array
|
|
if (itemNav) {
|
|
$.each(items, function (i, item) {
|
|
if (forceCenteredNav) {
|
|
pages.push(item.center);
|
|
} else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) {
|
|
tempPagePos = item.start;
|
|
pages.push(tempPagePos);
|
|
tempPagePos += frameSize;
|
|
if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) {
|
|
pages.push(pos.end);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
while (tempPagePos - frameSize < pos.end) {
|
|
pages.push(tempPagePos);
|
|
tempPagePos += frameSize;
|
|
}
|
|
}
|
|
|
|
// Pages bar
|
|
if ($pb[0] && lastPagesCount !== pages.length) {
|
|
for (var i = 0; i < pages.length; i++) {
|
|
pagesHtml += o.pageBuilder.call(self, i);
|
|
}
|
|
$pages = $pb.html(pagesHtml).children();
|
|
$pages.eq(rel.activePage).addClass(o.activeClass);
|
|
}
|
|
}
|
|
|
|
// Extend relative variables object with some useful info
|
|
rel.slideeSize = slideeSize;
|
|
rel.frameSize = frameSize;
|
|
rel.sbSize = sbSize;
|
|
rel.handleSize = handleSize;
|
|
|
|
// Activate requested position
|
|
if (itemNav) {
|
|
if (isInit && o.startAt != null) {
|
|
activate(o.startAt);
|
|
self[centeredNav ? 'toCenter' : 'toStart'](o.startAt);
|
|
}
|
|
// Fix possible overflowing
|
|
var activeItem = items[rel.activeItem];
|
|
slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end));
|
|
} else {
|
|
if (isInit) {
|
|
if (o.startAt != null) slideTo(o.startAt, 1);
|
|
} else {
|
|
// Fix possible overflowing
|
|
slideTo(within(pos.dest, pos.start, pos.end));
|
|
}
|
|
}
|
|
|
|
// Trigger load event
|
|
trigger('load');
|
|
}
|
|
self.reload = function () { load(); };
|
|
|
|
/**
|
|
* Animate to a position.
|
|
*
|
|
* @param {Int} newPos New position.
|
|
* @param {Bool} immediate Reposition immediately without an animation.
|
|
* @param {Bool} dontAlign Do not align items, use the raw position passed in first argument.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function slideTo(newPos, immediate, dontAlign) {
|
|
// Align items
|
|
if (itemNav && dragging.released && !dontAlign) {
|
|
var tempRel = getRelatives(newPos);
|
|
var isNotBordering = newPos > pos.start && newPos < pos.end;
|
|
|
|
if (centeredNav) {
|
|
if (isNotBordering) {
|
|
newPos = items[tempRel.centerItem].center;
|
|
}
|
|
if (forceCenteredNav && o.activateMiddle) {
|
|
activate(tempRel.centerItem);
|
|
}
|
|
} else if (isNotBordering) {
|
|
newPos = items[tempRel.firstItem].start;
|
|
}
|
|
}
|
|
|
|
// Handle overflowing position limits
|
|
if (dragging.init && dragging.slidee && o.elasticBounds) {
|
|
if (newPos > pos.end) {
|
|
newPos = pos.end + (newPos - pos.end) / 6;
|
|
} else if (newPos < pos.start) {
|
|
newPos = pos.start + (newPos - pos.start) / 6;
|
|
}
|
|
} else {
|
|
newPos = within(newPos, pos.start, pos.end);
|
|
}
|
|
|
|
// Update the animation object
|
|
animation.start = +new Date();
|
|
animation.time = 0;
|
|
animation.from = pos.cur;
|
|
animation.to = newPos;
|
|
animation.delta = newPos - pos.cur;
|
|
animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee;
|
|
animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed);
|
|
|
|
// Reset dragging tweesing request
|
|
dragging.tweese = 0;
|
|
|
|
// Start animation rendering
|
|
if (newPos !== pos.dest) {
|
|
pos.dest = newPos;
|
|
trigger('change');
|
|
if (!renderID) {
|
|
render();
|
|
}
|
|
}
|
|
|
|
// Reset next cycle timeout
|
|
resetCycle();
|
|
|
|
// Synchronize states
|
|
updateRelatives();
|
|
updateButtonsState();
|
|
syncPagesbar();
|
|
}
|
|
|
|
/**
|
|
* Render animation frame.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function render() {
|
|
if (!self.initialized) {
|
|
return;
|
|
}
|
|
|
|
// If first render call, wait for next animationFrame
|
|
if (!renderID) {
|
|
renderID = rAF(render);
|
|
if (dragging.released) {
|
|
trigger('moveStart');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If immediate repositioning is requested, don't animate.
|
|
if (animation.immediate) {
|
|
pos.cur = animation.to;
|
|
}
|
|
// Use tweesing for animations without known end point
|
|
else if (animation.tweesing) {
|
|
animation.tweeseDelta = animation.to - pos.cur;
|
|
// Fuck Zeno's paradox
|
|
if (abs(animation.tweeseDelta) < 0.1) {
|
|
pos.cur = animation.to;
|
|
} else {
|
|
pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed);
|
|
}
|
|
}
|
|
// Use tweening for basic animations with known end point
|
|
else {
|
|
animation.time = min(+new Date() - animation.start, o.speed);
|
|
pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed);
|
|
}
|
|
|
|
// If there is nothing more to render break the rendering loop, otherwise request new animation frame.
|
|
if (animation.to === pos.cur) {
|
|
pos.cur = animation.to;
|
|
dragging.tweese = renderID = 0;
|
|
} else {
|
|
renderID = rAF(render);
|
|
}
|
|
|
|
trigger('move');
|
|
|
|
// Update SLIDEE position
|
|
if (!parallax) {
|
|
if (transform) {
|
|
$slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)';
|
|
} else {
|
|
$slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px';
|
|
}
|
|
}
|
|
|
|
// When animation reached the end, and dragging is not active, trigger moveEnd
|
|
if (!renderID && dragging.released) {
|
|
trigger('moveEnd');
|
|
}
|
|
|
|
syncScrollbar();
|
|
}
|
|
|
|
/**
|
|
* Synchronizes scrollbar with the SLIDEE.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function syncScrollbar() {
|
|
if ($handle.length) {
|
|
hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end;
|
|
hPos.cur = within(round(hPos.cur), hPos.start, hPos.end);
|
|
if (last.hPos !== hPos.cur) {
|
|
last.hPos = hPos.cur;
|
|
if (transform) {
|
|
$handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)';
|
|
} else {
|
|
$handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Synchronizes pagesbar with SLIDEE.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function syncPagesbar() {
|
|
if ($pages[0] && last.page !== rel.activePage) {
|
|
last.page = rel.activePage;
|
|
$pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
|
|
trigger('activePage', last.page);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the position object.
|
|
*
|
|
* @param {Mixed} item
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
self.getPos = function (item) {
|
|
if (itemNav) {
|
|
var index = getIndex(item);
|
|
return index !== -1 ? items[index] : false;
|
|
} else {
|
|
var $item = $slidee.find(item).eq(0);
|
|
|
|
if ($item[0]) {
|
|
var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top;
|
|
var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight']();
|
|
|
|
return {
|
|
start: offset,
|
|
center: offset - frameSize / 2 + size / 2,
|
|
end: offset - frameSize + size,
|
|
size: size
|
|
};
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Continuous move in a specified direction.
|
|
*
|
|
* @param {Bool} forward True for forward movement, otherwise it'll go backwards.
|
|
* @param {Int} speed Movement speed in pixels per frame. Overrides options.moveBy value.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.moveBy = function (speed) {
|
|
move.speed = speed;
|
|
// If already initiated, or there is nowhere to move, abort
|
|
if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
|
|
return;
|
|
}
|
|
// Initiate move object
|
|
move.lastTime = +new Date();
|
|
move.startPos = pos.cur;
|
|
// Set dragging as initiated
|
|
continuousInit('button');
|
|
dragging.init = 1;
|
|
// Start movement
|
|
trigger('moveStart');
|
|
cAF(continuousID);
|
|
moveLoop();
|
|
};
|
|
|
|
/**
|
|
* Continuous movement loop.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function moveLoop() {
|
|
// If there is nowhere to move anymore, stop
|
|
if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
|
|
self.stop();
|
|
}
|
|
// Request new move loop if it hasn't been stopped
|
|
continuousID = dragging.init ? rAF(moveLoop) : 0;
|
|
// Update move object
|
|
move.now = +new Date();
|
|
move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed;
|
|
// Slide
|
|
slideTo(dragging.init ? move.pos : round(move.pos));
|
|
// Normally, this is triggered in render(), but if there
|
|
// is nothing to render, we have to do it manually here.
|
|
if (!dragging.init && pos.cur === pos.dest) {
|
|
trigger('moveEnd');
|
|
}
|
|
// Update times for future iteration
|
|
move.lastTime = move.now;
|
|
}
|
|
|
|
/**
|
|
* Stops continuous movement.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.stop = function () {
|
|
if (dragging.source === 'button') {
|
|
dragging.init = 0;
|
|
dragging.released = 1;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Activate previous item.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.prev = function () {
|
|
self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1);
|
|
};
|
|
|
|
/**
|
|
* Activate next item.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.next = function () {
|
|
self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1);
|
|
};
|
|
|
|
/**
|
|
* Activate previous page.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.prevPage = function () {
|
|
self.activatePage(rel.activePage - 1);
|
|
};
|
|
|
|
/**
|
|
* Activate next page.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.nextPage = function () {
|
|
self.activatePage(rel.activePage + 1);
|
|
};
|
|
|
|
/**
|
|
* Slide SLIDEE by amount of pixels.
|
|
*
|
|
* @param {Int} delta Pixels/Items. Positive means forward, negative means backward.
|
|
* @param {Bool} immediate Reposition immediately without an animation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.slideBy = function (delta, immediate) {
|
|
if (!delta) {
|
|
return;
|
|
}
|
|
if (itemNav) {
|
|
self[centeredNav ? 'toCenter' : 'toStart'](
|
|
within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length)
|
|
);
|
|
} else {
|
|
slideTo(pos.dest + delta, immediate);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Animate SLIDEE to a specific position.
|
|
*
|
|
* @param {Int} pos New position.
|
|
* @param {Bool} immediate Reposition immediately without an animation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.slideTo = function (pos, immediate) {
|
|
slideTo(pos, immediate);
|
|
};
|
|
|
|
/**
|
|
* Core method for handling `toLocation` methods.
|
|
*
|
|
* @param {String} location
|
|
* @param {Mixed} item
|
|
* @param {Bool} immediate
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function to(location, item, immediate) {
|
|
// Optional arguments logic
|
|
if (type(item) === 'boolean') {
|
|
immediate = item;
|
|
item = undefined;
|
|
}
|
|
|
|
if (item === undefined) {
|
|
slideTo(pos[location], immediate);
|
|
} else {
|
|
// You can't align items to sides of the frame
|
|
// when centered navigation type is enabled
|
|
if (centeredNav && location !== 'center') {
|
|
return;
|
|
}
|
|
|
|
var itemPos = self.getPos(item);
|
|
if (itemPos) {
|
|
slideTo(itemPos[location], immediate, !centeredNav);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Animate element or the whole SLIDEE to the start of the frame.
|
|
*
|
|
* @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
|
|
* @param {Bool} immediate Reposition immediately without an animation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.toStart = function (item, immediate) {
|
|
to('start', item, immediate);
|
|
};
|
|
|
|
/**
|
|
* Animate element or the whole SLIDEE to the end of the frame.
|
|
*
|
|
* @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
|
|
* @param {Bool} immediate Reposition immediately without an animation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.toEnd = function (item, immediate) {
|
|
to('end', item, immediate);
|
|
};
|
|
|
|
/**
|
|
* Animate element or the whole SLIDEE to the center of the frame.
|
|
*
|
|
* @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
|
|
* @param {Bool} immediate Reposition immediately without an animation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.toCenter = function (item, immediate) {
|
|
to('center', item, immediate);
|
|
};
|
|
|
|
/**
|
|
* Get the index of an item in SLIDEE.
|
|
*
|
|
* @param {Mixed} item Item DOM element.
|
|
*
|
|
* @return {Int} Item index, or -1 if not found.
|
|
*/
|
|
function getIndex(item) {
|
|
return item != null ?
|
|
isNumber(item) ?
|
|
item >= 0 && item < items.length ? item : -1 :
|
|
$items.index(item) :
|
|
-1;
|
|
}
|
|
// Expose getIndex without lowering the compressibility of it,
|
|
// as it is used quite often throughout Sly.
|
|
self.getIndex = getIndex;
|
|
|
|
/**
|
|
* Get index of an item in SLIDEE based on a variety of input types.
|
|
*
|
|
* @param {Mixed} item DOM element, positive or negative integer.
|
|
*
|
|
* @return {Int} Item index, or -1 if not found.
|
|
*/
|
|
function getRelativeIndex(item) {
|
|
return getIndex(isNumber(item) && item < 0 ? item + items.length : item);
|
|
}
|
|
|
|
/**
|
|
* Activates an item.
|
|
*
|
|
* @param {Mixed} item Item DOM element, or index starting at 0.
|
|
*
|
|
* @return {Mixed} Activated item index or false on fail.
|
|
*/
|
|
function activate(item, force) {
|
|
var index = getIndex(item);
|
|
|
|
if (!itemNav || index < 0) {
|
|
return false;
|
|
}
|
|
|
|
// Update classes, last active index, and trigger active event only when there
|
|
// has been a change. Otherwise just return the current active index.
|
|
if (last.active !== index || force) {
|
|
// Update classes
|
|
$items.eq(rel.activeItem).removeClass(o.activeClass);
|
|
$items.eq(index).addClass(o.activeClass);
|
|
|
|
last.active = rel.activeItem = index;
|
|
|
|
updateButtonsState();
|
|
trigger('active', index);
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Activates an item and helps with further navigation when o.smart is enabled.
|
|
*
|
|
* @param {Mixed} item Item DOM element, or index starting at 0.
|
|
* @param {Bool} immediate Whether to reposition immediately in smart navigation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.activate = function (item, immediate) {
|
|
var index = activate(item);
|
|
|
|
// Smart navigation
|
|
if (o.smart && index !== false) {
|
|
// When centeredNav is enabled, center the element.
|
|
// Otherwise, determine where to position the element based on its current position.
|
|
// If the element is currently on the far end side of the frame, assume that user is
|
|
// moving forward and animate it to the start of the visible frame, and vice versa.
|
|
if (centeredNav) {
|
|
self.toCenter(index, immediate);
|
|
} else if (index >= rel.lastItem) {
|
|
self.toStart(index, immediate);
|
|
} else if (index <= rel.firstItem) {
|
|
self.toEnd(index, immediate);
|
|
} else {
|
|
resetCycle();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Activates a page.
|
|
*
|
|
* @param {Int} index Page index, starting from 0.
|
|
* @param {Bool} immediate Whether to reposition immediately without animation.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.activatePage = function (index, immediate) {
|
|
if (isNumber(index)) {
|
|
slideTo(pages[within(index, 0, pages.length - 1)], immediate);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return relative positions of items based on their visibility within FRAME.
|
|
*
|
|
* @param {Int} slideePos Position of SLIDEE.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function getRelatives(slideePos) {
|
|
slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end);
|
|
|
|
var relatives = {};
|
|
var centerOffset = forceCenteredNav ? 0 : frameSize / 2;
|
|
|
|
// Determine active page
|
|
if (!parallax) {
|
|
for (var p = 0, pl = pages.length; p < pl; p++) {
|
|
if (slideePos >= pos.end || p === pages.length - 1) {
|
|
relatives.activePage = pages.length - 1;
|
|
break;
|
|
}
|
|
|
|
if (slideePos <= pages[p] + centerOffset) {
|
|
relatives.activePage = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relative item indexes
|
|
if (itemNav) {
|
|
var first = false;
|
|
var last = false;
|
|
var center = false;
|
|
|
|
// From start
|
|
for (var i = 0, il = items.length; i < il; i++) {
|
|
// First item
|
|
if (first === false && slideePos <= items[i].start + items[i].half) {
|
|
first = i;
|
|
}
|
|
|
|
// Center item
|
|
if (center === false && slideePos <= items[i].center + items[i].half) {
|
|
center = i;
|
|
}
|
|
|
|
// Last item
|
|
if (i === il - 1 || slideePos <= items[i].end + items[i].half) {
|
|
last = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Safe assignment, just to be sure the false won't be returned
|
|
relatives.firstItem = isNumber(first) ? first : 0;
|
|
relatives.centerItem = isNumber(center) ? center : relatives.firstItem;
|
|
relatives.lastItem = isNumber(last) ? last : relatives.centerItem;
|
|
}
|
|
|
|
return relatives;
|
|
}
|
|
|
|
/**
|
|
* Update object with relative positions.
|
|
*
|
|
* @param {Int} newPos
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function updateRelatives(newPos) {
|
|
$.extend(rel, getRelatives(newPos));
|
|
}
|
|
|
|
/**
|
|
* Disable navigation buttons when needed.
|
|
*
|
|
* Adds disabledClass, and when the button is <button> or <input>, activates :disabled state.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function updateButtonsState() {
|
|
var isStart = pos.dest <= pos.start;
|
|
var isEnd = pos.dest >= pos.end;
|
|
var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0);
|
|
|
|
// Update paging buttons only if there has been a change in SLIDEE position
|
|
if (last.slideePosState !== slideePosState) {
|
|
last.slideePosState = slideePosState;
|
|
|
|
if ($prevPageButton.is('button,input')) {
|
|
$prevPageButton.prop('disabled', isStart);
|
|
}
|
|
|
|
if ($nextPageButton.is('button,input')) {
|
|
$nextPageButton.prop('disabled', isEnd);
|
|
}
|
|
|
|
$prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass);
|
|
$nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass);
|
|
}
|
|
|
|
// Forward & Backward buttons need a separate state caching because we cannot "property disable"
|
|
// them while they are being used, as disabled buttons stop emitting mouse events.
|
|
if (last.fwdbwdState !== slideePosState && dragging.released) {
|
|
last.fwdbwdState = slideePosState;
|
|
|
|
if ($backwardButton.is('button,input')) {
|
|
$backwardButton.prop('disabled', isStart);
|
|
}
|
|
|
|
if ($forwardButton.is('button,input')) {
|
|
$forwardButton.prop('disabled', isEnd);
|
|
}
|
|
}
|
|
|
|
// Item navigation
|
|
if (itemNav && rel.activeItem != null) {
|
|
var isFirst = rel.activeItem === 0;
|
|
var isLast = rel.activeItem >= items.length - 1;
|
|
var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0);
|
|
|
|
if (last.itemsButtonState !== itemsButtonState) {
|
|
last.itemsButtonState = itemsButtonState;
|
|
|
|
if ($prevButton.is('button,input')) {
|
|
$prevButton.prop('disabled', isFirst);
|
|
}
|
|
|
|
if ($nextButton.is('button,input')) {
|
|
$nextButton.prop('disabled', isLast);
|
|
}
|
|
|
|
$prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass);
|
|
$nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resume cycling.
|
|
*
|
|
* @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.resume = function (priority) {
|
|
if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) {
|
|
return;
|
|
}
|
|
|
|
self.isPaused = 0;
|
|
|
|
if (cycleID) {
|
|
cycleID = clearTimeout(cycleID);
|
|
} else {
|
|
trigger('resume');
|
|
}
|
|
|
|
cycleID = setTimeout(function () {
|
|
trigger('cycle');
|
|
switch (o.cycleBy) {
|
|
case 'items':
|
|
self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1);
|
|
break;
|
|
|
|
case 'pages':
|
|
self.activatePage(rel.activePage >= pages.length - 1 ? 0 : rel.activePage + 1);
|
|
break;
|
|
}
|
|
}, o.cycleInterval);
|
|
};
|
|
|
|
/**
|
|
* Pause cycling.
|
|
*
|
|
* @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.pause = function (priority) {
|
|
if (priority < self.isPaused) {
|
|
return;
|
|
}
|
|
|
|
self.isPaused = priority || 100;
|
|
|
|
if (cycleID) {
|
|
cycleID = clearTimeout(cycleID);
|
|
trigger('pause');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Toggle cycling.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.toggle = function () {
|
|
self[cycleID ? 'pause' : 'resume']();
|
|
};
|
|
|
|
/**
|
|
* Updates a signle or multiple option values.
|
|
*
|
|
* @param {Mixed} name Name of the option that should be updated, or object that will extend the options.
|
|
* @param {Mixed} value New option value.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.set = function (name, value) {
|
|
if ($.isPlainObject(name)) {
|
|
$.extend(o, name);
|
|
} else if (o.hasOwnProperty(name)) {
|
|
o[name] = value;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add one or multiple items to the SLIDEE end, or a specified position index.
|
|
*
|
|
* @param {Mixed} element Node element, or HTML string.
|
|
* @param {Int} index Index of a new item position. By default item is appended at the end.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.add = function (element, index) {
|
|
var $element = $(element);
|
|
|
|
if (itemNav) {
|
|
// Insert the element(s)
|
|
if (index == null || !items[0] || index >= items.length) {
|
|
$element.appendTo($slidee);
|
|
} else if (items.length) {
|
|
$element.insertBefore(items[index].el);
|
|
}
|
|
|
|
// Adjust the activeItem index
|
|
if (rel.activeItem != null && index <= rel.activeItem) {
|
|
last.active = rel.activeItem += $element.length;
|
|
}
|
|
} else {
|
|
$slidee.append($element);
|
|
}
|
|
|
|
// Reload
|
|
load();
|
|
};
|
|
|
|
/**
|
|
* Remove an item from SLIDEE.
|
|
*
|
|
* @param {Mixed} element Item index, or DOM element.
|
|
* @param {Int} index Index of a new item position. By default item is appended at the end.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.remove = function (element) {
|
|
if (itemNav) {
|
|
var index = getRelativeIndex(element);
|
|
|
|
if (index > -1) {
|
|
// Remove the element
|
|
$items.eq(index).remove();
|
|
|
|
// If the current item is being removed, activate new one after reload
|
|
var reactivate = index === rel.activeItem;
|
|
|
|
// Adjust the activeItem index
|
|
if (rel.activeItem != null && index < rel.activeItem) {
|
|
last.active = --rel.activeItem;
|
|
}
|
|
|
|
// Reload
|
|
load();
|
|
|
|
// Activate new item at the removed position
|
|
if (reactivate) {
|
|
last.active = null;
|
|
self.activate(rel.activeItem);
|
|
}
|
|
}
|
|
} else {
|
|
$(element).remove();
|
|
load();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Helps re-arranging items.
|
|
*
|
|
* @param {Mixed} item Item DOM element, or index starting at 0. Use negative numbers to select items from the end.
|
|
* @param {Mixed} position Item insertion anchor. Accepts same input types as item argument.
|
|
* @param {Bool} after Insert after instead of before the anchor.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function moveItem(item, position, after) {
|
|
item = getRelativeIndex(item);
|
|
position = getRelativeIndex(position);
|
|
|
|
// Move only if there is an actual change requested
|
|
if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) {
|
|
$items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el);
|
|
|
|
var shiftStart = item < position ? item : (after ? position : position - 1);
|
|
var shiftEnd = item > position ? item : (after ? position + 1 : position);
|
|
var shiftsUp = item > position;
|
|
|
|
// Update activeItem index
|
|
if (rel.activeItem != null) {
|
|
if (item === rel.activeItem) {
|
|
last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1);
|
|
} else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) {
|
|
last.active = rel.activeItem += shiftsUp ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
// Reload
|
|
load();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move item after the target anchor.
|
|
*
|
|
* @param {Mixed} item Item to be moved. Can be DOM element or item index.
|
|
* @param {Mixed} position Target position anchor. Can be DOM element or item index.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.moveAfter = function (item, position) {
|
|
moveItem(item, position, 1);
|
|
};
|
|
|
|
/**
|
|
* Move item before the target anchor.
|
|
*
|
|
* @param {Mixed} item Item to be moved. Can be DOM element or item index.
|
|
* @param {Mixed} position Target position anchor. Can be DOM element or item index.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.moveBefore = function (item, position) {
|
|
moveItem(item, position);
|
|
};
|
|
|
|
/**
|
|
* Registers callbacks.
|
|
*
|
|
* @param {Mixed} name Event name, or callbacks map.
|
|
* @param {Mixed} fn Callback, or an array of callback functions.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.on = function (name, fn) {
|
|
// Callbacks map
|
|
if (type(name) === 'object') {
|
|
for (var key in name) {
|
|
if (name.hasOwnProperty(key)) {
|
|
self.on(key, name[key]);
|
|
}
|
|
}
|
|
// Callback
|
|
} else if (type(fn) === 'function') {
|
|
var names = name.split(' ');
|
|
for (var n = 0, nl = names.length; n < nl; n++) {
|
|
callbacks[names[n]] = callbacks[names[n]] || [];
|
|
if (callbackIndex(names[n], fn) === -1) {
|
|
callbacks[names[n]].push(fn);
|
|
}
|
|
}
|
|
// Callbacks array
|
|
} else if (type(fn) === 'array') {
|
|
for (var f = 0, fl = fn.length; f < fl; f++) {
|
|
self.on(name, fn[f]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Registers callbacks to be executed only once.
|
|
*
|
|
* @param {Mixed} name Event name, or callbacks map.
|
|
* @param {Mixed} fn Callback, or an array of callback functions.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.one = function (name, fn) {
|
|
function proxy() {
|
|
fn.apply(self, arguments);
|
|
self.off(name, proxy);
|
|
}
|
|
self.on(name, proxy);
|
|
};
|
|
|
|
/**
|
|
* Remove one or all callbacks.
|
|
*
|
|
* @param {String} name Event name.
|
|
* @param {Mixed} fn Callback, or an array of callback functions. Omit to remove all callbacks.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.off = function (name, fn) {
|
|
if (fn instanceof Array) {
|
|
for (var f = 0, fl = fn.length; f < fl; f++) {
|
|
self.off(name, fn[f]);
|
|
}
|
|
} else {
|
|
var names = name.split(' ');
|
|
for (var n = 0, nl = names.length; n < nl; n++) {
|
|
callbacks[names[n]] = callbacks[names[n]] || [];
|
|
if (fn == null) {
|
|
callbacks[names[n]].length = 0;
|
|
} else {
|
|
var index = callbackIndex(names[n], fn);
|
|
if (index !== -1) {
|
|
callbacks[names[n]].splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns callback array index.
|
|
*
|
|
* @param {String} name Event name.
|
|
* @param {Function} fn Function
|
|
*
|
|
* @return {Int} Callback array index, or -1 if isn't registered.
|
|
*/
|
|
function callbackIndex(name, fn) {
|
|
for (var i = 0, l = callbacks[name].length; i < l; i++) {
|
|
if (callbacks[name][i] === fn) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Reset next cycle timeout.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function resetCycle() {
|
|
if (dragging.released && !self.isPaused) {
|
|
self.resume();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate SLIDEE representation of handle position.
|
|
*
|
|
* @param {Int} handlePos
|
|
*
|
|
* @return {Int}
|
|
*/
|
|
function handleToSlidee(handlePos) {
|
|
return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start;
|
|
}
|
|
|
|
/**
|
|
* Keeps track of a dragging delta history.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function draggingHistoryTick() {
|
|
// Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way
|
|
// as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't
|
|
// generate garbage. The loop version would create 2 new variables on every tick. Unexaptable!
|
|
dragging.history[0] = dragging.history[1];
|
|
dragging.history[1] = dragging.history[2];
|
|
dragging.history[2] = dragging.history[3];
|
|
dragging.history[3] = dragging.delta;
|
|
}
|
|
|
|
/**
|
|
* Initialize continuous movement.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function continuousInit(source) {
|
|
dragging.released = 0;
|
|
dragging.source = source;
|
|
dragging.slidee = source === 'slidee';
|
|
}
|
|
|
|
/**
|
|
* Dragging initiator.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function dragInit(event) {
|
|
var isTouch = event.type === 'touchstart';
|
|
var source = event.data.source;
|
|
var isSlidee = source === 'slidee';
|
|
|
|
// Ignore when already in progress, or interactive element in non-touch navivagion
|
|
if (dragging.init || !isTouch && isInteractive(event.target)) {
|
|
return;
|
|
}
|
|
|
|
// Handle dragging conditions
|
|
if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) {
|
|
return;
|
|
}
|
|
|
|
// SLIDEE dragging conditions
|
|
if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) {
|
|
return;
|
|
}
|
|
|
|
if (!isTouch) {
|
|
// prevents native image dragging in Firefox
|
|
stopDefault(event);
|
|
}
|
|
|
|
// Reset dragging object
|
|
continuousInit(source);
|
|
|
|
// Properties used in dragHandler
|
|
dragging.init = 0;
|
|
dragging.$source = $(event.target);
|
|
dragging.touch = isTouch;
|
|
dragging.pointer = isTouch ? event.originalEvent.touches[0] : event;
|
|
dragging.initX = dragging.pointer.pageX;
|
|
dragging.initY = dragging.pointer.pageY;
|
|
dragging.initPos = isSlidee ? pos.cur : hPos.cur;
|
|
dragging.start = +new Date();
|
|
dragging.time = 0;
|
|
dragging.path = 0;
|
|
dragging.delta = 0;
|
|
dragging.locked = 0;
|
|
dragging.history = [0, 0, 0, 0];
|
|
dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0;
|
|
|
|
// Bind dragging events
|
|
$doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler);
|
|
|
|
// Pause ongoing cycle
|
|
self.pause(1);
|
|
|
|
// Add dragging class
|
|
(isSlidee ? $slidee : $handle).addClass(o.draggedClass);
|
|
|
|
// Trigger moveStart event
|
|
trigger('moveStart');
|
|
|
|
// Keep track of a dragging path history. This is later used in the
|
|
// dragging release swing calculation when dragging SLIDEE.
|
|
if (isSlidee) {
|
|
historyID = setInterval(draggingHistoryTick, 10);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for dragging scrollbar handle or SLIDEE.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function dragHandler(event) {
|
|
dragging.released = event.type === 'mouseup' || event.type === 'touchend';
|
|
dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event;
|
|
dragging.pathX = dragging.pointer.pageX - dragging.initX;
|
|
dragging.pathY = dragging.pointer.pageY - dragging.initY;
|
|
dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2));
|
|
dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY;
|
|
|
|
if (!dragging.released && dragging.path < 1) return;
|
|
|
|
// We haven't decided whether this is a drag or not...
|
|
if (!dragging.init) {
|
|
// If the drag path was very short, maybe it's not a drag?
|
|
if (dragging.path < o.dragThreshold) {
|
|
// If the pointer was released, the path will not become longer and it's
|
|
// definitely not a drag. If not released yet, decide on next iteration
|
|
return dragging.released ? dragEnd() : undefined;
|
|
}
|
|
else {
|
|
// If dragging path is sufficiently long we can confidently start a drag
|
|
// if drag is in different direction than scroll, ignore it
|
|
if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) {
|
|
dragging.init = 1;
|
|
} else {
|
|
return dragEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
stopDefault(event);
|
|
|
|
// Disable click on a source element, as it is unwelcome when dragging
|
|
if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) {
|
|
dragging.locked = 1;
|
|
dragging.$source.on(clickEvent, disableOneEvent);
|
|
}
|
|
|
|
// Cancel dragging on release
|
|
if (dragging.released) {
|
|
dragEnd();
|
|
|
|
// Adjust path with a swing on mouse release
|
|
if (o.releaseSwing && dragging.slidee) {
|
|
dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300;
|
|
dragging.delta += dragging.swing;
|
|
dragging.tweese = abs(dragging.swing) > 10;
|
|
}
|
|
}
|
|
|
|
slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta));
|
|
}
|
|
|
|
/**
|
|
* Stops dragging and cleans up after it.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function dragEnd() {
|
|
clearInterval(historyID);
|
|
dragging.released = true;
|
|
$doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler);
|
|
(dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass);
|
|
|
|
// Make sure that disableOneEvent is not active in next tick.
|
|
setTimeout(function () {
|
|
dragging.$source.off(clickEvent, disableOneEvent);
|
|
});
|
|
|
|
// Normally, this is triggered in render(), but if there
|
|
// is nothing to render, we have to do it manually here.
|
|
if (pos.cur === pos.dest && dragging.init) {
|
|
trigger('moveEnd');
|
|
}
|
|
|
|
// Resume ongoing cycle
|
|
self.resume(1);
|
|
|
|
dragging.init = 0;
|
|
}
|
|
|
|
/**
|
|
* Check whether element is interactive.
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
function isInteractive(element) {
|
|
return ~$.inArray(element.nodeName, interactiveElements) || $(element).is(o.interactive);
|
|
}
|
|
|
|
/**
|
|
* Continuous movement cleanup on mouseup.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function movementReleaseHandler() {
|
|
self.stop();
|
|
$doc.off('mouseup', movementReleaseHandler);
|
|
}
|
|
|
|
/**
|
|
* Buttons navigation handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function buttonsHandler(event) {
|
|
/*jshint validthis:true */
|
|
stopDefault(event);
|
|
switch (this) {
|
|
case $forwardButton[0]:
|
|
case $backwardButton[0]:
|
|
self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy);
|
|
$doc.on('mouseup', movementReleaseHandler);
|
|
break;
|
|
|
|
case $prevButton[0]:
|
|
self.prev();
|
|
break;
|
|
|
|
case $nextButton[0]:
|
|
self.next();
|
|
break;
|
|
|
|
case $prevPageButton[0]:
|
|
self.prevPage();
|
|
break;
|
|
|
|
case $nextPageButton[0]:
|
|
self.nextPage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mouse wheel delta normalization.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Int}
|
|
*/
|
|
function normalizeWheelDelta(event) {
|
|
// wheelDelta needed only for IE8-
|
|
scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta);
|
|
scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100;
|
|
if (!itemNav) {
|
|
return scrolling.curDelta;
|
|
}
|
|
time = +new Date();
|
|
if (scrolling.last < time - scrolling.resetTime) {
|
|
scrolling.delta = 0;
|
|
}
|
|
scrolling.last = time;
|
|
scrolling.delta += scrolling.curDelta;
|
|
if (abs(scrolling.delta) < 1) {
|
|
scrolling.finalDelta = 0;
|
|
} else {
|
|
scrolling.finalDelta = round(scrolling.delta / 1);
|
|
scrolling.delta %= 1;
|
|
}
|
|
return scrolling.finalDelta;
|
|
}
|
|
|
|
/**
|
|
* Mouse scrolling handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function scrollHandler(event) {
|
|
// Mark event as originating in a Sly instance
|
|
event.originalEvent[namespace] = self;
|
|
// Don't hijack global scrolling
|
|
var time = +new Date();
|
|
if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) {
|
|
lastGlobalWheel = time;
|
|
return;
|
|
}
|
|
// Ignore if there is no scrolling to be done
|
|
if (!o.scrollBy || pos.start === pos.end) {
|
|
return;
|
|
}
|
|
var delta = normalizeWheelDelta(event.originalEvent);
|
|
// Trap scrolling only when necessary and/or requested
|
|
if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) {
|
|
stopDefault(event, 1);
|
|
}
|
|
self.slideBy(o.scrollBy * delta);
|
|
}
|
|
|
|
/**
|
|
* Scrollbar click handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function scrollbarHandler(event) {
|
|
// Only clicks on scroll bar. Ignore the handle.
|
|
if (o.clickBar && event.target === $sb[0]) {
|
|
stopDefault(event);
|
|
// Calculate new handle position and sync SLIDEE to it
|
|
slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Keyboard input handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function keyboardHandler(event) {
|
|
if (!o.keyboardNavBy) {
|
|
return;
|
|
}
|
|
|
|
switch (event.which) {
|
|
// Left or Up
|
|
case o.horizontal ? 37 : 38:
|
|
stopDefault(event);
|
|
self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev']();
|
|
break;
|
|
|
|
// Right or Down
|
|
case o.horizontal ? 39 : 40:
|
|
stopDefault(event);
|
|
self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next']();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Click on item activation handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function activateHandler(event) {
|
|
/*jshint validthis:true */
|
|
|
|
// Ignore clicks on interactive elements.
|
|
if (isInteractive(this)) {
|
|
event.originalEvent[namespace + 'ignore'] = true;
|
|
return;
|
|
}
|
|
|
|
// Ignore events that:
|
|
// - are not originating from direct SLIDEE children
|
|
// - originated from interactive elements
|
|
if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return;
|
|
|
|
self.activate(this);
|
|
}
|
|
|
|
/**
|
|
* Click on page button handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function activatePageHandler() {
|
|
/*jshint validthis:true */
|
|
// Accept only events from direct pages bar children.
|
|
if (this.parentNode === $pb[0]) {
|
|
self.activatePage($pages.index(this));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pause on hover handler.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function pauseOnHoverHandler(event) {
|
|
if (o.pauseOnHover) {
|
|
self[event.type === 'mouseenter' ? 'pause' : 'resume'](2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger callbacks for event.
|
|
*
|
|
* @param {String} name Event name.
|
|
* @param {Mixed} argX Arguments passed to callbacks.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function trigger(name, arg1) {
|
|
if (callbacks[name]) {
|
|
l = callbacks[name].length;
|
|
// Callbacks will be stored and executed from a temporary array to not
|
|
// break the execution queue when one of the callbacks unbinds itself.
|
|
tmpArray.length = 0;
|
|
for (i = 0; i < l; i++) {
|
|
tmpArray.push(callbacks[name][i]);
|
|
}
|
|
// Execute the callbacks
|
|
for (i = 0; i < l; i++) {
|
|
tmpArray[i].call(self, name, arg1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroys instance and everything it created.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
self.destroy = function () {
|
|
// Remove the reference to itself
|
|
Sly.removeInstance(frame);
|
|
|
|
// Unbind all events
|
|
$scrollSource
|
|
.add($handle)
|
|
.add($sb)
|
|
.add($pb)
|
|
.add($forwardButton)
|
|
.add($backwardButton)
|
|
.add($prevButton)
|
|
.add($nextButton)
|
|
.add($prevPageButton)
|
|
.add($nextPageButton)
|
|
.off('.' + namespace);
|
|
|
|
// Unbinding specifically as to not nuke out other instances
|
|
$doc.off('keydown', keyboardHandler);
|
|
|
|
// Remove classes
|
|
$prevButton
|
|
.add($nextButton)
|
|
.add($prevPageButton)
|
|
.add($nextPageButton)
|
|
.removeClass(o.disabledClass);
|
|
|
|
if ($items && rel.activeItem != null) {
|
|
$items.eq(rel.activeItem).removeClass(o.activeClass);
|
|
}
|
|
|
|
// Remove page items
|
|
$pb.empty();
|
|
|
|
if (!parallax) {
|
|
// Unbind events from frame
|
|
$frame.off('.' + namespace);
|
|
// Restore original styles
|
|
frameStyles.restore();
|
|
slideeStyles.restore();
|
|
sbStyles.restore();
|
|
handleStyles.restore();
|
|
// Remove the instance from element data storage
|
|
$.removeData(frame, namespace);
|
|
}
|
|
|
|
// Clean up collections
|
|
items.length = pages.length = 0;
|
|
last = {};
|
|
|
|
// Reset initialized status and return the instance
|
|
self.initialized = 0;
|
|
return self;
|
|
};
|
|
|
|
/**
|
|
* Initialize.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
self.init = function () {
|
|
if (self.initialized) {
|
|
return;
|
|
}
|
|
|
|
// Disallow multiple instances on the same element
|
|
if (Sly.getInstance(frame)) throw new Error('There is already a Sly instance on this element');
|
|
|
|
// Store the reference to itself
|
|
Sly.storeInstance(frame, self);
|
|
|
|
// Register callbacks map
|
|
self.on(callbackMap);
|
|
|
|
// Save styles
|
|
var holderProps = ['overflow', 'position'];
|
|
var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height'];
|
|
frameStyles.save.apply(frameStyles, holderProps);
|
|
sbStyles.save.apply(sbStyles, holderProps);
|
|
slideeStyles.save.apply(slideeStyles, movableProps);
|
|
handleStyles.save.apply(handleStyles, movableProps);
|
|
|
|
// Set required styles
|
|
var $movables = $handle;
|
|
if (!parallax) {
|
|
$movables = $movables.add($slidee);
|
|
$frame.css('overflow', 'hidden');
|
|
if (!transform && $frame.css('position') === 'static') {
|
|
$frame.css('position', 'relative');
|
|
}
|
|
}
|
|
if (transform) {
|
|
if (gpuAcceleration) {
|
|
$movables.css(transform, gpuAcceleration);
|
|
}
|
|
} else {
|
|
if ($sb.css('position') === 'static') {
|
|
$sb.css('position', 'relative');
|
|
}
|
|
$movables.css({ position: 'absolute' });
|
|
}
|
|
|
|
// Navigation buttons
|
|
if (o.forward) {
|
|
$forwardButton.on(mouseDownEvent, buttonsHandler);
|
|
}
|
|
if (o.backward) {
|
|
$backwardButton.on(mouseDownEvent, buttonsHandler);
|
|
}
|
|
if (o.prev) {
|
|
$prevButton.on(clickEvent, buttonsHandler);
|
|
}
|
|
if (o.next) {
|
|
$nextButton.on(clickEvent, buttonsHandler);
|
|
}
|
|
if (o.prevPage) {
|
|
$prevPageButton.on(clickEvent, buttonsHandler);
|
|
}
|
|
if (o.nextPage) {
|
|
$nextPageButton.on(clickEvent, buttonsHandler);
|
|
}
|
|
|
|
// Scrolling navigation
|
|
$scrollSource.on(wheelEvent, scrollHandler);
|
|
|
|
// Clicking on scrollbar navigation
|
|
if ($sb[0]) {
|
|
$sb.on(clickEvent, scrollbarHandler);
|
|
}
|
|
|
|
// Click on items navigation
|
|
if (itemNav && o.activateOn) {
|
|
$frame.on(o.activateOn + '.' + namespace, '*', activateHandler);
|
|
}
|
|
|
|
// Pages navigation
|
|
if ($pb[0] && o.activatePageOn) {
|
|
$pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler);
|
|
}
|
|
|
|
// Dragging navigation
|
|
$dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit);
|
|
|
|
// Scrollbar dragging navigation
|
|
if ($handle) {
|
|
$handle.on(dragInitEvents, { source: 'handle' }, dragInit);
|
|
}
|
|
|
|
// Keyboard navigation
|
|
$doc.on('keydown', keyboardHandler);
|
|
|
|
if (!parallax) {
|
|
// Pause on hover
|
|
$frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler);
|
|
// Reset native FRAME element scroll
|
|
$frame.on('scroll.' + namespace, resetScroll);
|
|
}
|
|
|
|
// Mark instance as initialized
|
|
self.initialized = 1;
|
|
|
|
// Load
|
|
load(true);
|
|
|
|
// Initiate automatic cycling
|
|
if (o.cycleBy && !parallax) {
|
|
self[o.startPaused ? 'pause' : 'resume']();
|
|
}
|
|
|
|
// Return instance
|
|
return self;
|
|
};
|
|
}
|
|
|
|
Sly.getInstance = function (element) {
|
|
return $.data(element, namespace);
|
|
};
|
|
|
|
Sly.storeInstance = function (element, sly) {
|
|
return $.data(element, namespace, sly);
|
|
};
|
|
|
|
Sly.removeInstance = function (element) {
|
|
return $.removeData(element, namespace);
|
|
};
|
|
|
|
/**
|
|
* Return type of the value.
|
|
*
|
|
* @param {Mixed} value
|
|
*
|
|
* @return {String}
|
|
*/
|
|
function type(value) {
|
|
if (value == null) {
|
|
return String(value);
|
|
}
|
|
|
|
if (typeof value === 'object' || typeof value === 'function') {
|
|
return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
|
|
}
|
|
|
|
return typeof value;
|
|
}
|
|
|
|
/**
|
|
* Event preventDefault & stopPropagation helper.
|
|
*
|
|
* @param {Event} event Event object.
|
|
* @param {Bool} noBubbles Cancel event bubbling.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function stopDefault(event, noBubbles) {
|
|
event.preventDefault();
|
|
if (noBubbles) {
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disables an event it was triggered on and unbinds itself.
|
|
*
|
|
* @param {Event} event
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function disableOneEvent(event) {
|
|
/*jshint validthis:true */
|
|
stopDefault(event, 1);
|
|
$(this).off(event.type, disableOneEvent);
|
|
}
|
|
|
|
/**
|
|
* Resets native element scroll values to 0.
|
|
*
|
|
* @return {Void}
|
|
*/
|
|
function resetScroll() {
|
|
/*jshint validthis:true */
|
|
this.scrollLeft = 0;
|
|
this.scrollTop = 0;
|
|
}
|
|
|
|
/**
|
|
* Check if variable is a number.
|
|
*
|
|
* @param {Mixed} value
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
function isNumber(value) {
|
|
return !isNaN(parseFloat(value)) && isFinite(value);
|
|
}
|
|
|
|
/**
|
|
* Parse style to pixels.
|
|
*
|
|
* @param {Object} $item jQuery object with element.
|
|
* @param {Property} property CSS property to get the pixels from.
|
|
*
|
|
* @return {Int}
|
|
*/
|
|
function getPx($item, property) {
|
|
return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, ''));
|
|
}
|
|
|
|
/**
|
|
* Make sure that number is within the limits.
|
|
*
|
|
* @param {Number} number
|
|
* @param {Number} min
|
|
* @param {Number} max
|
|
*
|
|
* @return {Number}
|
|
*/
|
|
function within(number, min, max) {
|
|
return number < min ? min : number > max ? max : number;
|
|
}
|
|
|
|
/**
|
|
* Saves element styles for later restoration.
|
|
*
|
|
* Example:
|
|
* var styles = new StyleRestorer(frame);
|
|
* styles.save('position');
|
|
* element.style.position = 'absolute';
|
|
* styles.restore(); // restores to state before the assignment above
|
|
*
|
|
* @param {Element} element
|
|
*/
|
|
function StyleRestorer(element) {
|
|
var self = {};
|
|
self.style = {};
|
|
self.save = function () {
|
|
if (!element || !element.nodeType) return;
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
self.style[arguments[i]] = element.style[arguments[i]];
|
|
}
|
|
return self;
|
|
};
|
|
self.restore = function () {
|
|
if (!element || !element.nodeType) return;
|
|
for (var prop in self.style) {
|
|
if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop];
|
|
}
|
|
return self;
|
|
};
|
|
return self;
|
|
}
|
|
|
|
// Local WindowAnimationTiming interface polyfill
|
|
(function (w) {
|
|
rAF = w.requestAnimationFrame
|
|
|| w.webkitRequestAnimationFrame
|
|
|| fallback;
|
|
|
|
/**
|
|
* Fallback implementation.
|
|
*/
|
|
var prev = new Date().getTime();
|
|
function fallback(fn) {
|
|
var curr = new Date().getTime();
|
|
var ms = Math.max(0, 16 - (curr - prev));
|
|
var req = setTimeout(fn, ms);
|
|
prev = curr;
|
|
return req;
|
|
}
|
|
|
|
/**
|
|
* Cancel.
|
|
*/
|
|
var cancel = w.cancelAnimationFrame
|
|
|| w.webkitCancelAnimationFrame
|
|
|| w.clearTimeout;
|
|
|
|
cAF = function(id){
|
|
cancel.call(w, id);
|
|
};
|
|
}(window));
|
|
|
|
// Feature detects
|
|
(function () {
|
|
var prefixes = ['', 'Webkit', 'Moz', 'ms', 'O'];
|
|
var el = document.createElement('div');
|
|
|
|
function testProp(prop) {
|
|
for (var p = 0, pl = prefixes.length; p < pl; p++) {
|
|
var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop;
|
|
if (el.style[prefixedProp] != null) {
|
|
return prefixedProp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global support indicators
|
|
transform = testProp('transform');
|
|
gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : '';
|
|
}());
|
|
|
|
// Expose class globally
|
|
w[className] = Sly;
|
|
|
|
// jQuery proxy
|
|
$.fn[pluginName] = function (options, callbackMap) {
|
|
var method, methodArgs;
|
|
|
|
// Attributes logic
|
|
if (!$.isPlainObject(options)) {
|
|
if (type(options) === 'string' || options === false) {
|
|
method = options === false ? 'destroy' : options;
|
|
methodArgs = Array.prototype.slice.call(arguments, 1);
|
|
}
|
|
options = {};
|
|
}
|
|
|
|
// Apply to all elements
|
|
return this.each(function (i, element) {
|
|
// Call with prevention against multiple instantiations
|
|
var plugin = Sly.getInstance(element);
|
|
|
|
if (!plugin && !method) {
|
|
// Create a new object if it doesn't exist yet
|
|
plugin = new Sly(element, options, callbackMap).init();
|
|
} else if (plugin && method) {
|
|
// Call method
|
|
if (plugin[method]) {
|
|
plugin[method].apply(plugin, methodArgs);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// Default options
|
|
Sly.defaults = {
|
|
slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE.
|
|
horizontal: false, // Switch to horizontal mode.
|
|
|
|
// Item based navigation
|
|
itemNav: null, // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'.
|
|
itemSelector: null, // Select only items that match this selector.
|
|
smart: false, // Repositions the activated item to help with further navigation.
|
|
activateOn: null, // Activate an item on this event. Can be: 'click', 'mouseenter', ...
|
|
activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only.
|
|
|
|
// Scrolling
|
|
scrollSource: null, // Element for catching the mouse wheel scrolling. Default is FRAME.
|
|
scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling.
|
|
scrollHijack: 300, // Milliseconds since last wheel event after which it is acceptable to hijack global scroll.
|
|
scrollTrap: false, // Don't bubble scrolling when hitting scrolling limits.
|
|
|
|
// Dragging
|
|
dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME.
|
|
mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor.
|
|
touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events.
|
|
releaseSwing: false, // Ease out on dragging swing release.
|
|
swingSpeed: 0.2, // Swing synchronization speed, where: 1 = instant, 0 = infinite.
|
|
elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries.
|
|
dragThreshold: 3, // Distance in pixels before Sly recognizes dragging.
|
|
interactive: null, // Selector for special interactive elements.
|
|
|
|
// Scrollbar
|
|
scrollBar: null, // Selector or DOM element for scrollbar container.
|
|
dragHandle: false, // Whether the scrollbar handle should be draggable.
|
|
dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content.
|
|
minHandleSize: 50, // Minimal height or width (depends on sly direction) of a handle in pixels.
|
|
clickBar: false, // Enable navigation by clicking on scrollbar.
|
|
syncSpeed: 0.5, // Handle => SLIDEE synchronization speed, where: 1 = instant, 0 = infinite.
|
|
|
|
// Pagesbar
|
|
pagesBar: null, // Selector or DOM element for pages bar container.
|
|
activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ...
|
|
pageBuilder: // Page item generator.
|
|
function (index) {
|
|
return '<li>' + (index + 1) + '</li>';
|
|
},
|
|
|
|
// Navigation buttons
|
|
forward: null, // Selector or DOM element for "forward movement" button.
|
|
backward: null, // Selector or DOM element for "backward movement" button.
|
|
prev: null, // Selector or DOM element for "previous item" button.
|
|
next: null, // Selector or DOM element for "next item" button.
|
|
prevPage: null, // Selector or DOM element for "previous page" button.
|
|
nextPage: null, // Selector or DOM element for "next page" button.
|
|
|
|
// Automated cycling
|
|
cycleBy: null, // Enable automatic cycling by 'items' or 'pages'.
|
|
cycleInterval: 5000, // Delay between cycles in milliseconds.
|
|
pauseOnHover: false, // Pause cycling when mouse hovers over the FRAME.
|
|
startPaused: false, // Whether to start in paused sate.
|
|
|
|
// Mixed options
|
|
moveBy: 300, // Speed in pixels per second used by forward and backward buttons.
|
|
speed: 0, // Animations speed in milliseconds. 0 to disable animations.
|
|
easing: 'swing', // Easing for duration based (tweening) animations.
|
|
startAt: null, // Starting offset in pixels or items.
|
|
keyboardNavBy: null, // Enable keyboard navigation by 'items' or 'pages'.
|
|
|
|
// Classes
|
|
draggedClass: 'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle).
|
|
activeClass: 'active', // Class for active items and pages.
|
|
disabledClass: 'disabled' // Class for disabled navigation elements.
|
|
};
|
|
}(jQuery, window));
|