/
jquery.serialScroll.js
243 lines (210 loc) · 7.03 KB
/
jquery.serialScroll.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*!
* jQuery.SerialScroll
* Copyright (c) 2007-2010 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
* Dual licensed under MIT and GPL.
* Date: 05/31/2010
*
* @projectDescription Animated scrolling of series.
* @author Ariel Flesler
* @version 1.2.3b
*
* @id jQuery.serialScroll
* @id jQuery.fn.serialScroll
* @param {Object} settings Hash of settings, it is passed in to jQuery.ScrollTo, none is required.
* @return {jQuery} Returns the same jQuery object, for chaining.
*
* @link {http://flesler.blogspot.com/2008/02/jqueryserialscroll.html Homepage}
*
* Notes:
* - The plugin requires jQuery.ScrollTo.
* - The hash of settings, is passed to jQuery.ScrollTo, so its settings can be used as well.
*/
;(function( $ ){
var NAMESPACE = '.serialScroll';
var $serialScroll = $.serialScroll = function( settings ){
return $(window).serialScroll( settings );
};
// Many of these defaults, belong to jQuery.ScrollTo, check it's demo for an example of each option.
// @link {http://demos.flesler.com/jquery/scrollTo/ ScrollTo's Demo}
$serialScroll.defaults = {// the defaults are public and can be overriden.
duration:1000, // how long to animate.
axis:'x', // which of top and left should be scrolled
event:'click', // on which event to react.
start:0, // first element (zero-based index)
step:1, // how many elements to scroll on each action
lock:true,// ignore events if already animating
cycle:true, // cycle endlessly ( constant velocity )
constant:true // use contant speed ?
/*
navigation:null,// if specified, it's a selector to a collection of items to navigate the container
target:window, // if specified, it's a selector to the element to be scrolled.
interval:0, // it's the number of milliseconds to automatically go to the next
lazy:false,// go find the elements each time (allows AJAX or JS content, or reordering)
stop:false, // stop any previous animations to avoid queueing
force:false,// force the scroll to the first element on start ?
jump: false,// if true, when the event is triggered on an element, the pane scrolls to it
items:null, // selector to the items (relative to the matched elements)
prev:null, // selector to the 'prev' button
next:null, // selector to the 'next' button
onBefore: function(){}, // function called before scrolling, if it returns false, the event is ignored
exclude:0 // exclude the last x elements, so we cannot scroll past the end
*/
};
$.fn.serialScroll = function( options ){
return this.each(function(){
var
settings = $.extend( {}, $serialScroll.defaults, options ),
// this one is just to get shorter code when compressed
event = settings.event,
// ditto
step = settings.step,
// ditto
lazy = settings.lazy,
// if a target is specified, then everything's relative to 'this'.
context = settings.target ? this : document,
// the element to be scrolled (will carry all the events)
$pane = $(settings.target || this, context),
// will be reused, save it into a variable
pane = $pane[0],
// will hold a lazy list of elements
items = settings.items,
// index of the currently selected item
active = settings.start,
// boolean, do automatic scrolling or not
auto = settings.interval,
// save it now to make the code shorter
nav = settings.navigation,
// holds the interval id
timer;
// If no match, just ignore
if(!pane)
return;
// if not lazy, save the items now
if( !lazy )
items = getItems();
// generate an initial call
if( settings.force || auto )
jump( {}, active );
// Button binding, optional
$(settings.prev||[], context).bind( event, -step, move );
$(settings.next||[], context).bind( event, step, move );
// Custom events bound to the container
if( !pane.ssbound )
$pane
// You can trigger with just 'prev'
.bind('prev'+NAMESPACE, -step, move )
// f.e: $(container).trigger('next');
.bind('next'+NAMESPACE, step, move )
// f.e: $(container).trigger('goto', 4 );
.bind('goto'+NAMESPACE, jump );
if( auto )
$pane
.bind('start'+NAMESPACE, function(e){
if( !auto ){
clear();
auto = true;
next();
}
})
.bind('stop'+NAMESPACE, function(){
clear();
auto = false;
});
// Let serialScroll know that the index changed externally
$pane.bind('notify'+NAMESPACE, function(e, elem){
var i = index(elem);
if( i > -1 )
active = i;
});
// Avoid many bindings
pane.ssbound = true;
// Can't use jump if using lazy items and a non-bubbling event
if( settings.jump )
(lazy ? $pane : getItems()).bind( event, function( e ){
jump( e, index(e.target) );
});
if( nav )
nav = $(nav, context).bind(event, function( e ){
e.data = Math.round(getItems().length / nav.length) * nav.index(this);
jump( e, this );
});
function move( e ){
e.data += active;
jump( e, this );
};
function jump( e, pos ){
if( isNaN(pos) )
pos = e.data;
var n,
// Is a real event triggering ?
real = e.type,
// Handle a possible exclude
$items = settings.exclude ? getItems().slice(0,-settings.exclude) : getItems(),
limit = $items.length - 1,
elem = $items[pos],
duration = settings.duration;
if( real )
e.preventDefault();
if( auto ){
// clear any possible automatic scrolling.
clear();
timer = setTimeout( next, settings.interval );
}
// exceeded the limits
if( !elem ){
n = pos < 0 ? 0 : limit;
// we exceeded for the first time
if( active !== n )
pos = n;
// this is a bad case
else if( !settings.cycle )
return;
// invert, go to the other side
else
pos = limit - n;
elem = $items[pos];
}
// no animations while busy
if( !elem || settings.lock && $pane._scrollable().is(':animated') ||
real && settings.onBefore &&
// Allow implementors to cancel scrolling
settings.onBefore(e, elem, $pane, getItems(), pos) === false ) return;
if( settings.stop )
// remove all running animations
$pane._scrollable().stop(true);
if( settings.constant )
// keep constant velocity
duration = Math.abs(duration/step * (active - pos));
$pane.scrollTo( elem, duration, settings );
// in case serialScroll was called on this elemement more than once.
trigger('notify', pos);
};
function next(){
trigger('next');
};
function clear(){
clearTimeout(timer);
};
function getItems(){
return $( items, pane );
};
// I'll use the namespace to avoid conflicts
function trigger(event){
$pane.trigger(
event+NAMESPACE,
[].slice.call(arguments,1)
);
}
function index( elem ){
// Already a number
if( !isNaN(elem) )
return elem;
var $items = getItems(), i;
// See if it matches or one of its ancestors
while(( i = $items.index(elem)) === -1 && elem !== pane )
elem = elem.parentNode;
return i;
};
});
};
})( jQuery );