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);