Current Path : /var/www/www-root/data/www/monolith-realty.ru/local/templates/monolitsochi/js/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/local/templates/monolitsochi/js/sly.js |
/*! * sly 1.6.1 - 8th Aug 2015 * https://github.com/darsain/sly * * Licensed under the MIT license. * http://opensource.org/licenses/MIT */ ;(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'); if($item.parents('.frame.props').length){ if($item.find('td').eq(0).length){ itemSize -= $item.find('td').eq(0).outerWidth(); } } 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'; var scrollbar = $($sb).closest('.wrapp_scrollbar'); if(handleSize!=sbSize){ scrollbar.addClass('relative_block'); $($sb).parent().animate({'opacity': 1}, 50); $($sb).fadeIn(); scrollbar.find('.slider_navigation').fadeIn(); //if($($frame).hasClass('props')){ $($frame).addClass('border'); //} }else{ scrollbar.removeClass('relative_block'); $($sb).parent().animate({'opacity': 0}, 50); $($sb).fadeOut(); scrollbar.find('.slider_navigation').fadeOut(); //if($($frame).hasClass('props')){ $($frame).removeClass('border'); //} } } 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));