1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438 |
- /*jshint eqeqeq: true, noempty: true, strict: true, undef: true, expr: true, smarttabs: true, browser: true */
- /*global jQuery:false */
- ;(function($, undefined){
- 'use strict';
- // Plugin names
- var pluginName = 'sly',
- namespace = 'plugin_' + pluginName;
- /**
- * Plugin class
- *
- * @class
- * @param {Element} frame DOM element of sly container
- * @param {Object} o Object with plugin options
- */
- function Plugin( frame, o ){
- // Alias for this
- var self = this,
- // Frame variables
- $frame = $(frame),
- $slidee = $frame.children().eq(0),
- frameSize = 0,
- slideeSize = 0,
- pos = {
- cur: 0,
- max: 0,
- min: 0
- },
- // Scrollbar variables
- $sb = $(o.scrollBar).eq(0),
- $handle = $sb.length ? $sb.children().eq(0) : 0,
- sbSize = 0,
- handleSize = 0,
- hPos = {
- cur: 0,
- max: 0,
- min: 0
- },
- // Pagesbar variables
- $pb = $(o.pagesBar),
- $pages = 0,
- pages = [],
- // Navigation type booleans
- basicNav = o.itemNav === 'basic',
- smartNav = o.itemNav === 'smart',
- forceCenteredNav = o.itemNav === 'forceCentered',
- centeredNav = o.itemNav === 'centered' || forceCenteredNav,
- itemNav = basicNav || smartNav || centeredNav || forceCenteredNav,
- // Other variables
- $items = 0,
- items = [],
- rel = {
- firstItem: 0,
- lastItem: 1,
- centerItem: 1,
- activeItem: -1,
- activePage: 0,
- items: 0,
- pages: 0
- },
- $scrollSource = o.scrollSource ? $( o.scrollSource ) : $frame,
- $dragSource = o.dragSource ? $( o.dragSource ) : $frame,
- $prevButton = $(o.prev),
- $nextButton = $(o.next),
- $prevPageButton = $(o.prevPage),
- $nextPageButton = $(o.nextPage),
- cycleIndex = 0,
- cycleIsPaused = 0,
- isDragging = 0,
- callbacks = {};
- /**
- * (Re)Loading function
- *
- * Populates arrays, sets sizes, binds events, ...
- *
- * @public
- */
- var load = this.reload = function(){
- // Local variables
- var ignoredMargin = 0,
- oldPos = $.extend({}, pos);
- // Clear cycling timeout
- clearTimeout( cycleIndex );
- // Reset global variables
- frameSize = o.horizontal ? $frame.width() : $frame.height();
- sbSize = o.horizontal ? $sb.width() : $sb.height();
- slideeSize = o.horizontal ? $slidee.outerWidth() : $slidee.outerHeight();
- $items = $slidee.children();
- items = [];
- pages = [];
- // Set position limits & relatives
- pos.min = 0;
- pos.max = slideeSize > frameSize ? slideeSize - frameSize : 0;
- rel.items = $items.length;
- // Sizes & offsets logic, but only when needed
- if( itemNav ){
- var marginStart = getPx( $items, o.horizontal ? 'marginLeft' : 'marginTop' ),
- marginEnd = getPx( $items.slice(-1), o.horizontal ? 'marginRight' : 'marginBottom' ),
- centerOffset = 0,
- paddingStart = getPx( $slidee, o.horizontal ? 'paddingLeft' : 'paddingTop' ),
- paddingEnd = getPx( $slidee, o.horizontal ? 'paddingRight' : 'paddingBottom' ),
- areFloated = $items.css('float') !== 'none';
- // Update ignored margin
- ignoredMargin = marginStart ? 0 : marginEnd;
- // Reset slideeSize
- slideeSize = 0;
- // Iterate through items
- $items.each(function(i,e){
- // Item
- var item = $(e),
- itemSize = o.horizontal ? item.outerWidth(true) : item.outerHeight(true),
- marginTop = getPx( item, 'marginTop' ),
- marginBottom = getPx( item, 'marginBottom'),
- marginLeft = getPx( item, 'marginLeft'),
- marginRight = getPx( item, 'marginRight'),
- itemObj = {
- size: itemSize,
- offStart: slideeSize - ( !i || o.horizontal ? 0 : marginTop ),
- offCenter: slideeSize - Math.round( frameSize / 2 - itemSize / 2 ),
- offEnd: slideeSize - frameSize + itemSize - ( marginStart ? 0 : marginRight ),
- margins: {
- top: marginTop,
- bottom: marginBottom,
- left: marginLeft,
- right: marginRight
- }
- };
- // 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( marginBottom && marginTop && i > 0 ){
- slideeSize -= marginTop < marginBottom ? marginTop : marginBottom;
- }
- }
- // Things to be done at last item
- if( i === $items.length - 1 ){
- slideeSize += paddingEnd;
- }
- // Add item object to items array
- items.push(itemObj);
- });
- // Resize slidee
- $slidee.css( o.horizontal ? { width: slideeSize+'px' } : { height: slideeSize+'px' } );
- // Adjust slidee size for last margin
- slideeSize -= ignoredMargin;
- // Set limits
- pos.min = centerOffset;
- pos.max = forceCenteredNav ? items[items.length-1].offCenter : slideeSize > frameSize ? slideeSize - frameSize : 0;
- // Fix overflowing activeItem
- rel.activeItem >= items.length && self.activate( items.length-1 );
- }
- // Assign relative position indexes
- assignRelatives();
- // Scrollbar
- if( $handle ){
- // Stretch scrollbar handle to represent the visible area
- handleSize = o.dynamicHandle ? Math.round( sbSize * frameSize / slideeSize ) : o.horizontal ? $handle.width() : $handle.height();
- handleSize = handleSize > sbSize ? sbSize : handleSize;
- handleSize = handleSize < o.minHandleSize ? o.minHandleSize : handleSize;
- hPos.max = sbSize - handleSize;
- // Resize handle
- $handle.css( o.horizontal ? { width: handleSize+'px' } : { height: handleSize+'px' } );
- }
- // Pages
- var tempPagePos = 0,
- pagesHtml = '',
- pageIndex = 0;
- // Populate pages array
- if( forceCenteredNav ){
- pages = $.map( items, function( o ){ return o.offCenter; } );
- } else {
- while( tempPagePos - frameSize < pos.max ){
- var pagePos = tempPagePos > pos.max ? pos.max : tempPagePos;
- pages.push( pagePos );
- tempPagePos += frameSize;
- // When item navigation, and last page is smaller than half of the last item size,
- // adjust the last page position to pos.max and break the loop
- if( tempPagePos > pos.max && itemNav && pos.max - pagePos < ( items[items.length-1].size - ignoredMargin ) / 2 ){
- pages[pages.length-1] = pos.max;
- break;
- }
- }
- }
- // Pages bar
- if( $pb.length ){
- for( var i = 0; i < pages.length; i++ ){
- pagesHtml += o.pageBuilder( pageIndex++ );
- }
- // Bind page navigation, append to pagesbar, and save to $pages variable
- $pages = $(pagesHtml).bind('click.' + namespace, function(){
- self.activatePage( $pages.index(this) );
- }).appendTo( $pb.empty() );
- }
- // Bind activating to items
- $items.unbind('.' + namespace).bind('mouseup.' + namespace, function(e){
- e.which === 1 && !isDragging && self.activate( this );
- });
- // Fix overflowing
- pos.cur < pos.min && slide( pos.min );
- pos.cur > pos.max && slide( pos.max );
- // Extend relative variables object with some useful info
- rel.pages = pages.length;
- rel.slideeSize = slideeSize;
- rel.frameSize = frameSize;
- rel.sbSize = sbSize;
- rel.handleSize = handleSize;
- // Synchronize scrollbar
- syncBars(0);
- // Disable buttons
- disableButtons();
- // Automatic cycling
- if( itemNav && o.cycleBy ){
- var pauseEvents = 'mouseenter.' + namespace + ' mouseleave.' + namespace;
- // Pause on hover
- o.pauseOnHover && $frame.unbind(pauseEvents).bind(pauseEvents, function(e){
- !cycleIsPaused && self.cycle( e.type === 'mouseenter', 1 );
- });
- // Initiate cycling
- self.cycle( o.startPaused );
- }
- // Trigger :load event
- $frame.trigger( pluginName + ':load', [ $.extend({}, pos, { old: oldPos }), $items, rel ] );
- };
- /**
- * Slide the slidee
- *
- * @private
- *
- * @param {Int} newPos New slidee position in relation to frame
- * @param {Bool} align Whetner to Align elements to the frame border
- * @param {Int} speed Animation speed in milliseconds
- */
- function slide( newPos, align, speed ){
- speed = isNumber( speed ) ? speed : o.speed;
- // Align items
- if( align && itemNav ){
- var tempRel = getRelatives( newPos );
- if( centeredNav ){
- newPos = items[tempRel.centerItem].offCenter;
- self[ forceCenteredNav ? 'activate' : 'toCenter']( tempRel.centerItem, 1 );
- } else if( newPos > pos.min && newPos < pos.max ){
- newPos = items[tempRel.firstItem].offStart;
- }
- }
- // Fix overflowing position
- if( !isDragging || !o.elasticBounds ){
- newPos = newPos < pos.min ? pos.min : newPos;
- newPos = newPos > pos.max ? pos.max : newPos;
- }
- // Stop if position has not changed
- if( newPos === pos.cur ) {
- return;
- } else {
- pos.cur = newPos;
- }
- // Reassign relative indexes
- assignRelatives();
- // Add disabled classes
- disableButtons();
- // halt ongoing animations
- stop();
- // Trigger :move event
- !isDragging && $frame.trigger( pluginName + ':move', [ pos, $items, rel ] );
- var newProp = o.horizontal ? { left: -pos.cur+'px' } : { top: -pos.cur+'px' };
- // Slidee move
- if( speed > 16 ){
- $slidee.animate( newProp, speed, isDragging ? 'swing' : o.easing, function(e){
- // Trigger :moveEnd event
- !isDragging && $frame.trigger( pluginName + ':moveEnd', [ pos, $items, rel ] );
- });
- } else {
- $slidee.css( newProp );
- // Trigger :moveEnd event
- !isDragging && $frame.trigger( pluginName + ':moveEnd', [ pos, $items, rel ] );
- }
- }
- /**
- * Synchronizes scrollbar & pagesbar positions with the slidee
- *
- * @private
- *
- * @param {Int} speed Animation speed for scrollbar synchronization
- */
- function syncBars( speed ){
- // Scrollbar synchronization
- if ($handle) {
- hPos.cur = Math.round( ( pos.cur - pos.min ) / ( pos.max - pos.min ) * hPos.max );
- hPos.cur = hPos.cur < hPos.min ? hPos.min : hPos.cur > hPos.max ? hPos.max : hPos.cur;
- $handle.stop().animate( o.horizontal ? { left: hPos.cur+'px' } : { top: hPos.cur+'px' }, isNumber(speed) ? speed : o.speed, o.easing );
- }
- // Pagesbar synchronization
- syncPages();
- }
- /**
- * Synchronizes pagesbar
- *
- * @private
- */
- function syncPages(){
- if (!$pages.length) {
- return;
- }
- // Classes
- $pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
- }
- /**
- * Activate previous item
- *
- * @public
- */
- this.prev = function(){
- self.activate( rel.activeItem - 1 );
- };
- /**
- * Activate next item
- *
- * @public
- */
- this.next = function(){
- self.activate( rel.activeItem + 1 );
- };
- /**
- * Activate previous page
- *
- * @public
- */
- this.prevPage = function(){
- self.activatePage( rel.activePage - 1 );
- };
- /**
- * Activate next page
- *
- * @public
- */
- this.nextPage = function(){
- self.activatePage( rel.activePage + 1 );
- };
- /**
- * Stop ongoing animations
- *
- * @private
- */
- function stop(){
- $slidee.add($handle).stop();
- }
- /**
- * Animate element or the whole slidee to the start of the frame
- *
- * @public
- *
- * @param {Element|Int} el DOM element, or index of element in items array
- */
- this.toStart = function( el ){
- if( itemNav ){
- var index = getIndex( el );
- if( el === undefined ){
- slide( pos.min, 1 );
- } else if( index !== -1 ){
- // You can't align items to the start of the frame when centeredNav is enabled
- if (centeredNav) {
- return;
- }
- index !== -1 && slide( items[index].offStart );
- }
- } else {
- if( el === undefined ){
- slide( pos.min );
- } else {
- var $el = $slidee.find(el).eq(0);
- if( $el.length ){
- var offset = o.horizontal ? $el.offset().left - $slidee.offset().left : $el.offset().top - $slidee.offset().top;
- slide( offset );
- }
- }
- }
- syncBars();
- };
- /**
- * Animate element or the whole slidee to the end of the frame
- *
- * @public
- *
- * @param {Element|Int} el DOM element, or index of element in items array
- */
- this.toEnd = function( el ){
- if( itemNav ){
- var index = getIndex( el );
- if( el === undefined ){
- slide( pos.max, 1 );
- } else if( index !== -1 ){
- // You can't align items to the end of the frame when centeredNav is enabled
- if (centeredNav) {
- return;
- }
- slide( items[index].offEnd );
- }
- } else {
- if( el === undefined ){
- slide( pos.max );
- } else {
- var $el = $slidee.find(el).eq(0);
- if( $el.length ){
- var offset = o.horizontal ? $el.offset().left - $slidee.offset().left : $el.offset().top - $slidee.offset().top;
- slide( offset - frameSize + $el[o.horizontal ? 'outerWidth' : 'outerHeight']() );
- }
- }
- }
- syncBars();
- };
- /**
- * Animate element or the whole slidee to the center of the frame
- *
- * @public
- *
- * @param {Element|Int} el DOM element, or index of element in items array
- */
- this.toCenter = function( el ){
- if( itemNav ){
- var index = getIndex( el );
- if( el === undefined ){
- slide( Math.round( pos.max / 2 + pos.min / 2 ), 1 );
- } else if( index !== -1 ){
- slide( items[index].offCenter );
- forceCenteredNav && self.activate( index, 1 );
- }
- } else {
- if( el === undefined ){
- slide( Math.round( pos.max / 2 ) );
- } else {
- var $el = $slidee.find(el).eq(0);
- if( $el.length ){
- var offset = o.horizontal ? $el.offset().left - $slidee.offset().left : $el.offset().top - $slidee.offset().top;
- slide( offset - frameSize / 2 + $el[o.horizontal ? 'outerWidth' : 'outerHeight']() / 2 );
- }
- }
- }
- syncBars();
- };
- /**
- * Get an index of the element
- *
- * @private
- *
- * @param {Element|Int} el DOM element, or index of element in items array
- */
- function getIndex( el ){
- return isNumber(el) ? el < 0 ? 0 : el > items.length-1 ? items.length-1 : el : el === undefined ? -1 : $items.index( el );
- }
- /**
- * Parse style to pixels
- *
- * @private
- *
- * @param {Object} $item jQuery object with element
- * @param {Property} property Property to get the pixels from
- */
- function getPx( $item, property ){
- return parseInt( $item.css( property ), 10 );
- }
- /**
- * Activates an element
- *
- * Element is positioned to one of the sides of the frame, based on it's current position.
- * If the element is close to the right frame border, it will be animated to the start of the left border,
- * and vice versa. This helps user to navigate through the elements only by clicking on them, without
- * the need for navigation buttons, scrolling, or keyboard arrows.
- *
- * @public
- *
- * @param {Element|Int} el DOM element, or index of element in items array
- * @param {Bool} noReposition Activate item without repositioning it
- */
- this.activate = function( el, noReposition ){
- if (!itemNav || el === undefined) {
- return;
- }
- var index = getIndex( el ),
- oldActive = rel.activeItem;
- // Update activeItem index
- rel.activeItem = index;
- // Add active class to the active element
- $items.removeClass(o.activeClass).eq(index).addClass(o.activeClass);
- // Trigget :active event if a new element is being activated
- index !== oldActive && $items.eq( index ).trigger( pluginName + ':active', [ $items, rel ] );
- if( !noReposition ){
- // When centeredNav is enabled, center the element
- if( centeredNav ){
- self.toCenter( index );
- // Otherwise determine where to position the element
- } else if( smartNav ) {
- // If activated element is currently on the far right side of the frame, assume that
- // user is moving forward and animate it to the start of the visible frame, and vice versa
- if (index >= rel.lastItem) {
- self.toStart(index);
- } else if (index <= rel.firstItem) {
- self.toEnd(index);
- }
- }
- }
- // Add disabled classes
- disableButtons();
- };
- /**
- * Activates a page
- *
- * @public
- *
- * @param {Int} index Page index, starting from 0
- */
- this.activatePage = function( index ){
- // Fix overflowing
- index = index < 0 ? 0 : index >= pages.length ? pages.length-1 : index;
- slide( pages[index], itemNav );
- syncBars();
- };
- /**
- * Return relative positions of items based on their location within visible frame
- *
- * @private
- *
- * @param {Int} sPos Position of slidee
- */
- function getRelatives( sPos ){
- var newRel = {},
- centerOffset = forceCenteredNav ? 0 : frameSize / 2;
- // Determine active page
- for( var p = 0; p < pages.length; p++ ){
- if( sPos >= pos.max || p === pages.length - 1 ){
- newRel.activePage = pages.length - 1;
- break;
- }
- if( sPos <= pages[p] + centerOffset ){
- newRel.activePage = p;
- break;
- }
- }
- // Relative item indexes
- if( itemNav ){
- var first = false,
- last = false,
- center = false;
- /* From start */
- for( var i=0; i < items.length; i++ ){
- // First item
- if (first === false && sPos <= items[i].offStart) {
- first = i;
- }
- // Centered item
- if (center === false && sPos - items[i].size / 2 <= items[i].offCenter) {
- center = i;
- }
- // Last item
- if (i === items.length - 1 || (last === false && sPos < items[i + 1].offEnd)) {
- last = i;
- }
- // Terminate if all are assigned
- if (last !== false) {
- break;
- }
- }
- // Safe assignment, just to be sure the false won't be returned
- newRel.firstItem = isNumber( first ) ? first : 0;
- newRel.centerItem = isNumber( center ) ? center : newRel.firstItem;
- newRel.lastItem = isNumber( last ) ? last : newRel.centerItem;
- }
- return newRel;
- }
- /**
- * Assign element indexes to the relative positions
- *
- * @private
- */
- function assignRelatives(){
- $.extend( rel, getRelatives( pos.cur ) );
- }
- /**
- * Disable buttons when needed
- *
- * Adds disabledClass, and when the button is <button> or <input>,
- * activates :disabled state
- *
- * @private
- */
- function disableButtons(){
- // item navigation
- if( itemNav ){
- var isFirstItem = rel.activeItem === 0,
- isLastItem = rel.activeItem >= items.length-1;
- if( $prevButton.is('button,input') ){
- $prevButton.prop('disabled', isFirstItem);
- }
- if( $nextButton.is('button,input') ){
- $nextButton.prop('disabled', isLastItem);
- }
- $prevButton[ isFirstItem ? 'removeClass' : 'addClass'](o.disabledClass);
- $nextButton[ isLastItem ? 'removeClass' : 'addClass'](o.disabledClass);
- }
- // pages navigation
- if( $pages.length ){
- var isStart = pos.cur <= pos.min,
- isEnd = pos.cur >= pos.max;
- if( $prevPageButton.is('button,input') ){
- $prevPageButton.prop('disabled', isStart);
- }
- if( $nextPageButton.is('button,input') ){
- $nextPageButton.prop('disabled', isEnd);
- }
- $prevPageButton[ isStart ? 'removeClass' : 'addClass'](o.disabledClass);
- $nextPageButton[ isEnd ? 'removeClass' : 'addClass'](o.disabledClass);
- }
- }
- /**
- * Manage cycling
- *
- * @public
- *
- * @param {Bool} pause Pass true to pause cycling
- * @param {Bool} soft Soft pause intended for pauseOnHover - won't set cycleIsPaused variable to true
- */
- this.cycle = function( pause, soft ){
- if (!itemNav || !o.cycleBy) {
- return;
- }
- if( !soft ){
- cycleIsPaused = !!pause;
- }
- if( pause ){
- if( cycleIndex ){
- cycleIndex = clearTimeout( cycleIndex );
- // Trigger :cyclePause event
- $frame.trigger( pluginName + ':cyclePause', [ pos, $items, rel ] );
- }
- } else {
- // Don't initiate more than one cycle
- if (cycleIndex) {
- return;
- }
- // Trigger :cycleStart event
- $frame.trigger( pluginName + ':cycleStart', [ pos, $items, rel ] );
- // Cycling loop
- (function loop(){
- if( o.cycleInterval === 0 ){
- return;
- }
- cycleIndex = setTimeout( function(){
- if( !isDragging ){
- switch( o.cycleBy ){
- case 'items':
- var nextItem = rel.activeItem >= items.length-1 ? 0 : rel.activeItem + 1;
- self.activate( nextItem );
- break;
- case 'pages':
- var nextPage = rel.activePage >= pages.length-1 ? 0 : rel.activePage + 1;
- self.activatePage( nextPage );
- break;
- }
- }
- // Trigger :cycle event
- $frame.trigger( pluginName + ':cycle', [ pos, $items, rel ] );
- // Cycle the cycle!
- loop();
- }, o.cycleInterval );
- }());
- }
- };
- /**
- * Crossbrowser reliable way to stop default event action
- *
- * @private
- *
- * @param {Event} e Event object
- * @param {Bool} noBubbles Cancel event bubbling
- */
- function stopDefault( e, noBubbles ){
- var evt = e || window.event;
- evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;
- noBubbles && evt.stopPropagation ? evt.stopPropagation() : evt.cancelBubble = true;
- }
- /**
- * Updates a signle or multiple option values
- *
- * @param {Mixed} property Option property name that should be updated, or object with options that will extend the current one
- * @param {Mixed} value Option property value
- *
- * @public
- */
- this.set = function( property, value ){
- if( $.isPlainObject(property) ){
- o = $.extend({}, o, property);
- } else if( typeof property === 'string' ) {
- o[property] = value;
- }
- };
- /**
- * Destroys plugin instance and everything it created
- *
- * @public
- */
- this.destroy = function(){
- // Unbind all events
- $frame.add(document).add($slidee).add($items).add($scrollSource).add($handle)
- .add($prevButton).add($nextButton).add($prevPageButton).add($nextPageButton)
- .unbind('.' + namespace);
- // Reset some styles
- $slidee.add($handle).css( o.horizontal ? { left: 0 } : { top: 0 } );
- // Remove plugin classes
- $prevButton.add($nextButton).removeClass(o.disabledClass);
- // Remove page items
- $pb.empty();
- // Remove plugin from element data storage
- $.removeData(frame, namespace);
- };
- /**
- * Check if variable is a number
- *
- * @param {Mixed} n Any type of variable
- *
- * @return {Boolean}
- */
- function isNumber( n ) {
- return !isNaN(parseFloat(n)) && isFinite(n);
- }
- /** Constructor */
- (function(){
- var doc = $(document),
- dragEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
- // Extend options
- o = $.extend( {}, $.fn[pluginName].defaults, o );
- // Set required styles to elements
- $frame.css({ overflow: 'hidden' }).css('position') === 'static' && $frame.css({ position: 'relative' });
- $sb.css('position') === 'static' && $sb.css({ position: 'relative' });
- $slidee.add($handle).css( o.horizontal ? { position: 'absolute', left: 0 } : { position: 'absolute', top: 0 } );
- // Load
- load();
- // Activate requested position
- itemNav ? self.activate( o.startAt ) : slide( o.startAt );
- // Sync scrollbar & pages
- syncBars();
- // Scrolling navigation
- o.scrollBy && $scrollSource.bind('DOMMouseScroll.' + namespace + ' mousewheel.' + namespace, function(e){
- // If there is no scrolling to be done, leave the default event alone
- if (pos.min === pos.max) {
- return;
- }
- stopDefault( e, 1 );
- var orgEvent = e.originalEvent,
- delta = 0,
- isForward, nextItem;
- // Old school scrollwheel delta
- if ( orgEvent.wheelDelta ){ delta = orgEvent.wheelDelta / 120; }
- if ( orgEvent.detail ){ delta = -orgEvent.detail / 3; }
- isForward = delta < 0;
- if( itemNav ){
- nextItem = getIndex( ( centeredNav ? forceCenteredNav ? rel.activeItem : rel.centerItem : rel.firstItem ) + ( isForward ? o.scrollBy : -o.scrollBy ) );
- self[centeredNav ? forceCenteredNav ? 'activate' : 'toCenter' : 'toStart']( nextItem );
- } else {
- slide( pos.cur + ( isForward ? o.scrollBy : -o.scrollBy ) );
- }
- syncBars();
- });
- // Keyboard navigation
- o.keyboardNav && doc.bind('keydown.' + namespace, function(e){
- switch( e.keyCode || e.which ){
- // Left or Up
- case o.horizontal ? 37 : 38:
- stopDefault(e);
- o.keyboardNavByPages ? self.prevPage() : self.prev();
- break;
- // Right or Down
- case o.horizontal ? 39 : 40:
- stopDefault(e);
- o.keyboardNavByPages ? self.nextPage() : self.next();
- break;
- }
- });
- // Navigation buttons
- o.prev && $prevButton.bind('click.' + namespace, function(e){ stopDefault(e); self.prev(); });
- o.next && $nextButton.bind('click.' + namespace, function(e){ stopDefault(e); self.next(); });
- o.prevPage && $prevPageButton.bind('click.' + namespace, function(e){ stopDefault(e); self.prevPage(); });
- o.nextPage && $nextPageButton.bind('click.' + namespace, function(e){ stopDefault(e); self.nextPage(); });
- // Dragging navigation
- o.dragContent && $dragSource.bind('mousedown.' + namespace, function(e){
- // Ignore other than left mouse button
- if (e.which !== 1) {
- return;
- }
- stopDefault(e);
- var leftInit = e.clientX,
- topInit = e.clientY,
- posInit = pos.cur,
- start = +new Date(),
- srcEl = e.target,
- easeoff = 0,
- isInitialized = 0;
- // Add dragging class
- $slidee.addClass(o.draggedClass);
- // Stop potential ongoing animations
- stop();
- // Bind dragging events
- doc.bind(dragEvents, function(e){
- var released = e.type === 'mouseup',
- path = o.horizontal ? e.clientX - leftInit : e.clientY - topInit,
- newPos = posInit - path;
- // Initialized logic
- if( !isInitialized && Math.abs( path ) > 10 ){
- isInitialized = 1;
- // Trigger :dragStart event
- $slidee.trigger( pluginName + ':dragStart', [ pos ] );
- }
- // Limits & Elastic bounds
- if( newPos > pos.max ){
- newPos = o.elasticBounds ? pos.max + ( newPos - pos.max ) / 6 : pos.max;
- } else if( newPos < pos.min ){
- newPos = o.elasticBounds ? pos.min + ( newPos - pos.min ) / 6 : pos.min;
- }
- // Adjust newPos with easing when content has been released
- if( released ){
- // Cleanup
- doc.unbind(dragEvents);
- $slidee.removeClass(o.draggedClass);
- // How long was the dragging
- var time = +new Date() - start;
- // Calculate swing length
- var swing = time < 300 ? Math.ceil( Math.pow( 6 / ( time / 300 ) , 2 ) * Math.abs( path ) / 120 ) : 0;
- newPos += path > 0 ? -swing : swing;
- }
- // Drag only when isInitialized
- if (!isInitialized) {
- return;
- }
- stopDefault(e);
- // Stop default click action on source element
- if( srcEl ){
- $(srcEl).bind('click.' + namespace, function stopMe(e){
- stopDefault(e,true);
- $(this).unbind('click.' + namespace, stopMe);
- });
- srcEl = 0;
- }
- // Dragging state
- isDragging = !released;
- // Animage, synch bars, & align
- slide( newPos, released, released ? o.speed : 0 );
- syncBars( released ? null : 0 );
- // Trigger :drag event
- if (isInitialized) {
- $slidee.trigger(pluginName + ':drag', [pos]);
- }
- // Trigger :dragEnd event
- if (released) {
- $slidee.trigger(pluginName + ':dragEnd', [pos]);
- }
- });
- });
- // Scrollbar navigation
- $handle && o.dragHandle && $handle.bind('mousedown.' + namespace, function(e){
- // Ignore other than left mouse button
- if (e.which !== 1) {
- return;
- }
- stopDefault(e);
- var leftInit = e.clientX,
- topInit = e.clientY,
- posInit = hPos.cur,
- pathMin = -hPos.cur,
- pathMax = hPos.max - hPos.cur,
- nextDrag = 0;
- // Add dragging class
- $handle.addClass(o.draggedClass);
- // Stop potential ongoing animations
- stop();
- // Bind dragging events
- doc.bind(dragEvents, function(e){
- stopDefault(e);
- var released = e.type === 'mouseup',
- path = o.horizontal ? e.clientX - leftInit : e.clientY - topInit,
- newPos = posInit + path,
- time = +new Date();
- // Dragging state
- isDragging = !released;
- // Unbind events and remove classes when released
- if( released ){
- doc.unbind(dragEvents);
- $handle.removeClass(o.draggedClass);
- }
- // Execute only moves within path limits
- if( path < pathMax+5 && path > pathMin-5 || released ){
- // Fix overflows
- hPos.cur = newPos > hPos.max ? hPos.max : newPos < hPos.min ? hPos.min : newPos;
- // Move handle
- $handle.stop().css( o.horizontal ? { left: hPos.cur+'px' } : { top: hPos.cur+'px' } );
- // Trigger :dragStart event
- if (!nextDrag) {
- $handle.trigger(pluginName + ':dragStart', [hPos]);
- }
- // Trigger :drag event
- $handle.trigger( pluginName + ':drag', [ hPos ] );
- // Trigger :dragEnd event
- if (released) {
- $handle.trigger(pluginName + ':dragEnd', [hPos]);
- }
- // Throttle sync interval -> smoother animations, lower CPU load
- if( nextDrag <= time || released || path > pathMax || path < pathMin ){
- nextDrag = time + 50;
- // Synchronize slidee position
- slide( Math.round( hPos.cur / hPos.max * ( pos.max - pos.min ) ) + pos.min, released, released ? o.speed : 50 );
- }
- // Sync pagesbar
- syncPages();
- }
- });
- });
- }());
- }
- // jQuery plugin extension
- $.fn[pluginName] = function( options, returnInstance ){
- var method = false,
- methodArgs,
- instances = [];
- // Basic attributes logic
- if( typeof options !== 'undefined' && !$.isPlainObject( options ) ){
- method = options === false ? 'destroy' : options;
- methodArgs = arguments;
- Array.prototype.shift.call( methodArgs );
- }
- // Apply requested actions on all elements
- this.each(function( i, element ){
- // Plugin call with prevention against multiple instantiations
- var plugin = $.data( element, namespace );
- if( plugin && method ){
- // Call plugin method
- if( plugin[method] ){
- plugin[method].apply( plugin, methodArgs );
- }
- } else if( !plugin && !method ){
- // Create a new plugin object if it doesn't exist yet
- plugin = $.data( element, namespace, new Plugin( element, options ) );
- }
- // Push plugin to instances
- instances.push( plugin );
- });
- // Return chainable jQuery object, or plugin instance(s)
- return returnInstance && !method ? instances.length > 1 ? instances : instances[0] : this;
- };
- // Default options
- $.fn[pluginName].defaults = {
- // Sly direction
- horizontal: 0, // set to 1 to change the sly direction to horizontal
- // Navigation by items; when using this, `scrollBy` option scrolls by items, not pixels
- itemNav: 0, // enable type of item based navigation. when itemNav is enabled, items snap to frame edges or frame center
- // itemNav also enables "item activation" functionality and methods associated with it
- //
- // itemNav can be:
- // ------------------------------------------------------------------------------------
- // basic: items snap to edges (ideal if you don't care about "active item" functionality)
- // smart: same as basic, but activated item close to, or outside of the visible edge will be positioned to the opposite edge
- // centered: activated items are positioned to the center of visible frame if possible
- // forceCentered: active items are always centered & centered items are always active (scrolling & dragging end activates centered item)
- // Scrollbar
- scrollBar: null, // selector or DOM element for scrollbar container (scrollbar container should have one child element representing scrollbar handle)
- dynamicHandle: 1, // resizes scrollbar handle to represent the relation between hidden and visible content. set to "0" to leave it as big as CSS made it
- dragHandle: 1, // set to 0 to disable dragging of scrollbar handle with mouse
- minHandleSize: 50, // minimal height or width (depends on sly direction) of a handle in pixels
- // Pagesbar (when centerActive is enabled, every item is considered to be a page)
- pagesBar: null, // selector or DOM element for pages bar container
- pageBuilder: // function with `index` (starting at 0) as argument that returns an HTML for one item
- function( index ){
- return '<li>'+(index+1)+'</li>';
- },
- // Navigation buttons
- prev: null, // selector or DOM element for "previous item" button ; doesn't work when `itemsNav` is disabled
- next: null, // selector or DOM element for "next item" button ; doesn't work when `itemsNav` is disabled
- prevPage: null, // selector or DOM element for "previous page" button
- nextPage: null, // selector or DOM element for "next page" button
- // Automated cycling
- cycleBy: 0, // enable automatic cycling by 'items', or 'pages'
- cycleInterval: 5000, // number of milliseconds between cycles
- pauseOnHover: 1, // pause cycling when mouse hovers over frame
- startPaused: 0, // set to "1" to start in paused sate. cycling can be than resumed with "cycle" method
- // Mixed options
- scrollBy: 0, // how many pixels/items should one mouse scroll event go. leave "0" to disable mousewheel scrolling
- dragContent: 0, // set to 1 to enable navigation by dragging the content with your mouse
- elasticBounds: 0, // when dragging past limits, stretch them a little bit (like on spartphones)
- speed: 300, // animations speed
- easing: 'swing', // animations easing. build in jQuery options are "linear" and "swing". for more, install gsgd.co.uk/sandbox/jquery/easing/
- scrollSource: null, // selector or DOM element for catching the mouse wheel event for sly scrolling. default source is the frame
- dragSource: null, // selector or DOM element for catching the mouse dragging events. default source is the frame
- startAt: 0, // starting offset in pixels or items (depends on itemsNav option)
- keyboardNav: 0, // whether to allow navigation by keyboard arrows (left & right for horizontal, up & down for vertical)
- // NOTE! keyboard navigation will disable page scrolling with keyboard arrows in correspondent sly direction (vertical or horizontal)
- keyboardNavByPages: 0, // whether the keyboard should navigate by pages instead of items (useful when not using `itemsNav` navigation)
- // Classes
- draggedClass: 'dragged', // class that will be added to scrollbar handle, or content when they are being dragged
- activeClass: 'active', // class that will be added to the active item, or page
- disabledClass: 'disabled' // class that will be added to prev button when on start, or next button when on end
- };
- }(jQuery));
|