/*global define, window, document*/
/*jslint vars:true*/
define('vendor/sly', ['jquery', '$ready!'], function ($) {
'use strict';
var w = window;
/*!
* sly 1.0.1 - 8th Jun 2013
* https://github.com/Darsain/sly
*
* Licensed under the MIT license.
* http://opensource.org/licenses/MIT
*/
;(function ($, w, undefined) {
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 clickEvent = 'click.' + namespace;
var mouseDownEvent = 'mousedown.' + namespace;
/**
* 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) {
// Extend options
var o = $.extend({}, Sly.defaults, options);
// Private variables
var self = this;
var initialized = 0;
var parallax = isNumber(frame);
// Frame
var $frame = $(frame);
var $slidee = $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: -1,
activePage: 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 renderID = 0;
var historyID = 0;
var cycleID = 0;
var continuousID = 0;
var i, l;
// Normalizing frame
if (!parallax) {
frame = $frame[0];
}
// Expose properties
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;
/**
* (Re)Loading function.
*
* Populate arrays, set sizes, bind events, ...
*
* @return {Void}
*/
function load() {
// Local variables
var ignoredMargin = 0;
// 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 = Math.max(slideeSize - frameSize, 0);
last = {};
// Sizes & offsets for item based navigations
if (itemNav) {
// Save the number of current items
var 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 marginStart = getPx($items, o.horizontal ? 'marginLeft' : 'marginTop');
var marginEnd = getPx($items.slice(-1), o.horizontal ? 'marginRight' : 'marginBottom');
var centerOffset = 0;
var areFloated = $items.css('float') !== 'none';
// Update ignored margin
ignoredMargin = marginStart ? 0 : marginEnd;
// Reset slideeSize
slideeSize = 0;
// Iterate through items
$items.each(function (i, element) {
// Item
var $item = $(element);
var itemSize = $item[o.horizontal ? 'outerWidth' : 'outerHeight'](true);
var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop');
var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom');
var itemData = {
el: element,
size: itemSize,
half: itemSize / 2,
start: slideeSize - (!i || o.horizontal ? 0 : itemMarginStart),
center: slideeSize - Math.round(frameSize / 2 - itemSize / 2),
end: slideeSize - frameSize + itemSize - (marginStart ? 0 : itemMarginEnd)
};
// Account for centerOffset & slidee padding
if (!i) {
centerOffset = -(forceCenteredNav ? Math.round(frameSize / 2 - itemSize / 2) : 0) + paddingStart;
slideeSize += paddingStart;
}
// Increment slidee size for size of the active element
slideeSize += itemSize;
// 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 -= Math.min(itemMarginStart, itemMarginEnd);
}
}
// Things to be done on last item
if (i === $items.length - 1) {
slideeSize += paddingEnd;
}
// Add item object to items array
items.push(itemData);
});
// Resize SLIDEE to fit all items
$slidee[0].style[o.horizontal ? 'width' : 'height'] = slideeSize + 'px';
// Adjust internal SLIDEE size for last margin
slideeSize -= ignoredMargin;
// Set limits
pos.start = centerOffset;
pos.end = forceCenteredNav ? (items.length ? items[items.length - 1].center : centerOffset) : Math.max(slideeSize - frameSize, 0);
// Activate last item if previous active has been removed, or first item
// when there were no items before, and new got appended.
if (rel.activeItem >= items.length || lastItemsCount === 0 && items.length > 0) {
activate(items.length > 0 ? items.length - 1 : 0);
}
}
// Calculate SLIDEE center position
pos.center = Math.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) {
// ForceCentered navigation edge case
if (forceCenteredNav) {
handleSize = items.length ? sbSize * frameSize / (frameSize + items[items.length-1].center - items[0].center) : sbSize;
} else {
handleSize = sbSize * frameSize / slideeSize;
}
handleSize = within(Math.round(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 || item.start + item.size > tempPagePos) {
tempPagePos = item[forceCenteredNav ? 'center' : 'start'];
pages.push(tempPagePos);
tempPagePos += frameSize;
}
});
} else {
while (tempPagePos - frameSize < pos.end) {
pages.push(tempPagePos);
tempPagePos += frameSize;
}
}
// Pages bar
if ($pb[0]) {
for (var i = 0; i < pages.length; i++) {
pagesHtml += o.pageBuilder.call(self, i);
}
$pages = $pb.html(pagesHtml).children();
}
}
// Fix possible overflowing
slideTo(within(pos.dest, pos.start, pos.end));
// Extend relative variables object with some useful info
rel.slideeSize = slideeSize;
rel.frameSize = frameSize;
rel.sbSize = sbSize;
rel.handleSize = handleSize;
// Trigger load event
trigger('load');
}
self.reload = 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 = immediate || dragging.init && dragging.slidee && !dragging.tweese;
// 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 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 (Math.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 = Math.min(+new Date() - animation.start, o.speed);
pos.cur = animation.from + animation.delta * jQuery.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'] = -Math.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(Math.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 : Math.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 - 1);
};
/**
* Activate next item.
*
* @return {Void}
*/
self.next = function () {
self.activate(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 Difference in position. Positive means forward, negative means backward.
* @param {Bool} immediate Reposition immediately without an animation.
*
* @return {Void}
*/
self.slideBy = function (delta, immediate) {
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 type(item) !== 'undefined' ?
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) {
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) {
// 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 : isEnd ? 2 : 3;
// 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) {
var isFirst = rel.activeItem === 0;
var isLast = rel.activeItem >= items.length - 1;
var itemsButtonState = isFirst ? 1 : isLast ? 2 : 3;
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] || 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 (type(index) === 'undefined' || !items[0]) {
$element.appendTo($slidee);
} else if (items.length) {
$element.insertBefore(items[index].el);
}
// Adjust the activeItem index
if (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 && !(forceCenteredNav && o.activateMiddle);
// Adjust the activeItem index
if (index < rel.activeItem || rel.activeItem >= items.length - 1) {
last.active = --rel.activeItem;
}
// Reload
load();
// Activate new item at the removed position if the current active got removed
if (reactivate) {
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 (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]);
}
}
};
/**
* 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 (type(fn) === 'undefined') {
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 Math.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) {
// Ignore when already in progress
if (dragging.init) {
return;
}
var isTouch = event.type === 'touchstart';
var source = event.data.source;
var isSlidee = source === 'slidee';
// 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) {
stopDefault(event, 1);
}
// Reset dragging object
continuousInit(source);
// Properties used in dragHandler
dragging.$source = $(event.target);
dragging.init = 0;
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.pathToInit = isSlidee ? isTouch ? 50 : 10 : 0;
dragging.history = [0, 0, 0, 0];
dragging.initLoc = dragging[o.horizontal ? 'initX' : 'initY'];
dragging.deltaMin = isSlidee ? -dragging.initLoc : -hPos.cur;
dragging.deltaMax = isSlidee ? document[o.horizontal ? 'width' : 'height'] - dragging.initLoc : hPos.end - hPos.cur;
// Add dragging class
(isSlidee ? $slidee : $handle).addClass(o.draggedClass);
// Bind dragging events
$doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler);
// 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.pathTotal = Math.sqrt(Math.pow(dragging.pathX, 2) + Math.pow(dragging.pathY, 2));
dragging.delta = within(o.horizontal ? dragging.pathX : dragging.pathY, dragging.deltaMin, dragging.deltaMax);
// Initialization
if (!dragging.init && dragging.pathTotal > dragging.pathToInit) {
if (dragging.slidee) {
// If path has reached the pathToInit value, but in a wrong direction, cancel dragging
if (o.horizontal ? Math.abs(dragging.pathX) < Math.abs(dragging.pathY) : Math.abs(dragging.pathX) > Math.abs(dragging.pathY)) {
dragEnd();
return;
}
// Disable click on a source element, as it is unwelcome when dragging SLIDEE
dragging.$source.on(clickEvent, disableOneEvent);
}
// Mark dragging as initiated
dragging.init = 1;
// Pause ongoing cycle
self.pause(1);
// Trigger moveStart event
trigger('moveStart');
}
// Proceed when initialized
if (dragging.init) {
if (dragging.released) {
if (!dragging.touch) {
stopDefault(event);
}
dragging.init = 0;
// 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 = Math.abs(dragging.swing) > 10;
}
} else {
stopDefault(event);
}
slideTo(dragging.slidee ? Math.round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta));
}
// Stop and cleanup after dragging
if (dragging.released) {
dragEnd();
}
}
/**
* Stops dragging and cleans up after it.
*
* @return {Void}
*/
function dragEnd() {
clearInterval(historyID);
$doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler);
(dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass);
// Resume ongoing cycle
self.resume(1);
// 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');
}
}
/**
* 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) {
return within(-event.wheelDelta || event.detail, -1, 1);
}
/**
* Mouse scrolling handler.
*
* @param {Event} event
*
* @return {Void}
*/
function scrollHandler(event) {
// Ignore if there is no scrolling to be done
if (!o.scrollBy || pos.start === pos.end) {
return;
}
stopDefault(event, 1);
var delta = normalizeWheelDelta(event.originalEvent);
if (itemNav) {
self[centeredNav ? 'toCenter' : 'toStart'](
within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length)
);
} else {
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() {
/*jshint validthis:true */
// Accept only events from direct SLIDEE children.
if (this.parentNode === $slidee[0]) {
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]) {
for (i = 0, l = callbacks[name].length; i < l; i++) {
callbacks[name][i].call(self, name, arg1);
}
}
}
/**
* Destroys instance and everything it created.
*
* @return {Void}
*/
self.destroy = function () {
// Unbind all events
$doc
.add($scrollSource)
.add($handle)
.add($sb)
.add($pb)
.add($forwardButton)
.add($backwardButton)
.add($prevButton)
.add($nextButton)
.add($prevPageButton)
.add($nextPageButton)
.unbind('.' + namespace);
// Remove classes
$prevButton
.add($nextButton)
.add($prevPageButton)
.add($nextPageButton)
.removeClass(o.disabledClass);
if ($items) {
$items.eq(rel.activeItem).removeClass(o.activeClass);
}
// Remove page items
$pb.empty();
if (!parallax) {
// Unbind events from frame
$frame.unbind('.' + namespace);
// Reset SLIDEE and handle positions
$slidee.add($handle).css(transform || (o.horizontal ? 'left' : 'top'), transform ? 'none' : 0);
// Remove the instance from element data storage
$.removeData(frame, namespace);
}
// Reset initialized status and return the instance
initialized = 0;
return self;
};
/**
* Initialize.
*
* @return {Object}
*/
self.init = function () {
if (initialized) {
return;
}
// Register callbacks map
self.on(callbackMap);
// 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('DOMMouseScroll.' + namespace + ' mousewheel.' + namespace, 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.bind('keydown.' + namespace, keyboardHandler);
if (!parallax) {
// Pause on hover
$frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler);
// Reset native FRAME element scroll
$frame.on('scroll.' + namespace, resetScroll);
}
// Load
load();
// Activate requested position
if (itemNav) {
activate(o.startAt);
self[centeredNav ? 'toCenter' : 'toStart'](o.startAt);
} else {
slideTo(o.startAt, 1);
}
// Initiate automatic cycling
if (o.cycleBy && !parallax) {
self[o.startPaused ? 'pause' : 'resume']();
}
// Mark instance as initialized
initialized = 1;
// Return instance
return self;
};
}
/**
* 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 parseInt($item.css(property), 10) || 0;
}
/**
* 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;
}
// Local WindowAnimationTiming interface polyfill
(function (w) {
var vendors = ['moz', 'webkit', 'o'];
var lastTime = 0;
// For a more accurate WindowAnimationTiming interface implementation, ditch the native
// requestAnimationFrame when cancelAnimationFrame is not present (older versions of Firefox)
for(var i = 0, l = vendors.length; i < l && !cAF; ++i) {
cAF = w[vendors[i]+'CancelAnimationFrame'] || w[vendors[i]+'CancelRequestAnimationFrame'];
rAF = cAF && w[vendors[i]+'RequestAnimationFrame'];
}
if (!cAF) {
rAF = function (callback) {
var currTime = +new Date();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
lastTime = currTime + timeToCall;
return w.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall);
};
cAF = function (id) {
clearTimeout(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] !== undefined) {
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 = $.data(element, namespace);
if (!plugin && !method) {
// Create a new object if it doesn't exist yet
plugin = $.data(element, namespace, new Sly(element, options, callbackMap).init());
} else if (plugin && method) {
// Call method
if (plugin[method]) {
plugin[method].apply(plugin, methodArgs);
}
}
});
};
// Default options
Sly.defaults = {
horizontal: 0, // 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: 0, // Repositions the activated item to help with further navigation.
activateOn: null, // Activate an item on this event. Can be: 'click', 'mouseenter', ...
activateMiddle: 0, // 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.
// Dragging
dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME.
mouseDragging: 0, // Enable navigation by dragging the SLIDEE with mouse cursor.
touchDragging: 0, // Enable navigation by dragging the SLIDEE with touch events.
releaseSwing: 0, // Ease out on dragging swing release.
swingSpeed: 0.2, // Swing synchronization speed, where: 1 = instant, 0 = infinite.
elasticBounds: 0, // Stretch SLIDEE position limits when dragging past FRAME boundaries.
// Scrollbar
scrollBar: null, // Selector or DOM element for scrollbar container.
dragHandle: 0, // Whether the scrollbar handle should be draggable.
dynamicHandle: 0, // 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: 0, // 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: 0, // Pause cycling when mouse hovers over the FRAME.
startPaused: 0, // 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: 0, // 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.
};
}($, window));
return Sly;
});