1/* Copyright (c) 2009 Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
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 * See http://kelvinluck.com/assets/jquery/jScrollPane/
6 * $Id: jScrollPane.js 93 2010-06-01 08:17:28Z kelvin.luck $
7 */
8
9/**
10 * Replace the vertical scroll bars on any matched elements with a fancy
11 * styleable (via CSS) version. With JS disabled the elements will
12 * gracefully degrade to the browsers own implementation of overflow:auto.
13 * If the mousewheel plugin has been included on the page then the scrollable areas will also
14 * respond to the mouse wheel.
15 *
16 * @example jQuery(".scroll-pane").jScrollPane();
17 *
18 * @name jScrollPane
19 * @type jQuery
20 * @param Object	settings	hash with options, described below.
21 *								scrollbarWidth	-	The width of the generated scrollbar in pixels
22 *								scrollbarMargin	-	The amount of space to leave on the side of the scrollbar in pixels
23 *								wheelSpeed		-	The speed the pane will scroll in response to the mouse wheel in pixels
24 *								showArrows		-	Whether to display arrows for the user to scroll with
25 *								arrowSize		-	The height of the arrow buttons if showArrows=true
26 *								animateTo		-	Whether to animate when calling scrollTo and scrollBy
27 *								dragMinHeight	-	The minimum height to allow the drag bar to be
28 *								dragMaxHeight	-	The maximum height to allow the drag bar to be
29 *								animateInterval	-	The interval in milliseconds to update an animating scrollPane (default 100)
30 *								animateStep		-	The amount to divide the remaining scroll distance by when animating (default 3)
31 *								maintainPosition-	Whether you want the contents of the scroll pane to maintain it's position when you re-initialise it - so it doesn't scroll as you add more content (default true)
32 *								tabIndex		-	The tabindex for this jScrollPane to control when it is tabbed to when navigating via keyboard (default 0)
33 *								enableKeyboardNavigation - Whether to allow keyboard scrolling of this jScrollPane when it is focused (default true)
34 *								animateToInternalLinks - Whether the move to an internal link (e.g. when it's focused by tabbing or by a hash change in the URL) should be animated or instant (default false)
35 *								scrollbarOnLeft	-	Display the scrollbar on the left side?  (needs stylesheet changes, see examples.html)
36 *								reinitialiseOnImageLoad - Whether the jScrollPane should automatically re-initialise itself when any contained images are loaded (default false)
37 *								topCapHeight	-	The height of the "cap" area between the top of the jScrollPane and the top of the track/ buttons
38 *								bottomCapHeight	-	The height of the "cap" area between the bottom of the jScrollPane and the bottom of the track/ buttons
39 *								observeHash		-	Whether jScrollPane should attempt to automagically scroll to the correct place when an anchor inside the scrollpane is linked to (default true)
40 * @return jQuery
41 * @cat Plugins/jScrollPane
42 * @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
43 */
44
45(function($) {
46
47$.jScrollPane = {
48	active : []
49};
50$.fn.jScrollPane = function(settings)
51{
52	settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
53
54	var rf = function() { return false; };
55
56	return this.each(
57		function()
58		{
59			var $this = $(this);
60			var paneEle = this;
61			var currentScrollPosition = 0;
62			var paneWidth;
63			var paneHeight;
64			var trackHeight;
65			var trackOffset = settings.topCapHeight;
66			var $container;
67
68			if ($(this).parent().is('.jScrollPaneContainer')) {
69				$container = $(this).parent();
70				currentScrollPosition = settings.maintainPosition ? $this.position().top : 0;
71				var $c = $(this).parent();
72				paneWidth = $c.innerWidth();
73				paneHeight = $c.outerHeight();
74				$('>.jScrollPaneTrack, >.jScrollArrowUp, >.jScrollArrowDown, >.jScrollCap', $c).remove();
75				$this.css({'top':0});
76			} else {
77				$this.data('originalStyleTag', $this.attr('style'));
78				// Switch the element's overflow to hidden to ensure we get the size of the element without the scrollbars [http://plugins.jquery.com/node/1208]
79				$this.css('overflow', 'hidden');
80				this.originalPadding = $this.css('paddingTop') + ' ' + $this.css('paddingRight') + ' ' + $this.css('paddingBottom') + ' ' + $this.css('paddingLeft');
81				this.originalSidePaddingTotal = (parseInt($this.css('paddingLeft')) || 0) + (parseInt($this.css('paddingRight')) || 0);
82				paneWidth = $this.innerWidth();
83				paneHeight = $this.innerHeight();
84				$container = $('<div></div>')
85					.attr({'className':'jScrollPaneContainer'})
86					.css(
87						{
88							'height':paneHeight+'px',
89							'width':paneWidth+'px'
90						}
91					);
92				if (settings.enableKeyboardNavigation) {
93					$container.attr(
94						'tabindex',
95						settings.tabIndex
96					);
97				}
98				$this.wrap($container);
99				$container = $this.parent();
100				// deal with text size changes (if the jquery.em plugin is included)
101				// and re-initialise the scrollPane so the track maintains the
102				// correct size
103				$(document).bind(
104					'emchange',
105					function(e, cur, prev)
106					{
107						$this.jScrollPane(settings);
108					}
109				);
110
111			}
112			trackHeight = paneHeight;
113
114			if (settings.reinitialiseOnImageLoad) {
115				// code inspired by jquery.onImagesLoad: http://plugins.jquery.com/project/onImagesLoad
116				// except we re-initialise the scroll pane when each image loads so that the scroll pane is always up to size...
117				// TODO: Do I even need to store it in $.data? Is a local variable here the same since I don't pass the reinitialiseOnImageLoad when I re-initialise?
118				var $imagesToLoad = $.data(paneEle, 'jScrollPaneImagesToLoad') || $('img', $this);
119				var loadedImages = [];
120
121				if ($imagesToLoad.length) {
122					$imagesToLoad.each(function(i, val)	{
123						$(this).bind('load readystatechange', function() {
124							if($.inArray(i, loadedImages) == -1){ //don't double count images
125								loadedImages.push(val); //keep a record of images we've seen
126								$imagesToLoad = $.grep($imagesToLoad, function(n, i) {
127									return n != val;
128								});
129								$.data(paneEle, 'jScrollPaneImagesToLoad', $imagesToLoad);
130								var s2 = $.extend(settings, {reinitialiseOnImageLoad:false});
131								$this.jScrollPane(s2); // re-initialise
132							}
133						}).each(function(i, val) {
134							if(this.complete || this.complete===undefined) {
135								//needed for potential cached images
136								this.src = this.src;
137							}
138						});
139					});
140				};
141			}
142
143			var p = this.originalSidePaddingTotal;
144			var realPaneWidth = paneWidth - settings.scrollbarWidth - settings.scrollbarMargin - p;
145
146			var cssToApply = {
147				'height':'auto',
148				'width': realPaneWidth + 'px'
149			}
150
151			if(settings.scrollbarOnLeft) {
152				cssToApply.paddingLeft = settings.scrollbarMargin + settings.scrollbarWidth + 'px';
153			} else {
154				cssToApply.paddingRight = settings.scrollbarMargin + 'px';
155			}
156
157			$this.css(cssToApply);
158
159			var contentHeight = $this.outerHeight();
160			var percentInView = paneHeight / contentHeight;
161
162			var isScrollable = percentInView < .99;
163			$container[isScrollable ? 'addClass' : 'removeClass']('jScrollPaneScrollable');
164
165			if (isScrollable) {
166				$container.append(
167					$('<div></div>').addClass('jScrollCap jScrollCapTop').css({height:settings.topCapHeight}),
168					$('<div></div>').attr({'className':'jScrollPaneTrack'}).css({'width':settings.scrollbarWidth+'px'}).append(
169						$('<div></div>').attr({'className':'jScrollPaneDrag'}).css({'width':settings.scrollbarWidth+'px'}).append(
170							$('<div></div>').attr({'className':'jScrollPaneDragTop'}).css({'width':settings.scrollbarWidth+'px'}),
171							$('<div></div>').attr({'className':'jScrollPaneDragBottom'}).css({'width':settings.scrollbarWidth+'px'})
172						)
173					),
174					$('<div></div>').addClass('jScrollCap jScrollCapBottom').css({height:settings.bottomCapHeight})
175				);
176
177				var $track = $('>.jScrollPaneTrack', $container);
178				var $drag = $('>.jScrollPaneTrack .jScrollPaneDrag', $container);
179
180
181				var currentArrowDirection;
182				var currentArrowTimerArr = [];// Array is used to store timers since they can stack up when dealing with keyboard events. This ensures all timers are cleaned up in the end, preventing an acceleration bug.
183				var currentArrowInc;
184				var whileArrowButtonDown = function()
185				{
186					if (currentArrowInc > 4 || currentArrowInc % 4 == 0) {
187						positionDrag(dragPosition + currentArrowDirection * mouseWheelMultiplier);
188					}
189					currentArrowInc++;
190				};
191
192				if (settings.enableKeyboardNavigation) {
193					$container.bind(
194						'keydown.jscrollpane',
195						function(e)
196						{
197							switch (e.keyCode) {
198								case 38: //up
199									currentArrowDirection = -1;
200									currentArrowInc = 0;
201									whileArrowButtonDown();
202									currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
203									return false;
204								case 40: //down
205									currentArrowDirection = 1;
206									currentArrowInc = 0;
207									whileArrowButtonDown();
208									currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
209									return false;
210								case 33: // page up
211								case 34: // page down
212									// TODO
213									return false;
214								default:
215							}
216						}
217					).bind(
218						'keyup.jscrollpane',
219						function(e)
220						{
221							if (e.keyCode == 38 || e.keyCode == 40) {
222								for (var i = 0; i < currentArrowTimerArr.length; i++) {
223									clearInterval(currentArrowTimerArr[i]);
224								}
225								return false;
226							}
227						}
228					);
229				}
230
231				if (settings.showArrows) {
232
233					var currentArrowButton;
234					var currentArrowInterval;
235
236					var onArrowMouseUp = function(event)
237					{
238						$('html').unbind('mouseup', onArrowMouseUp);
239						currentArrowButton.removeClass('jScrollActiveArrowButton');
240						clearInterval(currentArrowInterval);
241					};
242					var onArrowMouseDown = function() {
243						$('html').bind('mouseup', onArrowMouseUp);
244						currentArrowButton.addClass('jScrollActiveArrowButton');
245						currentArrowInc = 0;
246						whileArrowButtonDown();
247						currentArrowInterval = setInterval(whileArrowButtonDown, 100);
248					};
249					$container
250						.append(
251							$('<a></a>')
252								.attr(
253									{
254										'href':'javascript:;',
255										'className':'jScrollArrowUp',
256										'tabindex':-1
257									}
258								)
259								.css(
260									{
261										'width':settings.scrollbarWidth+'px',
262										'top':settings.topCapHeight + 'px'
263									}
264								)
265								.html('Scroll up')
266								.bind('mousedown', function()
267								{
268									currentArrowButton = $(this);
269									currentArrowDirection = -1;
270									onArrowMouseDown();
271									this.blur();
272									return false;
273								})
274								.bind('click', rf),
275							$('<a></a>')
276								.attr(
277									{
278										'href':'javascript:;',
279										'className':'jScrollArrowDown',
280										'tabindex':-1
281									}
282								)
283								.css(
284									{
285										'width':settings.scrollbarWidth+'px',
286										'bottom':settings.bottomCapHeight + 'px'
287									}
288								)
289								.html('Scroll down')
290								.bind('mousedown', function()
291								{
292									currentArrowButton = $(this);
293									currentArrowDirection = 1;
294									onArrowMouseDown();
295									this.blur();
296									return false;
297								})
298								.bind('click', rf)
299						);
300					var $upArrow = $('>.jScrollArrowUp', $container);
301					var $downArrow = $('>.jScrollArrowDown', $container);
302				}
303
304				if (settings.arrowSize) {
305					trackHeight = paneHeight - settings.arrowSize - settings.arrowSize;
306					trackOffset += settings.arrowSize;
307				} else if ($upArrow) {
308					var topArrowHeight = $upArrow.height();
309					settings.arrowSize = topArrowHeight;
310					trackHeight = paneHeight - topArrowHeight - $downArrow.height();
311					trackOffset += topArrowHeight;
312				}
313				trackHeight -= settings.topCapHeight + settings.bottomCapHeight;
314				$track.css({'height': trackHeight+'px', top:trackOffset+'px'})
315
316				var $pane = $(this).css({'position':'absolute', 'overflow':'visible'});
317
318				var currentOffset;
319				var maxY;
320				var mouseWheelMultiplier;
321				// store this in a seperate variable so we can keep track more accurately than just updating the css property..
322				var dragPosition = 0;
323				var dragMiddle = percentInView*paneHeight/2;
324
325				// pos function borrowed from tooltip plugin and adapted...
326				var getPos = function (event, c) {
327					var p = c == 'X' ? 'Left' : 'Top';
328					return event['page' + c] || (event['client' + c] + (document.documentElement['scroll' + p] || document.body['scroll' + p])) || 0;
329				};
330
331				var ignoreNativeDrag = function() {	return false; };
332
333				var initDrag = function()
334				{
335					ceaseAnimation();
336					currentOffset = $drag.offset(false);
337					currentOffset.top -= dragPosition;
338					maxY = trackHeight - $drag[0].offsetHeight;
339					mouseWheelMultiplier = 2 * settings.wheelSpeed * maxY / contentHeight;
340				};
341
342				var onStartDrag = function(event)
343				{
344					initDrag();
345					dragMiddle = getPos(event, 'Y') - dragPosition - currentOffset.top;
346					$('html').bind('mouseup', onStopDrag).bind('mousemove', updateScroll).bind('mouseleave', onStopDrag)
347					if ($.browser.msie) {
348						$('html').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag);
349					}
350					return false;
351				};
352				var onStopDrag = function()
353				{
354					$('html').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll);
355					dragMiddle = percentInView*paneHeight/2;
356					if ($.browser.msie) {
357						$('html').unbind('dragstart', ignoreNativeDrag).unbind('selectstart', ignoreNativeDrag);
358					}
359				};
360				var positionDrag = function(destY)
361				{
362					$container.scrollTop(0);
363					destY = destY < 0 ? 0 : (destY > maxY ? maxY : destY);
364					dragPosition = destY;
365					$drag.css({'top':destY+'px'});
366					var p = destY / maxY;
367					$this.data('jScrollPanePosition', (paneHeight-contentHeight)*-p);
368					$pane.css({'top':((paneHeight-contentHeight)*p) + 'px'});
369					$this.trigger('scroll');
370					if (settings.showArrows) {
371						$upArrow[destY == 0 ? 'addClass' : 'removeClass']('disabled');
372						$downArrow[destY == maxY ? 'addClass' : 'removeClass']('disabled');
373					}
374				};
375				var updateScroll = function(e)
376				{
377					positionDrag(getPos(e, 'Y') - currentOffset.top - dragMiddle);
378				};
379
380				var dragH = Math.max(Math.min(percentInView*(paneHeight-settings.arrowSize*2), settings.dragMaxHeight), settings.dragMinHeight);
381
382				$drag.css(
383					{'height':dragH+'px'}
384				).bind('mousedown', onStartDrag);
385
386				var trackScrollInterval;
387				var trackScrollInc;
388				var trackScrollMousePos;
389				var doTrackScroll = function()
390				{
391					if (trackScrollInc > 8 || trackScrollInc%4==0) {
392						positionDrag((dragPosition - ((dragPosition - trackScrollMousePos) / 2)));
393					}
394					trackScrollInc ++;
395				};
396				var onStopTrackClick = function()
397				{
398					clearInterval(trackScrollInterval);
399					$('html').unbind('mouseup', onStopTrackClick).unbind('mousemove', onTrackMouseMove);
400				};
401				var onTrackMouseMove = function(event)
402				{
403					trackScrollMousePos = getPos(event, 'Y') - currentOffset.top - dragMiddle;
404				};
405				var onTrackClick = function(event)
406				{
407					initDrag();
408					onTrackMouseMove(event);
409					trackScrollInc = 0;
410					$('html').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove);
411					trackScrollInterval = setInterval(doTrackScroll, 100);
412					doTrackScroll();
413					return false;
414				};
415
416				$track.bind('mousedown', onTrackClick);
417
418				$container.bind(
419					'mousewheel',
420					function (event, delta) {
421						delta = delta || (event.wheelDelta ? event.wheelDelta / 120 : (event.detail) ?
422-event.detail/3 : 0);
423						initDrag();
424						ceaseAnimation();
425						var d = dragPosition;
426						positionDrag(dragPosition - delta * mouseWheelMultiplier);
427						var dragOccured = d != dragPosition;
428						return !dragOccured;
429					}
430				);
431
432				var _animateToPosition;
433				var _animateToInterval;
434				function animateToPosition()
435				{
436					var diff = (_animateToPosition - dragPosition) / settings.animateStep;
437					if (diff > 1 || diff < -1) {
438						positionDrag(dragPosition + diff);
439					} else {
440						positionDrag(_animateToPosition);
441						ceaseAnimation();
442					}
443				}
444				var ceaseAnimation = function()
445				{
446					if (_animateToInterval) {
447						clearInterval(_animateToInterval);
448						delete _animateToPosition;
449					}
450				};
451				var scrollTo = function(pos, preventAni)
452				{
453					if (typeof pos == "string") {
454						// Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
455						// errors from the lookup...
456						try {
457							$e = $(pos, $this);
458						} catch (err) {
459							return;
460						}
461						if (!$e.length) return;
462						pos = $e.offset().top - $this.offset().top;
463					}
464					ceaseAnimation();
465					var maxScroll = contentHeight - paneHeight;
466					pos = pos > maxScroll ? maxScroll : pos;
467					$this.data('jScrollPaneMaxScroll', maxScroll);
468					var destDragPosition = pos/maxScroll * maxY;
469					if (preventAni || !settings.animateTo) {
470						positionDrag(destDragPosition);
471					} else {
472						$container.scrollTop(0);
473						_animateToPosition = destDragPosition;
474						_animateToInterval = setInterval(animateToPosition, settings.animateInterval);
475					}
476				};
477				$this[0].scrollTo = scrollTo;
478
479				$this[0].scrollBy = function(delta)
480				{
481					var currentPos = -parseInt($pane.css('top')) || 0;
482					scrollTo(currentPos + delta);
483				};
484
485				initDrag();
486
487				scrollTo(-currentScrollPosition, true);
488
489				// Deal with it when the user tabs to a link or form element within this scrollpane
490				$('*', this).bind(
491					'focus',
492					function(event)
493					{
494						var $e = $(this);
495
496						// loop through parents adding the offset top of any elements that are relatively positioned between
497						// the focused element and the jScrollPaneContainer so we can get the true distance from the top
498						// of the focused element to the top of the scrollpane...
499						var eleTop = 0;
500
501						var preventInfiniteLoop = 100;
502
503						while ($e[0] != $this[0]) {
504							eleTop += $e.position().top;
505							$e = $e.offsetParent();
506							if (!preventInfiniteLoop--) {
507								return;
508							}
509						}
510
511						var viewportTop = -parseInt($pane.css('top')) || 0;
512						var maxVisibleEleTop = viewportTop + paneHeight;
513						var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
514						if (!eleInView) {
515							var destPos = eleTop - settings.scrollbarMargin;
516							if (eleTop > viewportTop) { // element is below viewport - scroll so it is at bottom.
517								destPos += $(this).height() + 15 + settings.scrollbarMargin - paneHeight;
518							}
519							scrollTo(destPos);
520						}
521					}
522				)
523
524
525				if (settings.observeHash) {
526					if (location.hash && location.hash.length > 1) {
527						setTimeout(function(){
528							scrollTo(location.hash);
529						}, $.browser.safari ? 100 : 0);
530					}
531
532					// use event delegation to listen for all clicks on links and hijack them if they are links to
533					// anchors within our content...
534					$(document).bind('click', function(e){
535						$target = $(e.target);
536						if ($target.is('a')) {
537							var h = $target.attr('href');
538							if (h && h.substr(0, 1) == '#' && h.length > 1) {
539								setTimeout(function(){
540									scrollTo(h, !settings.animateToInternalLinks);
541								}, $.browser.safari ? 100 : 0);
542							}
543						}
544					});
545				}
546
547				// Deal with dragging and selecting text to make the scrollpane scroll...
548				function onSelectScrollMouseDown(e)
549				{
550				   $(document).bind('mousemove.jScrollPaneDragging', onTextSelectionScrollMouseMove);
551				   $(document).bind('mouseup.jScrollPaneDragging',   onSelectScrollMouseUp);
552
553				}
554
555				var textDragDistanceAway;
556				var textSelectionInterval;
557
558				function onTextSelectionInterval()
559				{
560					direction = textDragDistanceAway < 0 ? -1 : 1;
561					$this[0].scrollBy(textDragDistanceAway / 2);
562				}
563
564				function clearTextSelectionInterval()
565				{
566					if (textSelectionInterval) {
567						clearInterval(textSelectionInterval);
568						textSelectionInterval = undefined;
569					}
570				}
571
572				function onTextSelectionScrollMouseMove(e)
573				{
574					var offset = $this.parent().offset().top;
575					var maxOffset = offset + paneHeight;
576					var mouseOffset = getPos(e, 'Y');
577					textDragDistanceAway = mouseOffset < offset ? mouseOffset - offset : (mouseOffset > maxOffset ? mouseOffset - maxOffset : 0);
578					if (textDragDistanceAway == 0) {
579						clearTextSelectionInterval();
580					} else {
581						if (!textSelectionInterval) {
582							textSelectionInterval  = setInterval(onTextSelectionInterval, 100);
583						}
584					}
585				}
586
587				function onSelectScrollMouseUp(e)
588				{
589				   $(document)
590					  .unbind('mousemove.jScrollPaneDragging')
591					  .unbind('mouseup.jScrollPaneDragging');
592				   clearTextSelectionInterval();
593				}
594
595				$container.bind('mousedown.jScrollPane', onSelectScrollMouseDown);
596
597
598				$.jScrollPane.active.push($this[0]);
599
600			} else {
601				$this.css(
602					{
603						'height':paneHeight+'px',
604						'width':paneWidth-this.originalSidePaddingTotal+'px',
605						'padding':this.originalPadding
606					}
607				);
608				$this[0].scrollTo = $this[0].scrollBy = function() {};
609				// clean up listeners
610				$this.parent().unbind('mousewheel').unbind('mousedown.jScrollPane').unbind('keydown.jscrollpane').unbind('keyup.jscrollpane');
611			}
612
613		}
614	)
615};
616
617$.fn.jScrollPaneRemove = function()
618{
619	$(this).each(function()
620	{
621		$this = $(this);
622		var $c = $this.parent();
623		if ($c.is('.jScrollPaneContainer')) {
624			$this.css(
625				{
626					'top':'',
627					'height':'',
628					'width':'',
629					'padding':'',
630					'overflow':'',
631					'position':''
632				}
633			);
634			$this.attr('style', $this.data('originalStyleTag'));
635			$c.after($this).remove();
636		}
637	});
638}
639
640$.fn.jScrollPane.defaults = {
641	scrollbarWidth : 10,
642	scrollbarMargin : 5,
643	wheelSpeed : 18,
644	showArrows : false,
645	arrowSize : 0,
646	animateTo : false,
647	dragMinHeight : 1,
648	dragMaxHeight : 99999,
649	animateInterval : 100,
650	animateStep: 3,
651	maintainPosition: true,
652	scrollbarOnLeft: false,
653	reinitialiseOnImageLoad: false,
654	tabIndex : 0,
655	enableKeyboardNavigation: true,
656	animateToInternalLinks: false,
657	topCapHeight: 0,
658	bottomCapHeight: 0,
659	observeHash: true
660};
661
662// clean up the scrollTo expandos
663$(window)
664	.bind('unload', function() {
665		var els = $.jScrollPane.active;
666		for (var i=0; i<els.length; i++) {
667			els[i].scrollTo = els[i].scrollBy = null;
668		}
669	}
670);
671
672})(jQuery);