1 | /*! Copyright (c) 2011 Piotr Rochala (http://rocha.la) |
---|
2 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) |
---|
3 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. |
---|
4 | * |
---|
5 | * Version: 1.2.0 |
---|
6 | * |
---|
7 | */ |
---|
8 | (function($) { |
---|
9 | |
---|
10 | jQuery.fn.extend({ |
---|
11 | slimScroll: function(options) { |
---|
12 | |
---|
13 | var defaults = { |
---|
14 | |
---|
15 | // width in pixels of the visible scroll area |
---|
16 | width : 'auto', |
---|
17 | |
---|
18 | // height in pixels of the visible scroll area |
---|
19 | height : '250px', |
---|
20 | |
---|
21 | // width in pixels of the scrollbar and rail |
---|
22 | size : '7px', |
---|
23 | |
---|
24 | // scrollbar color, accepts any hex/color value |
---|
25 | color: '#000', |
---|
26 | |
---|
27 | // scrollbar position - left/right |
---|
28 | position : 'right', |
---|
29 | |
---|
30 | // distance in pixels between the side edge and the scrollbar |
---|
31 | distance : '1px', |
---|
32 | |
---|
33 | // default scroll position on load - top / bottom / $('selector') |
---|
34 | start : 'top', |
---|
35 | |
---|
36 | // sets scrollbar opacity |
---|
37 | opacity : .4, |
---|
38 | |
---|
39 | // enables always-on mode for the scrollbar |
---|
40 | alwaysVisible : false, |
---|
41 | |
---|
42 | // check if we should hide the scrollbar when user is hovering over |
---|
43 | disableFadeOut: false, |
---|
44 | |
---|
45 | // sets visibility of the rail |
---|
46 | railVisible : false, |
---|
47 | |
---|
48 | // sets rail color |
---|
49 | railColor : '#333', |
---|
50 | |
---|
51 | // sets rail opacity |
---|
52 | railOpacity : .2, |
---|
53 | |
---|
54 | // whether we should use jQuery UI Draggable to enable bar dragging |
---|
55 | railDraggable : true, |
---|
56 | |
---|
57 | // defautlt CSS class of the slimscroll rail |
---|
58 | railClass : 'slimScrollRail', |
---|
59 | |
---|
60 | // defautlt CSS class of the slimscroll bar |
---|
61 | barClass : 'slimScrollBar', |
---|
62 | |
---|
63 | // defautlt CSS class of the slimscroll wrapper |
---|
64 | wrapperClass : 'slimScrollDiv', |
---|
65 | |
---|
66 | // check if mousewheel should scroll the window if we reach top/bottom |
---|
67 | allowPageScroll : false, |
---|
68 | |
---|
69 | // scroll amount applied to each mouse wheel step |
---|
70 | wheelStep : 20, |
---|
71 | |
---|
72 | // scroll amount applied when user is using gestures |
---|
73 | touchScrollStep : 200, |
---|
74 | |
---|
75 | // sets border radius |
---|
76 | borderRadius: '7px', |
---|
77 | |
---|
78 | // sets border radius of the rail |
---|
79 | railBorderRadius: '7px' |
---|
80 | }; |
---|
81 | |
---|
82 | var o = $.extend(defaults, options); |
---|
83 | |
---|
84 | // do it for every element that matches selector |
---|
85 | this.each(function(){ |
---|
86 | |
---|
87 | var isOverPanel, isOverBar, isDragg, queueHide, touchDif, |
---|
88 | barHeight, percentScroll, lastScroll, |
---|
89 | divS = '<div></div>', |
---|
90 | minBarHeight = 30, |
---|
91 | releaseScroll = false; |
---|
92 | |
---|
93 | // used in event handlers and for better minification |
---|
94 | var me = $(this); |
---|
95 | |
---|
96 | // ensure we are not binding it again |
---|
97 | if (me.parent().hasClass(o.wrapperClass)) |
---|
98 | { |
---|
99 | // start from last bar position |
---|
100 | var offset = me.scrollTop(); |
---|
101 | |
---|
102 | // find bar and rail |
---|
103 | bar = me.parent().find('.' + o.barClass); |
---|
104 | rail = me.parent().find('.' + o.railClass); |
---|
105 | |
---|
106 | getBarHeight(); |
---|
107 | |
---|
108 | // check if we should scroll existing instance |
---|
109 | if ($.isPlainObject(options)) |
---|
110 | { |
---|
111 | // Pass height: auto to an existing slimscroll object to force a resize after contents have changed |
---|
112 | if ( 'height' in options && options.height == 'auto' ) { |
---|
113 | me.parent().css('height', 'auto'); |
---|
114 | me.css('height', 'auto'); |
---|
115 | var height = me.parent().parent().height(); |
---|
116 | me.parent().css('height', height); |
---|
117 | me.css('height', height); |
---|
118 | } |
---|
119 | |
---|
120 | if ('scrollTo' in options) |
---|
121 | { |
---|
122 | // jump to a static point |
---|
123 | offset = parseInt(o.scrollTo); |
---|
124 | } |
---|
125 | else if ('scrollBy' in options) |
---|
126 | { |
---|
127 | // jump by value pixels |
---|
128 | offset += parseInt(o.scrollBy); |
---|
129 | } |
---|
130 | else if ('destroy' in options) |
---|
131 | { |
---|
132 | // remove slimscroll elements |
---|
133 | bar.remove(); |
---|
134 | rail.remove(); |
---|
135 | me.unwrap(); |
---|
136 | return; |
---|
137 | } |
---|
138 | |
---|
139 | // scroll content by the given offset |
---|
140 | scrollContent(offset, false, true); |
---|
141 | } |
---|
142 | |
---|
143 | return; |
---|
144 | } |
---|
145 | |
---|
146 | // optionally set height to the parent's height |
---|
147 | o.height = (o.height == 'auto') ? me.parent().height() : o.height; |
---|
148 | |
---|
149 | // wrap content |
---|
150 | var wrapper = $(divS) |
---|
151 | .addClass(o.wrapperClass) |
---|
152 | .css({ |
---|
153 | position: 'relative', |
---|
154 | overflow: 'hidden', |
---|
155 | width: o.width, |
---|
156 | height: o.height |
---|
157 | }); |
---|
158 | |
---|
159 | // update style for the div |
---|
160 | me.css({ |
---|
161 | overflow: 'hidden', |
---|
162 | width: o.width, |
---|
163 | height: o.height |
---|
164 | }); |
---|
165 | |
---|
166 | // create scrollbar rail |
---|
167 | var rail = $(divS) |
---|
168 | .addClass(o.railClass) |
---|
169 | .css({ |
---|
170 | width: o.size, |
---|
171 | height: '100%', |
---|
172 | position: 'absolute', |
---|
173 | top: 0, |
---|
174 | display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none', |
---|
175 | 'border-radius': o.railBorderRadius, |
---|
176 | background: o.railColor, |
---|
177 | opacity: o.railOpacity, |
---|
178 | zIndex: 90 |
---|
179 | }); |
---|
180 | |
---|
181 | // create scrollbar |
---|
182 | var bar = $(divS) |
---|
183 | .addClass(o.barClass) |
---|
184 | .css({ |
---|
185 | background: o.color, |
---|
186 | width: o.size, |
---|
187 | position: 'absolute', |
---|
188 | top: 0, |
---|
189 | opacity: o.opacity, |
---|
190 | display: o.alwaysVisible ? 'block' : 'none', |
---|
191 | 'border-radius' : o.borderRadius, |
---|
192 | BorderRadius: o.borderRadius, |
---|
193 | MozBorderRadius: o.borderRadius, |
---|
194 | WebkitBorderRadius: o.borderRadius, |
---|
195 | zIndex: 99 |
---|
196 | }); |
---|
197 | |
---|
198 | // set position |
---|
199 | var posCss = (o.position == 'right') ? { right: o.distance } : { left: o.distance }; |
---|
200 | rail.css(posCss); |
---|
201 | bar.css(posCss); |
---|
202 | |
---|
203 | // wrap it |
---|
204 | me.wrap(wrapper); |
---|
205 | |
---|
206 | // append to parent div |
---|
207 | me.parent().append(bar); |
---|
208 | me.parent().append(rail); |
---|
209 | |
---|
210 | // make it draggable |
---|
211 | if (o.railDraggable && $.ui && typeof($.ui.draggable) == 'function') |
---|
212 | { |
---|
213 | bar.draggable({ |
---|
214 | axis: 'y', |
---|
215 | containment: 'parent', |
---|
216 | start: function() { isDragg = true; }, |
---|
217 | stop: function() { isDragg = false; hideBar(); }, |
---|
218 | drag: function(e) |
---|
219 | { |
---|
220 | // scroll content |
---|
221 | scrollContent(0, $(this).position().top, false); |
---|
222 | } |
---|
223 | }); |
---|
224 | } |
---|
225 | |
---|
226 | // on rail over |
---|
227 | rail.hover(function(){ |
---|
228 | showBar(); |
---|
229 | }, function(){ |
---|
230 | hideBar(); |
---|
231 | }); |
---|
232 | |
---|
233 | // on bar over |
---|
234 | bar.hover(function(){ |
---|
235 | isOverBar = true; |
---|
236 | }, function(){ |
---|
237 | isOverBar = false; |
---|
238 | }); |
---|
239 | |
---|
240 | // show on parent mouseover |
---|
241 | me.hover(function(){ |
---|
242 | isOverPanel = true; |
---|
243 | showBar(); |
---|
244 | hideBar(); |
---|
245 | }, function(){ |
---|
246 | isOverPanel = false; |
---|
247 | hideBar(); |
---|
248 | }); |
---|
249 | |
---|
250 | // support for mobile |
---|
251 | me.bind('touchstart', function(e,b){ |
---|
252 | if (e.originalEvent.touches.length) |
---|
253 | { |
---|
254 | // record where touch started |
---|
255 | touchDif = e.originalEvent.touches[0].pageY; |
---|
256 | } |
---|
257 | }); |
---|
258 | |
---|
259 | me.bind('touchmove', function(e){ |
---|
260 | // prevent scrolling the page |
---|
261 | e.originalEvent.preventDefault(); |
---|
262 | if (e.originalEvent.touches.length) |
---|
263 | { |
---|
264 | // see how far user swiped |
---|
265 | var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep; |
---|
266 | // scroll content |
---|
267 | scrollContent(diff, true); |
---|
268 | } |
---|
269 | }); |
---|
270 | |
---|
271 | // check start position |
---|
272 | if (o.start === 'bottom') |
---|
273 | { |
---|
274 | // scroll content to bottom |
---|
275 | bar.css({ top: me.outerHeight() - bar.outerHeight() }); |
---|
276 | scrollContent(0, true); |
---|
277 | } |
---|
278 | else if (o.start !== 'top') |
---|
279 | { |
---|
280 | // assume jQuery selector |
---|
281 | scrollContent($(o.start).position().top, null, true); |
---|
282 | |
---|
283 | // make sure bar stays hidden |
---|
284 | if (!o.alwaysVisible) { bar.hide(); } |
---|
285 | } |
---|
286 | |
---|
287 | // attach scroll events |
---|
288 | attachWheel(); |
---|
289 | |
---|
290 | // set up initial height |
---|
291 | getBarHeight(); |
---|
292 | |
---|
293 | function _onWheel(e) |
---|
294 | { |
---|
295 | // use mouse wheel only when mouse is over |
---|
296 | if (!isOverPanel) { return; } |
---|
297 | |
---|
298 | var e = e || window.event; |
---|
299 | |
---|
300 | var delta = 0; |
---|
301 | if (e.wheelDelta) { delta = -e.wheelDelta/120; } |
---|
302 | if (e.detail) { delta = e.detail / 3; } |
---|
303 | |
---|
304 | var target = e.target || e.srcTarget || e.srcElement; |
---|
305 | if ($(target).closest('.' + o.wrapperClass).is(me.parent())) { |
---|
306 | // scroll content |
---|
307 | scrollContent(delta, true); |
---|
308 | } |
---|
309 | |
---|
310 | // stop window scroll |
---|
311 | if (e.preventDefault && !releaseScroll) { e.preventDefault(); } |
---|
312 | if (!releaseScroll) { e.returnValue = false; } |
---|
313 | } |
---|
314 | |
---|
315 | function scrollContent(y, isWheel, isJump) |
---|
316 | { |
---|
317 | var delta = y; |
---|
318 | var maxTop = me.outerHeight() - bar.outerHeight(); |
---|
319 | |
---|
320 | if (isWheel) |
---|
321 | { |
---|
322 | // move bar with mouse wheel |
---|
323 | delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight(); |
---|
324 | |
---|
325 | // move bar, make sure it doesn't go out |
---|
326 | delta = Math.min(Math.max(delta, 0), maxTop); |
---|
327 | |
---|
328 | // if scrolling down, make sure a fractional change to the |
---|
329 | // scroll position isn't rounded away when the scrollbar's CSS is set |
---|
330 | // this flooring of delta would happened automatically when |
---|
331 | // bar.css is set below, but we floor here for clarity |
---|
332 | delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta); |
---|
333 | |
---|
334 | // scroll the scrollbar |
---|
335 | bar.css({ top: delta + 'px' }); |
---|
336 | } |
---|
337 | |
---|
338 | // calculate actual scroll amount |
---|
339 | percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight()); |
---|
340 | delta = percentScroll * (me[0].scrollHeight - me.outerHeight()); |
---|
341 | |
---|
342 | if (isJump) |
---|
343 | { |
---|
344 | delta = y; |
---|
345 | var offsetTop = delta / me[0].scrollHeight * me.outerHeight(); |
---|
346 | offsetTop = Math.min(Math.max(offsetTop, 0), maxTop); |
---|
347 | bar.css({ top: offsetTop + 'px' }); |
---|
348 | } |
---|
349 | |
---|
350 | // scroll content |
---|
351 | me.scrollTop(delta); |
---|
352 | |
---|
353 | // fire scrolling event |
---|
354 | me.trigger('slimscrolling', ~~delta); |
---|
355 | |
---|
356 | // ensure bar is visible |
---|
357 | showBar(); |
---|
358 | |
---|
359 | // trigger hide when scroll is stopped |
---|
360 | hideBar(); |
---|
361 | } |
---|
362 | |
---|
363 | function attachWheel() |
---|
364 | { |
---|
365 | if (window.addEventListener) |
---|
366 | { |
---|
367 | this.addEventListener('DOMMouseScroll', _onWheel, false ); |
---|
368 | this.addEventListener('mousewheel', _onWheel, false ); |
---|
369 | } |
---|
370 | else |
---|
371 | { |
---|
372 | document.attachEvent("onmousewheel", _onWheel) |
---|
373 | } |
---|
374 | } |
---|
375 | |
---|
376 | function getBarHeight() |
---|
377 | { |
---|
378 | // calculate scrollbar height and make sure it is not too small |
---|
379 | barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight); |
---|
380 | bar.css({ height: barHeight + 'px' }); |
---|
381 | |
---|
382 | // hide scrollbar if content is not long enough |
---|
383 | var display = barHeight == me.outerHeight() ? 'none' : 'block'; |
---|
384 | bar.css({ display: display }); |
---|
385 | } |
---|
386 | |
---|
387 | function showBar() |
---|
388 | { |
---|
389 | // recalculate bar height |
---|
390 | getBarHeight(); |
---|
391 | clearTimeout(queueHide); |
---|
392 | |
---|
393 | // when bar reached top or bottom |
---|
394 | if (percentScroll == ~~percentScroll) |
---|
395 | { |
---|
396 | //release wheel |
---|
397 | releaseScroll = o.allowPageScroll; |
---|
398 | |
---|
399 | // publish approporiate event |
---|
400 | if (lastScroll != percentScroll) |
---|
401 | { |
---|
402 | var msg = (~~percentScroll == 0) ? 'top' : 'bottom'; |
---|
403 | me.trigger('slimscroll', msg); |
---|
404 | } |
---|
405 | } |
---|
406 | else |
---|
407 | { |
---|
408 | releaseScroll = false; |
---|
409 | } |
---|
410 | lastScroll = percentScroll; |
---|
411 | |
---|
412 | // show only when required |
---|
413 | if(barHeight >= me.outerHeight()) { |
---|
414 | //allow window scroll |
---|
415 | releaseScroll = true; |
---|
416 | return; |
---|
417 | } |
---|
418 | bar.stop(true,true).fadeIn('fast'); |
---|
419 | if (o.railVisible) { rail.stop(true,true).fadeIn('fast'); } |
---|
420 | } |
---|
421 | |
---|
422 | function hideBar() |
---|
423 | { |
---|
424 | // only hide when options allow it |
---|
425 | if (!o.alwaysVisible) |
---|
426 | { |
---|
427 | queueHide = setTimeout(function(){ |
---|
428 | if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) |
---|
429 | { |
---|
430 | bar.fadeOut('slow'); |
---|
431 | rail.fadeOut('slow'); |
---|
432 | } |
---|
433 | }, 1000); |
---|
434 | } |
---|
435 | } |
---|
436 | |
---|
437 | }); |
---|
438 | |
---|
439 | // maintain chainability |
---|
440 | return this; |
---|
441 | } |
---|
442 | }); |
---|
443 | |
---|
444 | jQuery.fn.extend({ |
---|
445 | slimscroll: jQuery.fn.slimScroll |
---|
446 | }); |
---|
447 | |
---|
448 | })(jQuery); |
---|