3 * https://github.com/Darsain/sly
5 * Licensed under the MIT license.
6 * http://www.opensource.org/licenses/MIT
9 /*jshint eqeqeq: true, noempty: true, strict: true, undef: true, expr: true, smarttabs: true, browser: true */
10 /*global jQuery:false */
12 ;(function($, undefined){
16 var pluginName = 'sly',
17 namespace = 'plugin_' + pluginName;
23 * @param {Element} frame DOM element of sly container
24 * @param {Object} o Object with plugin options
26 function Plugin( frame, o ){
33 $slidee = $frame.children().eq(0),
42 // Scrollbar variables
43 $sb = $(o.scrollBar).eq(0),
44 $handle = $sb.length ? $sb.children().eq(0) : 0,
58 // Navigation type booleans
59 basicNav = o.itemNav === 'basic',
60 smartNav = o.itemNav === 'smart',
61 forceCenteredNav = o.itemNav === 'forceCentered',
62 centeredNav = o.itemNav === 'centered' || forceCenteredNav,
63 itemNav = basicNav || smartNav || centeredNav || forceCenteredNav,
77 $scrollSource = o.scrollSource ? $( o.scrollSource ) : $frame,
78 $dragSource = o.dragSource ? $( o.dragSource ) : $frame,
79 $prevButton = $(o.prev),
80 $nextButton = $(o.next),
81 $prevPageButton = $(o.prevPage),
82 $nextPageButton = $(o.nextPage),
90 * (Re)Loading function
92 * Populates arrays, sets sizes, binds events, ...
96 var load = this.reload = function(){
99 var ignoredMargin = 0,
100 oldPos = $.extend({}, pos);
102 // Clear cycling timeout
103 clearTimeout( cycleIndex );
105 // Reset global variables
106 frameSize = o.horizontal ? $frame.width() : $frame.height();
107 sbSize = o.horizontal ? $sb.width() : $sb.height();
108 slideeSize = o.horizontal ? $slidee.outerWidth() : $slidee.outerHeight();
109 $items = $slidee.children();
113 // Set position limits & relatives
115 pos.max = slideeSize > frameSize ? slideeSize - frameSize : 0;
116 rel.items = $items.length;
118 // Sizes & offsets logic, but only when needed
121 var marginStart = getPx( $items, o.horizontal ? 'marginLeft' : 'marginTop' ),
122 marginEnd = getPx( $items.slice(-1), o.horizontal ? 'marginRight' : 'marginBottom' ),
124 paddingStart = getPx( $slidee, o.horizontal ? 'paddingLeft' : 'paddingTop' ),
125 paddingEnd = getPx( $slidee, o.horizontal ? 'paddingRight' : 'paddingBottom' ),
126 areFloated = $items.css('float') !== 'none';
128 // Update ignored margin
129 ignoredMargin = marginStart ? 0 : marginEnd;
134 // Iterate through items
135 $items.each(function(i,e){
139 itemSize = o.horizontal ? item.outerWidth(true) : item.outerHeight(true),
140 marginTop = getPx( item, 'marginTop' ),
141 marginBottom = getPx( item, 'marginBottom'),
142 marginLeft = getPx( item, 'marginLeft'),
143 marginRight = getPx( item, 'marginRight'),
146 offStart: slideeSize - ( !i || o.horizontal ? 0 : marginTop ),
147 offCenter: slideeSize - Math.round( frameSize / 2 - itemSize / 2 ),
148 offEnd: slideeSize - frameSize + itemSize - ( marginStart ? 0 : marginRight ),
151 bottom: marginBottom,
157 // Account for centerOffset & slidee padding
159 centerOffset = -( forceCenteredNav ? Math.round( frameSize / 2 - itemSize / 2 ) : 0 ) + paddingStart;
160 slideeSize += paddingStart;
163 // Increment slidee size for size of the active element
164 slideeSize += itemSize;
166 // Try to account for vertical margin collapsing in vertical mode
167 // It's not bulletproof, but should work in 99% of cases
168 if( !o.horizontal && !areFloated ){
170 // Subtract smaller margin, but only when top margin is not 0, and this is not the first element
171 if( marginBottom && marginTop && i > 0 ){
172 slideeSize -= marginTop < marginBottom ? marginTop : marginBottom;
177 // Things to be done at last item
178 if( i === $items.length - 1 ){
179 slideeSize += paddingEnd;
182 // Add item object to items array
188 $slidee.css( o.horizontal ? { width: slideeSize+'px' } : { height: slideeSize+'px' } );
190 // Adjust slidee size for last margin
191 slideeSize -= ignoredMargin;
194 pos.min = centerOffset;
195 pos.max = forceCenteredNav ? items[items.length-1].offCenter : slideeSize > frameSize ? slideeSize - frameSize : 0;
197 // Fix overflowing activeItem
198 rel.activeItem >= items.length && self.activate( items.length-1 );
202 // Assign relative position indexes
208 // Stretch scrollbar handle to represent the visible area
209 handleSize = o.dynamicHandle ? Math.round( sbSize * frameSize / slideeSize ) : o.horizontal ? $handle.width() : $handle.height();
210 handleSize = handleSize > sbSize ? sbSize : handleSize;
211 handleSize = handleSize < o.minHandleSize ? o.minHandleSize : handleSize;
212 hPos.max = sbSize - handleSize;
215 $handle.css( o.horizontal ? { width: handleSize+'px' } : { height: handleSize+'px' } );
224 // Populate pages array
225 if( forceCenteredNav ){
226 pages = $.map( items, function( o ){ return o.offCenter; } );
228 while( tempPagePos - frameSize < pos.max ){
230 var pagePos = tempPagePos > pos.max ? pos.max : tempPagePos;
232 pages.push( pagePos );
233 tempPagePos += frameSize;
235 // When item navigation, and last page is smaller than half of the last item size,
236 // adjust the last page position to pos.max and break the loop
237 if( tempPagePos > pos.max && itemNav && pos.max - pagePos < ( items[items.length-1].size - ignoredMargin ) / 2 ){
239 pages[pages.length-1] = pos.max;
250 for( var i = 0; i < pages.length; i++ ){
251 pagesHtml += o.pageBuilder( pageIndex++ );
254 // Bind page navigation, append to pagesbar, and save to $pages variable
255 $pages = $(pagesHtml).bind('click.' + namespace, function(){
257 self.activatePage( $pages.index(this) );
259 }).appendTo( $pb.empty() );
263 // Bind activating to items
264 $items.unbind('.' + namespace).bind('mouseup.' + namespace, function(e){
266 e.which === 1 && !isDragging && self.activate( this );
271 pos.cur < pos.min && slide( pos.min );
272 pos.cur > pos.max && slide( pos.max );
274 // Extend relative variables object with some useful info
275 rel.pages = pages.length;
276 rel.slideeSize = slideeSize;
277 rel.frameSize = frameSize;
279 rel.handleSize = handleSize;
281 // Synchronize scrollbar
288 if( itemNav && o.cycleBy ){
290 var pauseEvents = 'mouseenter.' + namespace + ' mouseleave.' + namespace;
293 o.pauseOnHover && $frame.unbind(pauseEvents).bind(pauseEvents, function(e){
295 !cycleIsPaused && self.cycle( e.type === 'mouseenter', 1 );
300 self.cycle( o.startPaused );
304 // Trigger :load event
305 $frame.trigger( pluginName + ':load', [ $.extend({}, pos, { old: oldPos }), $items, rel ] );
315 * @param {Int} newPos New slidee position in relation to frame
316 * @param {Bool} align Whetner to Align elements to the frame border
317 * @param {Int} speed Animation speed in milliseconds
319 function slide( newPos, align, speed ){
321 speed = isNumber( speed ) ? speed : o.speed;
324 if( align && itemNav ){
326 var tempRel = getRelatives( newPos );
330 newPos = items[tempRel.centerItem].offCenter;
331 self[ forceCenteredNav ? 'activate' : 'toCenter']( tempRel.centerItem, 1 );
333 } else if( newPos > pos.min && newPos < pos.max ){
335 newPos = items[tempRel.firstItem].offStart;
341 // Fix overflowing position
342 if( !isDragging || !o.elasticBounds ){
343 newPos = newPos < pos.min ? pos.min : newPos;
344 newPos = newPos > pos.max ? pos.max : newPos;
347 // Stop if position has not changed
348 if( newPos === pos.cur ) {
354 // Reassign relative indexes
357 // Add disabled classes
360 // halt ongoing animations
363 // Trigger :move event
364 !isDragging && $frame.trigger( pluginName + ':move', [ pos, $items, rel ] );
366 var newProp = o.horizontal ? { left: -pos.cur+'px' } : { top: -pos.cur+'px' };
371 $slidee.animate( newProp, speed, isDragging ? 'swing' : o.easing, function(e){
373 // Trigger :moveEnd event
374 !isDragging && $frame.trigger( pluginName + ':moveEnd', [ pos, $items, rel ] );
380 $slidee.css( newProp );
382 // Trigger :moveEnd event
383 !isDragging && $frame.trigger( pluginName + ':moveEnd', [ pos, $items, rel ] );
391 * Synchronizes scrollbar & pagesbar positions with the slidee
395 * @param {Int} speed Animation speed for scrollbar synchronization
397 function syncBars( speed ){
399 // Scrollbar synchronization
402 hPos.cur = Math.round( ( pos.cur - pos.min ) / ( pos.max - pos.min ) * hPos.max );
403 hPos.cur = hPos.cur < hPos.min ? hPos.min : hPos.cur > hPos.max ? hPos.max : hPos.cur;
404 $handle.stop().animate( o.horizontal ? { left: hPos.cur+'px' } : { top: hPos.cur+'px' }, isNumber(speed) ? speed : o.speed, o.easing );
408 // Pagesbar synchronization
415 * Synchronizes pagesbar
419 function syncPages(){
421 if (!$pages.length) {
426 $pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
432 * Activate previous item
436 this.prev = function(){
438 self.activate( rel.activeItem - 1 );
448 this.next = function(){
450 self.activate( rel.activeItem + 1 );
456 * Activate previous page
460 this.prevPage = function(){
462 self.activatePage( rel.activePage - 1 );
472 this.nextPage = function(){
474 self.activatePage( rel.activePage + 1 );
480 * Stop ongoing animations
486 $slidee.add($handle).stop();
492 * Animate element or the whole slidee to the start of the frame
496 * @param {Element|Int} el DOM element, or index of element in items array
498 this.toStart = function( el ){
502 var index = getIndex( el );
504 if( el === undefined ){
508 } else if( index !== -1 ){
510 // You can't align items to the start of the frame when centeredNav is enabled
515 index !== -1 && slide( items[index].offStart );
521 if( el === undefined ){
527 var $el = $slidee.find(el).eq(0);
531 var offset = o.horizontal ? $el.offset().left - $slidee.offset().left : $el.offset().top - $slidee.offset().top;
547 * Animate element or the whole slidee to the end of the frame
551 * @param {Element|Int} el DOM element, or index of element in items array
553 this.toEnd = function( el ){
557 var index = getIndex( el );
559 if( el === undefined ){
563 } else if( index !== -1 ){
565 // You can't align items to the end of the frame when centeredNav is enabled
570 slide( items[index].offEnd );
576 if( el === undefined ){
582 var $el = $slidee.find(el).eq(0);
586 var offset = o.horizontal ? $el.offset().left - $slidee.offset().left : $el.offset().top - $slidee.offset().top;
588 slide( offset - frameSize + $el[o.horizontal ? 'outerWidth' : 'outerHeight']() );
602 * Animate element or the whole slidee to the center of the frame
606 * @param {Element|Int} el DOM element, or index of element in items array
608 this.toCenter = function( el ){
612 var index = getIndex( el );
614 if( el === undefined ){
616 slide( Math.round( pos.max / 2 + pos.min / 2 ), 1 );
618 } else if( index !== -1 ){
620 slide( items[index].offCenter );
621 forceCenteredNav && self.activate( index, 1 );
627 if( el === undefined ){
629 slide( Math.round( pos.max / 2 ) );
633 var $el = $slidee.find(el).eq(0);
637 var offset = o.horizontal ? $el.offset().left - $slidee.offset().left : $el.offset().top - $slidee.offset().top;
639 slide( offset - frameSize / 2 + $el[o.horizontal ? 'outerWidth' : 'outerHeight']() / 2 );
654 * Get an index of the element
658 * @param {Element|Int} el DOM element, or index of element in items array
660 function getIndex( el ){
662 return isNumber(el) ? el < 0 ? 0 : el > items.length-1 ? items.length-1 : el : el === undefined ? -1 : $items.index( el );
668 * Parse style to pixels
672 * @param {Object} $item jQuery object with element
673 * @param {Property} property Property to get the pixels from
675 function getPx( $item, property ){
677 return parseInt( $item.css( property ), 10 );
683 * Activates an element
685 * Element is positioned to one of the sides of the frame, based on it's current position.
686 * If the element is close to the right frame border, it will be animated to the start of the left border,
687 * and vice versa. This helps user to navigate through the elements only by clicking on them, without
688 * the need for navigation buttons, scrolling, or keyboard arrows.
692 * @param {Element|Int} el DOM element, or index of element in items array
693 * @param {Bool} noReposition Activate item without repositioning it
695 this.activate = function( el, noReposition ){
697 if (!itemNav || el === undefined) {
701 var index = getIndex( el ),
702 oldActive = rel.activeItem;
704 // Update activeItem index
705 rel.activeItem = index;
707 // Add active class to the active element
708 $items.removeClass(o.activeClass).eq(index).addClass(o.activeClass);
710 // Trigget :active event if a new element is being activated
711 index !== oldActive && $items.eq( index ).trigger( pluginName + ':active', [ $items, rel ] );
715 // When centeredNav is enabled, center the element
718 self.toCenter( index );
720 // Otherwise determine where to position the element
721 } else if( smartNav ) {
723 // If activated element is currently on the far right side of the frame, assume that
724 // user is moving forward and animate it to the start of the visible frame, and vice versa
725 if (index >= rel.lastItem) {
727 } else if (index <= rel.firstItem) {
735 // Add disabled classes
746 * @param {Int} index Page index, starting from 0
748 this.activatePage = function( index ){
751 index = index < 0 ? 0 : index >= pages.length ? pages.length-1 : index;
752 slide( pages[index], itemNav );
760 * Return relative positions of items based on their location within visible frame
764 * @param {Int} sPos Position of slidee
766 function getRelatives( sPos ){
769 centerOffset = forceCenteredNav ? 0 : frameSize / 2;
771 // Determine active page
772 for( var p = 0; p < pages.length; p++ ){
774 if( sPos >= pos.max || p === pages.length - 1 ){
775 newRel.activePage = pages.length - 1;
779 if( sPos <= pages[p] + centerOffset ){
780 newRel.activePage = p;
786 // Relative item indexes
794 for( var i=0; i < items.length; i++ ){
797 if (first === false && sPos <= items[i].offStart) {
802 if (center === false && sPos - items[i].size / 2 <= items[i].offCenter) {
807 if (i === items.length - 1 || (last === false && sPos < items[i + 1].offEnd)) {
811 // Terminate if all are assigned
812 if (last !== false) {
818 // Safe assignment, just to be sure the false won't be returned
819 newRel.firstItem = isNumber( first ) ? first : 0;
820 newRel.centerItem = isNumber( center ) ? center : newRel.firstItem;
821 newRel.lastItem = isNumber( last ) ? last : newRel.centerItem;
831 * Assign element indexes to the relative positions
835 function assignRelatives(){
837 $.extend( rel, getRelatives( pos.cur ) );
843 * Disable buttons when needed
845 * Adds disabledClass, and when the button is <button> or <input>,
846 * activates :disabled state
850 function disableButtons(){
855 var isFirstItem = rel.activeItem === 0,
856 isLastItem = rel.activeItem >= items.length-1;
858 if( $prevButton.is('button,input') ){
859 $prevButton.prop('disabled', isFirstItem);
862 if( $nextButton.is('button,input') ){
863 $nextButton.prop('disabled', isLastItem);
866 $prevButton[ isFirstItem ? 'removeClass' : 'addClass'](o.disabledClass);
867 $nextButton[ isLastItem ? 'removeClass' : 'addClass'](o.disabledClass);
874 var isStart = pos.cur <= pos.min,
875 isEnd = pos.cur >= pos.max;
877 if( $prevPageButton.is('button,input') ){
878 $prevPageButton.prop('disabled', isStart);
881 if( $nextPageButton.is('button,input') ){
882 $nextPageButton.prop('disabled', isEnd);
885 $prevPageButton[ isStart ? 'removeClass' : 'addClass'](o.disabledClass);
886 $nextPageButton[ isEnd ? 'removeClass' : 'addClass'](o.disabledClass);
898 * @param {Bool} pause Pass true to pause cycling
899 * @param {Bool} soft Soft pause intended for pauseOnHover - won't set cycleIsPaused variable to true
901 this.cycle = function( pause, soft ){
903 if (!itemNav || !o.cycleBy) {
908 cycleIsPaused = !!pause;
915 cycleIndex = clearTimeout( cycleIndex );
917 // Trigger :cyclePause event
918 $frame.trigger( pluginName + ':cyclePause', [ pos, $items, rel ] );
924 // Don't initiate more than one cycle
929 // Trigger :cycleStart event
930 $frame.trigger( pluginName + ':cycleStart', [ pos, $items, rel ] );
935 if( o.cycleInterval === 0 ){
939 cycleIndex = setTimeout( function(){
945 var nextItem = rel.activeItem >= items.length-1 ? 0 : rel.activeItem + 1;
946 self.activate( nextItem );
950 var nextPage = rel.activePage >= pages.length-1 ? 0 : rel.activePage + 1;
951 self.activatePage( nextPage );
957 // Trigger :cycle event
958 $frame.trigger( pluginName + ':cycle', [ pos, $items, rel ] );
963 }, o.cycleInterval );
973 * Crossbrowser reliable way to stop default event action
977 * @param {Event} e Event object
978 * @param {Bool} noBubbles Cancel event bubbling
980 function stopDefault( e, noBubbles ){
982 var evt = e || window.event;
983 evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;
984 noBubbles && evt.stopPropagation ? evt.stopPropagation() : evt.cancelBubble = true;
990 * Updates a signle or multiple option values
992 * @param {Mixed} property Option property name that should be updated, or object with options that will extend the current one
993 * @param {Mixed} value Option property value
997 this.set = function( property, value ){
999 if( $.isPlainObject(property) ){
1001 o = $.extend({}, o, property);
1003 } else if( typeof property === 'string' ) {
1005 o[property] = value;
1013 * Destroys plugin instance and everything it created
1017 this.destroy = function(){
1019 // Unbind all events
1020 $frame.add(document).add($slidee).add($items).add($scrollSource).add($handle)
1021 .add($prevButton).add($nextButton).add($prevPageButton).add($nextPageButton)
1022 .unbind('.' + namespace);
1024 // Reset some styles
1025 $slidee.add($handle).css( o.horizontal ? { left: 0 } : { top: 0 } );
1027 // Remove plugin classes
1028 $prevButton.add($nextButton).removeClass(o.disabledClass);
1030 // Remove page items
1033 // Remove plugin from element data storage
1034 $.removeData(frame, namespace);
1040 * Check if variable is a number
1042 * @param {Mixed} n Any type of variable
1046 function isNumber( n ) {
1047 return !isNaN(parseFloat(n)) && isFinite(n);
1054 var doc = $(document),
1055 dragEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
1058 o = $.extend( {}, $.fn[pluginName].defaults, o );
1060 // Set required styles to elements
1061 $frame.css({ overflow: 'hidden' }).css('position') === 'static' && $frame.css({ position: 'relative' });
1062 $sb.css('position') === 'static' && $sb.css({ position: 'relative' });
1063 $slidee.add($handle).css( o.horizontal ? { position: 'absolute', left: 0 } : { position: 'absolute', top: 0 } );
1068 // Activate requested position
1069 itemNav ? self.activate( o.startAt ) : slide( o.startAt );
1071 // Sync scrollbar & pages
1074 // Scrolling navigation
1075 o.scrollBy && $scrollSource.bind('DOMMouseScroll.' + namespace + ' mousewheel.' + namespace, function(e){
1077 // If there is no scrolling to be done, leave the default event alone
1078 if (pos.min === pos.max) {
1082 stopDefault( e, 1 );
1084 var orgEvent = e.originalEvent,
1086 isForward, nextItem;
1088 // Old school scrollwheel delta
1089 if ( orgEvent.wheelDelta ){ delta = orgEvent.wheelDelta / 120; }
1090 if ( orgEvent.detail ){ delta = -orgEvent.detail / 3; }
1092 isForward = delta < 0;
1096 nextItem = getIndex( ( centeredNav ? forceCenteredNav ? rel.activeItem : rel.centerItem : rel.firstItem ) + ( isForward ? o.scrollBy : -o.scrollBy ) );
1098 self[centeredNav ? forceCenteredNav ? 'activate' : 'toCenter' : 'toStart']( nextItem );
1102 slide( pos.cur + ( isForward ? o.scrollBy : -o.scrollBy ) );
1110 // Keyboard navigation
1111 o.keyboardNav && doc.bind('keydown.' + namespace, function(e){
1113 switch( e.keyCode || e.which ){
1116 case o.horizontal ? 37 : 38:
1119 o.keyboardNavByPages ? self.prevPage() : self.prev();
1124 case o.horizontal ? 39 : 40:
1127 o.keyboardNavByPages ? self.nextPage() : self.next();
1135 // Navigation buttons
1136 o.prev && $prevButton.bind('click.' + namespace, function(e){ stopDefault(e); self.prev(); });
1137 o.next && $nextButton.bind('click.' + namespace, function(e){ stopDefault(e); self.next(); });
1138 o.prevPage && $prevPageButton.bind('click.' + namespace, function(e){ stopDefault(e); self.prevPage(); });
1139 o.nextPage && $nextPageButton.bind('click.' + namespace, function(e){ stopDefault(e); self.nextPage(); });
1141 // Dragging navigation
1142 o.dragContent && $dragSource.bind('mousedown.' + namespace, function(e){
1144 // Ignore other than left mouse button
1145 if (e.which !== 1) {
1151 var leftInit = e.clientX,
1152 topInit = e.clientY,
1154 start = +new Date(),
1159 // Add dragging class
1160 $slidee.addClass(o.draggedClass);
1162 // Stop potential ongoing animations
1165 // Bind dragging events
1166 doc.bind(dragEvents, function(e){
1168 var released = e.type === 'mouseup',
1169 path = o.horizontal ? e.clientX - leftInit : e.clientY - topInit,
1170 newPos = posInit - path;
1172 // Initialized logic
1173 if( !isInitialized && Math.abs( path ) > 10 ){
1177 // Trigger :dragStart event
1178 $slidee.trigger( pluginName + ':dragStart', [ pos ] );
1182 // Limits & Elastic bounds
1183 if( newPos > pos.max ){
1184 newPos = o.elasticBounds ? pos.max + ( newPos - pos.max ) / 6 : pos.max;
1185 } else if( newPos < pos.min ){
1186 newPos = o.elasticBounds ? pos.min + ( newPos - pos.min ) / 6 : pos.min;
1189 // Adjust newPos with easing when content has been released
1193 doc.unbind(dragEvents);
1194 $slidee.removeClass(o.draggedClass);
1196 // How long was the dragging
1197 var time = +new Date() - start;
1199 // Calculate swing length
1200 var swing = time < 300 ? Math.ceil( Math.pow( 6 / ( time / 300 ) , 2 ) * Math.abs( path ) / 120 ) : 0;
1201 newPos += path > 0 ? -swing : swing;
1205 // Drag only when isInitialized
1206 if (!isInitialized) {
1212 // Stop default click action on source element
1215 $(srcEl).bind('click.' + namespace, function stopMe(e){
1217 stopDefault(e,true);
1218 $(this).unbind('click.' + namespace, stopMe);
1227 isDragging = !released;
1229 // Animage, synch bars, & align
1230 slide( newPos, released, released ? o.speed : 0 );
1231 syncBars( released ? null : 0 );
1233 // Trigger :drag event
1234 if (isInitialized) {
1235 $slidee.trigger(pluginName + ':drag', [pos]);
1238 // Trigger :dragEnd event
1240 $slidee.trigger(pluginName + ':dragEnd', [pos]);
1247 // Scrollbar navigation
1248 $handle && o.dragHandle && $handle.bind('mousedown.' + namespace, function(e){
1250 // Ignore other than left mouse button
1251 if (e.which !== 1) {
1257 var leftInit = e.clientX,
1258 topInit = e.clientY,
1260 pathMin = -hPos.cur,
1261 pathMax = hPos.max - hPos.cur,
1264 // Add dragging class
1265 $handle.addClass(o.draggedClass);
1267 // Stop potential ongoing animations
1270 // Bind dragging events
1271 doc.bind(dragEvents, function(e){
1275 var released = e.type === 'mouseup',
1276 path = o.horizontal ? e.clientX - leftInit : e.clientY - topInit,
1277 newPos = posInit + path,
1281 isDragging = !released;
1283 // Unbind events and remove classes when released
1286 doc.unbind(dragEvents);
1287 $handle.removeClass(o.draggedClass);
1291 // Execute only moves within path limits
1292 if( path < pathMax+5 && path > pathMin-5 || released ){
1295 hPos.cur = newPos > hPos.max ? hPos.max : newPos < hPos.min ? hPos.min : newPos;
1298 $handle.stop().css( o.horizontal ? { left: hPos.cur+'px' } : { top: hPos.cur+'px' } );
1300 // Trigger :dragStart event
1302 $handle.trigger(pluginName + ':dragStart', [hPos]);
1305 // Trigger :drag event
1306 $handle.trigger( pluginName + ':drag', [ hPos ] );
1308 // Trigger :dragEnd event
1310 $handle.trigger(pluginName + ':dragEnd', [hPos]);
1313 // Throttle sync interval -> smoother animations, lower CPU load
1314 if( nextDrag <= time || released || path > pathMax || path < pathMin ){
1316 nextDrag = time + 50;
1318 // Synchronize slidee position
1319 slide( Math.round( hPos.cur / hPos.max * ( pos.max - pos.min ) ) + pos.min, released, released ? o.speed : 50 );
1337 // jQuery plugin extension
1338 $.fn[pluginName] = function( options, returnInstance ){
1344 // Basic attributes logic
1345 if( typeof options !== 'undefined' && !$.isPlainObject( options ) ){
1346 method = options === false ? 'destroy' : options;
1347 methodArgs = arguments;
1348 Array.prototype.shift.call( methodArgs );
1351 // Apply requested actions on all elements
1352 this.each(function( i, element ){
1354 // Plugin call with prevention against multiple instantiations
1355 var plugin = $.data( element, namespace );
1357 if( plugin && method ){
1359 // Call plugin method
1360 if( plugin[method] ){
1362 plugin[method].apply( plugin, methodArgs );
1366 } else if( !plugin && !method ){
1368 // Create a new plugin object if it doesn't exist yet
1369 plugin = $.data( element, namespace, new Plugin( element, options ) );
1373 // Push plugin to instances
1374 instances.push( plugin );
1378 // Return chainable jQuery object, or plugin instance(s)
1379 return returnInstance && !method ? instances.length > 1 ? instances : instances[0] : this;
1385 $.fn[pluginName].defaults = {
1388 horizontal: 0, // set to 1 to change the sly direction to horizontal
1390 // Navigation by items; when using this, `scrollBy` option scrolls by items, not pixels
1391 itemNav: 0, // enable type of item based navigation. when itemNav is enabled, items snap to frame edges or frame center
1392 // itemNav also enables "item activation" functionality and methods associated with it
1395 // ------------------------------------------------------------------------------------
1396 // basic: items snap to edges (ideal if you don't care about "active item" functionality)
1397 // smart: same as basic, but activated item close to, or outside of the visible edge will be positioned to the opposite edge
1398 // centered: activated items are positioned to the center of visible frame if possible
1399 // forceCentered: active items are always centered & centered items are always active (scrolling & dragging end activates centered item)
1402 scrollBar: null, // selector or DOM element for scrollbar container (scrollbar container should have one child element representing scrollbar handle)
1403 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
1404 dragHandle: 1, // set to 0 to disable dragging of scrollbar handle with mouse
1405 minHandleSize: 50, // minimal height or width (depends on sly direction) of a handle in pixels
1407 // Pagesbar (when centerActive is enabled, every item is considered to be a page)
1408 pagesBar: null, // selector or DOM element for pages bar container
1409 pageBuilder: // function with `index` (starting at 0) as argument that returns an HTML for one item
1411 return '<li>'+(index+1)+'</li>';
1414 // Navigation buttons
1415 prev: null, // selector or DOM element for "previous item" button ; doesn't work when `itemsNav` is disabled
1416 next: null, // selector or DOM element for "next item" button ; doesn't work when `itemsNav` is disabled
1417 prevPage: null, // selector or DOM element for "previous page" button
1418 nextPage: null, // selector or DOM element for "next page" button
1420 // Automated cycling
1421 cycleBy: 0, // enable automatic cycling by 'items', or 'pages'
1422 cycleInterval: 5000, // number of milliseconds between cycles
1423 pauseOnHover: 1, // pause cycling when mouse hovers over frame
1424 startPaused: 0, // set to "1" to start in paused sate. cycling can be than resumed with "cycle" method
1427 scrollBy: 0, // how many pixels/items should one mouse scroll event go. leave "0" to disable mousewheel scrolling
1428 dragContent: 0, // set to 1 to enable navigation by dragging the content with your mouse
1429 elasticBounds: 0, // when dragging past limits, stretch them a little bit (like on spartphones)
1430 speed: 300, // animations speed
1431 easing: 'swing', // animations easing. build in jQuery options are "linear" and "swing". for more, install gsgd.co.uk/sandbox/jquery/easing/
1432 scrollSource: null, // selector or DOM element for catching the mouse wheel event for sly scrolling. default source is the frame
1433 dragSource: null, // selector or DOM element for catching the mouse dragging events. default source is the frame
1434 startAt: 0, // starting offset in pixels or items (depends on itemsNav option)
1435 keyboardNav: 0, // whether to allow navigation by keyboard arrows (left & right for horizontal, up & down for vertical)
1436 // NOTE! keyboard navigation will disable page scrolling with keyboard arrows in correspondent sly direction (vertical or horizontal)
1437 keyboardNavByPages: 0, // whether the keyboard should navigate by pages instead of items (useful when not using `itemsNav` navigation)
1440 draggedClass: 'dragged', // class that will be added to scrollbar handle, or content when they are being dragged
1441 activeClass: 'active', // class that will be added to the active item, or page
1442 disabledClass: 'disabled' // class that will be added to prev button when on start, or next button when on end