O desejo de muitos designers e desenvolvedores de personalizar barras de rolagem dentro de seus sites para que elas acompanhem a identidade visual de seus projetos agora pode ser solucionada facilmente. Durante muito tempo esse tipo de estilização era feita somente em sites desenvolvidos em Flash ou utilizando a os atributos proprietários scrollpanel, como o mestre Maujor mostra nesse artigo, mas que funcionavam somente no Internet Explorer.
Nesse artigo utilizaremos um plugin Jquery, o jScrollPane que permite a estilização de suas barrinhas de maneira fácil e rápida.
Para utilizar o plugin, você precisará dos seguintes arquivos:
- jquery.jscrollpane.css – Css básico aonde será setado a personalização da barra
/* * CSS Styles that are needed by jScrollPane for it to operate correctly. * * Include this stylesheet in your site or copy and paste the styles below into your stylesheet - jScrollPane * may not operate correctly without them. */ .jspContainer { overflow: hidden; position: relative; } .jspPane { position: absolute; } .jspVerticalBar { position: absolute; top: 0; right: 0; width: 16px; height: 100%; background: red; } .jspHorizontalBar { position: absolute; bottom: 0; left: 0; width: 100%; height: 16px; background: red; } .jspVerticalBar *, .jspHorizontalBar * { margin: 0; padding: 0; } .jspCap { display: none; } .jspHorizontalBar .jspCap { float: left; } .jspTrack { background: #dde; position: relative; } .jspDrag { background: #bbd; position: relative; top: 0; left: 0; cursor: pointer; } .jspHorizontalBar .jspTrack, .jspHorizontalBar .jspDrag { float: left; height: 100%; } .jspArrow { background: #50506d; text-indent: -20000px; display: block; cursor: pointer; } .jspArrow.jspDisabled { cursor: default; background: #80808d; } .jspVerticalBar .jspArrow { height: 16px; } .jspHorizontalBar .jspArrow { width: 16px; float: left; height: 100%; } .jspVerticalBar .jspArrow:focus { outline: none; } .jspCorner { background: #eeeef4; float: left; height: 100%; } /* Yuk! CSS Hack for IE6 3 pixel bug :( */ * html .jspCorner { margin: 0 -3px 0 0; }
- jquery.jscrollpane.min.js – O Plugin jScrollPane.
/*! * jScrollPane - v2.0.0beta11 - 2011-07-04 * http://jscrollpane.kelvinluck.com/ * * Copyright (c) 2010 Kelvin Luck * Dual licensed under the MIT and GPL licenses. */ // Script: jScrollPane - cross browser customisable scrollbars // // *Version: 2.0.0beta11, Last updated: 2011-07-04* // // Project Home - http://jscrollpane.kelvinluck.com/ // GitHub - http://github.com/vitch/jScrollPane // Source - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.js // (Minified) - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.min.js // // About: License // // Copyright (c) 2011 Kelvin Luck // Dual licensed under the MIT or GPL Version 2 licenses. // http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt // http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt // // About: Examples // // All examples and demos are available through the jScrollPane example site at: // http://jscrollpane.kelvinluck.com/ // // About: Support and Testing // // This plugin is tested on the browsers below and has been found to work reliably on them. If you run // into a problem on one of the supported browsers then please visit the support section on the jScrollPane // website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also // welcome to fork the project on GitHub if you can contribute a fix for a given issue. // // jQuery Versions - tested in 1.4.2+ - reported to work in 1.3.x // Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8 // // About: Release History // // 2.0.0beta11 - (in progress) // 2.0.0beta10 - (2011-04-17) cleaner required size calculation, improved keyboard support, stickToBottom/Left, other small fixes // 2.0.0beta9 - (2011-01-31) new API methods, bug fixes and correct keyboard support for FF/OSX // 2.0.0beta8 - (2011-01-29) touchscreen support, improved keyboard support // 2.0.0beta7 - (2011-01-23) scroll speed consistent (thanks Aivo Paas) // 2.0.0beta6 - (2010-12-07) scrollToElement horizontal support // 2.0.0beta5 - (2010-10-18) jQuery 1.4.3 support, various bug fixes // 2.0.0beta4 - (2010-09-17) clickOnTrack support, bug fixes // 2.0.0beta3 - (2010-08-27) Horizontal mousewheel, mwheelIntent, keyboard support, bug fixes // 2.0.0beta2 - (2010-08-21) Bug fixes // 2.0.0beta1 - (2010-08-17) Rewrite to follow modern best practices and enable horizontal scrolling, initially hidden // elements and dynamically sized elements. // 1.x - (2006-12-31 - 2010-07-31) Initial version, hosted at googlecode, deprecated (function($,window,undefined){ $.fn.jScrollPane = function(settings) { // JScrollPane "class" - public methods are available through $('selector').data('jsp') function JScrollPane(elem, s) { var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight, percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY, verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition, verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown, horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight, reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth, wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false, originalElement = elem.clone(false, false).empty(), mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp'; originalPadding = elem.css('paddingTop') + ' ' + elem.css('paddingRight') + ' ' + elem.css('paddingBottom') + ' ' + elem.css('paddingLeft'); originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) + (parseInt(elem.css('paddingRight'), 10) || 0); function initialise(s) { var /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY, hasContainingSpaceChanged, originalScrollTop, originalScrollLeft, maintainAtBottom = false, maintainAtRight = false; settings = s; if (pane === undefined) { originalScrollTop = elem.scrollTop(); originalScrollLeft = elem.scrollLeft(); elem.css( { overflow: 'hidden', padding: 0 } ); // TODO: Deal with where width/ height is 0 as it probably means the element is hidden and we should // come back to it later and check once it is unhidden... paneWidth = elem.innerWidth() + originalPaddingTotalWidth; paneHeight = elem.innerHeight(); elem.width(paneWidth); pane = $(' ').css('padding', originalPadding).append(elem.children()); container = $(' ') .css({ 'width': paneWidth + 'px', 'height': paneHeight + 'px' } ).append(pane).appendTo(elem); /* // Move any margins from the first and last children up to the container so they can still // collapse with neighbouring elements as they would before jScrollPane firstChild = pane.find(':first-child'); lastChild = pane.find(':last-child'); elem.css( { 'margin-top': firstChild.css('margin-top'), 'margin-bottom': lastChild.css('margin-bottom') } ); firstChild.css('margin-top', 0); lastChild.css('margin-bottom', 0); */ } else { elem.css('width', ''); maintainAtBottom = settings.stickToBottom &amp;&amp; isCloseToBottom(); maintainAtRight = settings.stickToRight &amp;&amp; isCloseToRight(); hasContainingSpaceChanged = elem.innerWidth() + originalPaddingTotalWidth != paneWidth || elem.outerHeight() != paneHeight; if (hasContainingSpaceChanged) { paneWidth = elem.innerWidth() + originalPaddingTotalWidth; paneHeight = elem.innerHeight(); container.css({ width: paneWidth + 'px', height: paneHeight + 'px' }); } // If nothing changed since last check... if (!hasContainingSpaceChanged &amp;&amp; previousContentWidth == contentWidth &amp;&amp; pane.outerHeight() == contentHeight) { elem.width(paneWidth); return; } previousContentWidth = contentWidth; pane.css('width', ''); elem.width(paneWidth); container.find('>.jspVerticalBar,>.jspHorizontalBar').remove().end(); } pane.css('overflow', 'auto'); if (s.contentWidth) { contentWidth = s.contentWidth; } else { contentWidth = pane[0].scrollWidth; } contentHeight = pane[0].scrollHeight; pane.css('overflow', ''); percentInViewH = contentWidth / paneWidth; percentInViewV = contentHeight / paneHeight; isScrollableV = percentInViewV > 1; isScrollableH = percentInViewH > 1; //console.log(paneWidth, paneHeight, contentWidth, contentHeight, percentInViewH, percentInViewV, isScrollableH, isScrollableV); if (!(isScrollableH || isScrollableV)) { elem.removeClass('jspScrollable'); pane.css({ top: 0, width: container.width() - originalPaddingTotalWidth }); removeMousewheel(); removeFocusHandler(); removeKeyboardNav(); removeClickOnTrack(); unhijackInternalLinks(); } else { elem.addClass('jspScrollable'); isMaintainingPositon = settings.maintainPosition &amp;&amp; (verticalDragPosition || horizontalDragPosition); if (isMaintainingPositon) { lastContentX = contentPositionX(); lastContentY = contentPositionY(); } initialiseVerticalScroll(); initialiseHorizontalScroll(); resizeScrollbars(); if (isMaintainingPositon) { scrollToX(maintainAtRight ? (contentWidth - paneWidth ) : lastContentX, false); scrollToY(maintainAtBottom ? (contentHeight - paneHeight) : lastContentY, false); } initFocusHandler(); initMousewheel(); initTouch(); if (settings.enableKeyboardNavigation) { initKeyboardNav(); } if (settings.clickOnTrack) { initClickOnTrack(); } observeHash(); if (settings.hijackInternalLinks) { hijackInternalLinks(); } } if (settings.autoReinitialise &amp;&amp; !reinitialiseInterval) { reinitialiseInterval = setInterval( function() { initialise(settings); }, settings.autoReinitialiseDelay ); } else if (!settings.autoReinitialise &amp;&amp; reinitialiseInterval) { clearInterval(reinitialiseInterval); } originalScrollTop &amp;&amp; elem.scrollTop(0) &amp;&amp; scrollToY(originalScrollTop, false); originalScrollLeft &amp;&amp; elem.scrollLeft(0) &amp;&amp; scrollToX(originalScrollLeft, false); elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]); } function initialiseVerticalScroll() { if (isScrollableV) { container.append( $(' ').append( $(' '), $(' ').append( $(' ').append( $(' '), $(' ') ) ), $(' ') ) ); verticalBar = container.find('>.jspVerticalBar'); verticalTrack = verticalBar.find('>.jspTrack'); verticalDrag = verticalTrack.find('>.jspDrag'); if (settings.showArrows) { arrowUp = $('').bind( 'mousedown.jsp', getArrowScroll(0, -1) ).bind('click.jsp', nil); arrowDown = $('').bind( 'mousedown.jsp', getArrowScroll(0, 1) ).bind('click.jsp', nil); if (settings.arrowScrollOnHover) { arrowUp.bind('mouseover.jsp', getArrowScroll(0, -1, arrowUp)); arrowDown.bind('mouseover.jsp', getArrowScroll(0, 1, arrowDown)); } appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown); } verticalTrackHeight = paneHeight; container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each( function() { verticalTrackHeight -= $(this).outerHeight(); } ); verticalDrag.hover( function() { verticalDrag.addClass('jspHover'); }, function() { verticalDrag.removeClass('jspHover'); } ).bind( 'mousedown.jsp', function(e) { // Stop IE from allowing text selection $('html').bind('dragstart.jsp selectstart.jsp', nil); verticalDrag.addClass('jspActive'); var startY = e.pageY - verticalDrag.position().top; $('html').bind( 'mousemove.jsp', function(e) { positionDragY(e.pageY - startY, false); } ).bind('mouseup.jsp mouseleave.jsp', cancelDrag); return false; } ); sizeVerticalScrollbar(); } } function sizeVerticalScrollbar() { verticalTrack.height(verticalTrackHeight + 'px'); verticalDragPosition = 0; scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth(); // Make the pane thinner to allow for the vertical scrollbar pane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth); // Add margin to the left of the pane if scrollbars are on that side (to position // the scrollbar on the left or right set it's left or right property in CSS) try { if (verticalBar.position().left === 0) { pane.css('margin-left', scrollbarWidth + 'px'); } } catch (err) { } } function initialiseHorizontalScroll() { if (isScrollableH) { container.append( $(' ').append( $(' '), $(' ').append( $(' ').append( $(' '), $(' ') ) ), $(' ') ) ); horizontalBar = container.find('>.jspHorizontalBar'); horizontalTrack = horizontalBar.find('>.jspTrack'); horizontalDrag = horizontalTrack.find('>.jspDrag'); if (settings.showArrows) { arrowLeft = $('').bind( 'mousedown.jsp', getArrowScroll(-1, 0) ).bind('click.jsp', nil); arrowRight = $('').bind( 'mousedown.jsp', getArrowScroll(1, 0) ).bind('click.jsp', nil); if (settings.arrowScrollOnHover) { arrowLeft.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft)); arrowRight.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRight)); } appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight); } horizontalDrag.hover( function() { horizontalDrag.addClass('jspHover'); }, function() { horizontalDrag.removeClass('jspHover'); } ).bind( 'mousedown.jsp', function(e) { // Stop IE from allowing text selection $('html').bind('dragstart.jsp selectstart.jsp', nil); horizontalDrag.addClass('jspActive'); var startX = e.pageX - horizontalDrag.position().left; $('html').bind( 'mousemove.jsp', function(e) { positionDragX(e.pageX - startX, false); } ).bind('mouseup.jsp mouseleave.jsp', cancelDrag); return false; } ); horizontalTrackWidth = container.innerWidth(); sizeHorizontalScrollbar(); } } function sizeHorizontalScrollbar() { container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each( function() { horizontalTrackWidth -= $(this).outerWidth(); } ); horizontalTrack.width(horizontalTrackWidth + 'px'); horizontalDragPosition = 0; } function resizeScrollbars() { if (isScrollableH &amp;&amp; isScrollableV) { var horizontalTrackHeight = horizontalTrack.outerHeight(), verticalTrackWidth = verticalTrack.outerWidth(); verticalTrackHeight -= horizontalTrackHeight; $(horizontalBar).find('>.jspCap:visible,>.jspArrow').each( function() { horizontalTrackWidth += $(this).outerWidth(); } ); horizontalTrackWidth -= verticalTrackWidth; paneHeight -= verticalTrackWidth; paneWidth -= horizontalTrackHeight; horizontalTrack.parent().append( $(' ').css('width', horizontalTrackHeight + 'px') ); sizeVerticalScrollbar(); sizeHorizontalScrollbar(); } // reflow content if (isScrollableH) { pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px'); } contentHeight = pane.outerHeight(); percentInViewV = contentHeight / paneHeight; if (isScrollableH) { horizontalDragWidth = Math.ceil(1 / percentInViewH * horizontalTrackWidth); if (horizontalDragWidth > settings.horizontalDragMaxWidth) { horizontalDragWidth = settings.horizontalDragMaxWidth; } else if (horizontalDragWidth < settings.horizontalDragMinWidth) { horizontalDragWidth = settings.horizontalDragMinWidth; } horizontalDrag.width(horizontalDragWidth + 'px'); dragMaxX = horizontalTrackWidth - horizontalDragWidth; _positionDragX(horizontalDragPosition); // To update the state for the arrow buttons } if (isScrollableV) { verticalDragHeight = Math.ceil(1 / percentInViewV * verticalTrackHeight); if (verticalDragHeight > settings.verticalDragMaxHeight) { verticalDragHeight = settings.verticalDragMaxHeight; } else if (verticalDragHeight < settings.verticalDragMinHeight) { verticalDragHeight = settings.verticalDragMinHeight; } verticalDrag.height(verticalDragHeight + 'px'); dragMaxY = verticalTrackHeight - verticalDragHeight; _positionDragY(verticalDragPosition); // To update the state for the arrow buttons } } function appendArrows(ele, p, a1, a2) { var p1 = "before", p2 = "after", aTemp; // Sniff for mac... Is there a better way to determine whether the arrows would naturally appear // at the top or the bottom of the bar? if (p == "os") { p = /Mac/.test(navigator.platform) ? "after" : "split"; } if (p == p1) { p2 = p; } else if (p == p2) { p1 = p; aTemp = a1; a1 = a2; a2 = aTemp; } ele[p1](a1)[p2](a2); } function getArrowScroll(dirX, dirY, ele) { return function() { arrowScroll(dirX, dirY, this, ele); this.blur(); return false; }; } function arrowScroll(dirX, dirY, arrow, ele) { arrow = $(arrow).addClass('jspActive'); var eve, scrollTimeout, isFirst = true, doScroll = function() { if (dirX !== 0) { jsp.scrollByX(dirX * settings.arrowButtonSpeed); } if (dirY !== 0) { jsp.scrollByY(dirY * settings.arrowButtonSpeed); } scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.arrowRepeatFreq); isFirst = false; }; doScroll(); eve = ele ? 'mouseout.jsp' : 'mouseup.jsp'; ele = ele || $('html'); ele.bind( eve, function() { arrow.removeClass('jspActive'); scrollTimeout &amp;&amp; clearTimeout(scrollTimeout); scrollTimeout = null; ele.unbind(eve); } ); } function initClickOnTrack() { removeClickOnTrack(); if (isScrollableV) { verticalTrack.bind( 'mousedown.jsp', function(e) { if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) { var clickedTrack = $(this), offset = clickedTrack.offset(), direction = e.pageY - offset.top - verticalDragPosition, scrollTimeout, isFirst = true, doScroll = function() { var offset = clickedTrack.offset(), pos = e.pageY - offset.top - verticalDragHeight / 2, contentDragY = paneHeight * settings.scrollPagePercent, dragY = dragMaxY * contentDragY / (contentHeight - paneHeight); if (direction < 0) { if (verticalDragPosition - dragY > pos) { jsp.scrollByY(-contentDragY); } else { positionDragY(pos); } } else if (direction > 0) { if (verticalDragPosition + dragY < pos) { jsp.scrollByY(contentDragY); } else { positionDragY(pos); } } else { cancelClick(); return; } scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq); isFirst = false; }, cancelClick = function() { scrollTimeout &amp;&amp; clearTimeout(scrollTimeout); scrollTimeout = null; $(document).unbind('mouseup.jsp', cancelClick); }; doScroll(); $(document).bind('mouseup.jsp', cancelClick); return false; } } ); } if (isScrollableH) { horizontalTrack.bind( 'mousedown.jsp', function(e) { if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) { var clickedTrack = $(this), offset = clickedTrack.offset(), direction = e.pageX - offset.left - horizontalDragPosition, scrollTimeout, isFirst = true, doScroll = function() { var offset = clickedTrack.offset(), pos = e.pageX - offset.left - horizontalDragWidth / 2, contentDragX = paneWidth * settings.scrollPagePercent, dragX = dragMaxX * contentDragX / (contentWidth - paneWidth); if (direction < 0) { if (horizontalDragPosition - dragX > pos) { jsp.scrollByX(-contentDragX); } else { positionDragX(pos); } } else if (direction > 0) { if (horizontalDragPosition + dragX < pos) { jsp.scrollByX(contentDragX); } else { positionDragX(pos); } } else { cancelClick(); return; } scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq); isFirst = false; }, cancelClick = function() { scrollTimeout &amp;&amp; clearTimeout(scrollTimeout); scrollTimeout = null; $(document).unbind('mouseup.jsp', cancelClick); }; doScroll(); $(document).bind('mouseup.jsp', cancelClick); return false; } } ); } } function removeClickOnTrack() { if (horizontalTrack) { horizontalTrack.unbind('mousedown.jsp'); } if (verticalTrack) { verticalTrack.unbind('mousedown.jsp'); } } function cancelDrag() { $('html').unbind('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp'); if (verticalDrag) { verticalDrag.removeClass('jspActive'); } if (horizontalDrag) { horizontalDrag.removeClass('jspActive'); } } function positionDragY(destY, animate) { if (!isScrollableV) { return; } if (destY < 0) { destY = 0; } else if (destY > dragMaxY) { destY = dragMaxY; } // can't just check if(animate) because false is a valid value that could be passed in... if (animate === undefined) { animate = settings.animateScroll; } if (animate) { jsp.animate(verticalDrag, 'top', destY, _positionDragY); } else { verticalDrag.css('top', destY); _positionDragY(destY); } } function _positionDragY(destY) { if (destY === undefined) { destY = verticalDrag.position().top; } container.scrollTop(0); verticalDragPosition = destY; var isAtTop = verticalDragPosition === 0, isAtBottom = verticalDragPosition == dragMaxY, percentScrolled = destY/ dragMaxY, destTop = -percentScrolled * (contentHeight - paneHeight); if (wasAtTop != isAtTop || wasAtBottom != isAtBottom) { wasAtTop = isAtTop; wasAtBottom = isAtBottom; elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]); } updateVerticalArrows(isAtTop, isAtBottom); pane.css('top', destTop); elem.trigger('jsp-scroll-y', [-destTop, isAtTop, isAtBottom]).trigger('scroll'); } function positionDragX(destX, animate) { if (!isScrollableH) { return; } if (destX < 0) { destX = 0; } else if (destX > dragMaxX) { destX = dragMaxX; } if (animate === undefined) { animate = settings.animateScroll; } if (animate) { jsp.animate(horizontalDrag, 'left', destX, _positionDragX); } else { horizontalDrag.css('left', destX); _positionDragX(destX); } } function _positionDragX(destX) { if (destX === undefined) { destX = horizontalDrag.position().left; } container.scrollTop(0); horizontalDragPosition = destX; var isAtLeft = horizontalDragPosition === 0, isAtRight = horizontalDragPosition == dragMaxX, percentScrolled = destX / dragMaxX, destLeft = -percentScrolled * (contentWidth - paneWidth); if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) { wasAtLeft = isAtLeft; wasAtRight = isAtRight; elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]); } updateHorizontalArrows(isAtLeft, isAtRight); pane.css('left', destLeft); elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]).trigger('scroll'); } function updateVerticalArrows(isAtTop, isAtBottom) { if (settings.showArrows) { arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled'); arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled'); } } function updateHorizontalArrows(isAtLeft, isAtRight) { if (settings.showArrows) { arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled'); arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled'); } } function scrollToY(destY, animate) { var percentScrolled = destY / (contentHeight - paneHeight); positionDragY(percentScrolled * dragMaxY, animate); } function scrollToX(destX, animate) { var percentScrolled = destX / (contentWidth - paneWidth); positionDragX(percentScrolled * dragMaxX, animate); } function scrollToElement(ele, stickToTop, animate) { var e, eleHeight, eleWidth, eleTop = 0, eleLeft = 0, viewportTop, viewportLeft, maxVisibleEleTop, maxVisibleEleLeft, destY, destX; // Legal hash values aren't necessarily legal jQuery selectors so we need to catch any // errors from the lookup... try { e = $(ele); } catch (err) { return; } eleHeight = e.outerHeight(); eleWidth= e.outerWidth(); container.scrollTop(0); container.scrollLeft(0); // loop through parents adding the offset top of any elements that are relatively positioned between // the focused element and the jspPane so we can get the true distance from the top // of the focused element to the top of the scrollpane... while (!e.is('.jspPane')) { eleTop += e.position().top; eleLeft += e.position().left; e = e.offsetParent(); if (/^body|html$/i.test(e[0].nodeName)) { // we ended up too high in the document structure. Quit! return; } } viewportTop = contentPositionY(); maxVisibleEleTop = viewportTop + paneHeight; if (eleTop < viewportTop || stickToTop) { // element is above viewport destY = eleTop - settings.verticalGutter; } else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport destY = eleTop - paneHeight + eleHeight + settings.verticalGutter; } if (destY) { scrollToY(destY, animate); } viewportLeft = contentPositionX(); maxVisibleEleLeft = viewportLeft + paneWidth; if (eleLeft < viewportLeft || stickToTop) { // element is to the left of viewport destX = eleLeft - settings.horizontalGutter; } else if (eleLeft + eleWidth > maxVisibleEleLeft) { // element is to the right viewport destX = eleLeft - paneWidth + eleWidth + settings.horizontalGutter; } if (destX) { scrollToX(destX, animate); } } function contentPositionX() { return -pane.position().left; } function contentPositionY() { return -pane.position().top; } function isCloseToBottom() { var scrollableHeight = contentHeight - paneHeight; return (scrollableHeight > 20) &amp;&amp; (scrollableHeight - contentPositionY() < 10); } function isCloseToRight() { var scrollableWidth = contentWidth - paneWidth; return (scrollableWidth > 20) &amp;&amp; (scrollableWidth - contentPositionX() < 10); } function initMousewheel() { container.unbind(mwEvent).bind( mwEvent, function (event, delta, deltaX, deltaY) { var dX = horizontalDragPosition, dY = verticalDragPosition; jsp.scrollBy(deltaX * settings.mouseWheelSpeed, -deltaY * settings.mouseWheelSpeed, false); // return true if there was no movement so rest of screen can scroll return dX == horizontalDragPosition &amp;&amp; dY == verticalDragPosition; } ); } function removeMousewheel() { container.unbind(mwEvent); } function nil() { return false; } function initFocusHandler() { pane.find(':input,a').unbind('focus.jsp').bind( 'focus.jsp', function(e) { scrollToElement(e.target, false); } ); } function removeFocusHandler() { pane.find(':input,a').unbind('focus.jsp'); } function initKeyboardNav() { var keyDown, elementHasScrolled, validParents = []; isScrollableH &amp;&amp; validParents.push(horizontalBar[0]); isScrollableV &amp;&amp; validParents.push(verticalBar[0]); // IE also focuses elements that don't have tabindex set. pane.focus( function() { elem.focus(); } ); elem.attr('tabindex', 0) .unbind('keydown.jsp keypress.jsp') .bind( 'keydown.jsp', function(e) { if (e.target !== this &amp;&amp; !(validParents.length &amp;&amp; $(e.target).closest(validParents).length)){ return; } var dX = horizontalDragPosition, dY = verticalDragPosition; switch(e.keyCode) { case 40: // down case 38: // up case 34: // page down case 32: // space case 33: // page up case 39: // right case 37: // left keyDown = e.keyCode; keyDownHandler(); break; case 35: // end scrollToY(contentHeight - paneHeight); keyDown = null; break; case 36: // home scrollToY(0); keyDown = null; break; } elementHasScrolled = e.keyCode == keyDown &amp;&amp; dX != horizontalDragPosition || dY != verticalDragPosition; return !elementHasScrolled; } ).bind( 'keypress.jsp', // For FF/ OSX so that we can cancel the repeat key presses if the JSP scrolls... function(e) { if (e.keyCode == keyDown) { keyDownHandler(); } return !elementHasScrolled; } ); if (settings.hideFocus) { elem.css('outline', 'none'); if ('hideFocus' in container[0]){ elem.attr('hideFocus', true); } } else { elem.css('outline', ''); if ('hideFocus' in container[0]){ elem.attr('hideFocus', false); } } function keyDownHandler() { var dX = horizontalDragPosition, dY = verticalDragPosition; switch(keyDown) { case 40: // down jsp.scrollByY(settings.keyboardSpeed, false); break; case 38: // up jsp.scrollByY(-settings.keyboardSpeed, false); break; case 34: // page down case 32: // space jsp.scrollByY(paneHeight * settings.scrollPagePercent, false); break; case 33: // page up jsp.scrollByY(-paneHeight * settings.scrollPagePercent, false); break; case 39: // right jsp.scrollByX(settings.keyboardSpeed, false); break; case 37: // left jsp.scrollByX(-settings.keyboardSpeed, false); break; } elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition; return elementHasScrolled; } } function removeKeyboardNav() { elem.attr('tabindex', '-1') .removeAttr('tabindex') .unbind('keydown.jsp keypress.jsp'); } function observeHash() { if (location.hash &amp;&amp; location.hash.length > 1) { var e, retryInt, hash = escape(location.hash) // hash must be escaped to prevent XSS ; try { e = $(hash); } catch (err) { return; } if (e.length &amp;&amp; pane.find(hash)) { // nasty workaround but it appears to take a little while before the hash has done its thing // to the rendered page so we just wait until the container's scrollTop has been messed up. if (container.scrollTop() === 0) { retryInt = setInterval( function() { if (container.scrollTop() > 0) { scrollToElement(hash, true); $(document).scrollTop(container.position().top); clearInterval(retryInt); } }, 50 ); } else { scrollToElement(hash, true); $(document).scrollTop(container.position().top); } } } } function unhijackInternalLinks() { $('a.jspHijack').unbind('click.jsp-hijack').removeClass('jspHijack'); } function hijackInternalLinks() { unhijackInternalLinks(); $('a[href^=#]').addClass('jspHijack').bind( 'click.jsp-hijack', function() { var uriParts = this.href.split('#'), hash; if (uriParts.length > 1) { hash = uriParts[1]; if (hash.length > 0 &amp;&amp; pane.find('#' + hash).length > 0) { scrollToElement('#' + hash, true); // Need to return false otherwise things mess up... Would be nice to maybe also scroll // the window to the top of the scrollpane? return false; } } } ); } // Init touch on iPad, iPhone, iPod, Android function initTouch() { var startX, startY, touchStartX, touchStartY, moved, moving = false; container.unbind('touchstart.jsp touchmove.jsp touchend.jsp click.jsp-touchclick').bind( 'touchstart.jsp', function(e) { var touch = e.originalEvent.touches[0]; startX = contentPositionX(); startY = contentPositionY(); touchStartX = touch.pageX; touchStartY = touch.pageY; moved = false; moving = true; } ).bind( 'touchmove.jsp', function(ev) { if(!moving) { return; } var touchPos = ev.originalEvent.touches[0], dX = horizontalDragPosition, dY = verticalDragPosition; jsp.scrollTo(startX + touchStartX - touchPos.pageX, startY + touchStartY - touchPos.pageY); moved = moved || Math.abs(touchStartX - touchPos.pageX) > 5 || Math.abs(touchStartY - touchPos.pageY) > 5; // return true if there was no movement so rest of screen can scroll return dX == horizontalDragPosition &amp;&amp; dY == verticalDragPosition; } ).bind( 'touchend.jsp', function(e) { moving = false; /*if(moved) { return false; }*/ } ).bind( 'click.jsp-touchclick', function(e) { if(moved) { moved = false; return false; } } ); } function destroy(){ var currentY = contentPositionY(), currentX = contentPositionX(); elem.removeClass('jspScrollable').unbind('.jsp'); elem.replaceWith(originalElement.append(pane.children())); originalElement.scrollTop(currentY); originalElement.scrollLeft(currentX); } // Public API $.extend( jsp, { // Reinitialises the scroll pane (if it's internal dimensions have changed since the last time it // was initialised). The settings object which is passed in will override any settings from the // previous time it was initialised - if you don't pass any settings then the ones from the previous // initialisation will be used. reinitialise: function(s) { s = $.extend({}, settings, s); initialise(s); }, // Scrolls the specified element (a jQuery object, DOM node or jQuery selector string) into view so // that it can be seen within the viewport. If stickToTop is true then the element will appear at // the top of the viewport, if it is false then the viewport will scroll as little as possible to // show the element. You can also specify if you want animation to occur. If you don't provide this // argument then the animateScroll value from the settings object is used instead. scrollToElement: function(ele, stickToTop, animate) { scrollToElement(ele, stickToTop, animate); }, // Scrolls the pane so that the specified co-ordinates within the content are at the top left // of the viewport. animate is optional and if not passed then the value of animateScroll from // the settings object this jScrollPane was initialised with is used. scrollTo: function(destX, destY, animate) { scrollToX(destX, animate); scrollToY(destY, animate); }, // Scrolls the pane so that the specified co-ordinate within the content is at the left of the // viewport. animate is optional and if not passed then the value of animateScroll from the settings // object this jScrollPane was initialised with is used. scrollToX: function(destX, animate) { scrollToX(destX, animate); }, // Scrolls the pane so that the specified co-ordinate within the content is at the top of the // viewport. animate is optional and if not passed then the value of animateScroll from the settings // object this jScrollPane was initialised with is used. scrollToY: function(destY, animate) { scrollToY(destY, animate); }, // Scrolls the pane to the specified percentage of its maximum horizontal scroll position. animate // is optional and if not passed then the value of animateScroll from the settings object this // jScrollPane was initialised with is used. scrollToPercentX: function(destPercentX, animate) { scrollToX(destPercentX * (contentWidth - paneWidth), animate); }, // Scrolls the pane to the specified percentage of its maximum vertical scroll position. animate // is optional and if not passed then the value of animateScroll from the settings object this // jScrollPane was initialised with is used. scrollToPercentY: function(destPercentY, animate) { scrollToY(destPercentY * (contentHeight - paneHeight), animate); }, // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then // the value of animateScroll from the settings object this jScrollPane was initialised with is used. scrollBy: function(deltaX, deltaY, animate) { jsp.scrollByX(deltaX, animate); jsp.scrollByY(deltaY, animate); }, // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then // the value of animateScroll from the settings object this jScrollPane was initialised with is used. scrollByX: function(deltaX, animate) { var destX = contentPositionX() + Math[deltaXpercentScrolled = destX / (contentWidth - paneWidth); positionDragX(percentScrolled * dragMaxX, animate); }, // Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then // the value of animateScroll from the settings object this jScrollPane was initialised with is used. scrollByY: function(deltaY, animate) { var destY = contentPositionY() + Math[deltaYpercentScrolled = destY / (contentHeight - paneHeight); positionDragY(percentScrolled * dragMaxY, animate); }, // Positions the horizontal drag at the specified x position (and updates the viewport to reflect // this). animate is optional and if not passed then the value of animateScroll from the settings // object this jScrollPane was initialised with is used. positionDragX: function(x, animate) { positionDragX(x, animate); }, // Positions the vertical drag at the specified y position (and updates the viewport to reflect // this). animate is optional and if not passed then the value of animateScroll from the settings // object this jScrollPane was initialised with is used. positionDragY: function(y, animate) { positionDragY(y, animate); }, // This method is called when jScrollPane is trying to animate to a new position. You can override // it if you want to provide advanced animation functionality. It is passed the following arguments: // * ele - the element whose position is being animated // * prop - the property that is being animated // * value - the value it's being animated to // * stepCallback - a function that you must execute each time you update the value of the property // You can use the default implementation (below) as a starting point for your own implementation. animate: function(ele, prop, value, stepCallback) { var params = {}; params[prop] = value; ele.animate( params, { 'duration' : settings.animateDuration, 'easing' : settings.animateEase, 'queue' : false, 'step' : stepCallback } ); }, // Returns the current x position of the viewport with regards to the content pane. getContentPositionX: function() { return contentPositionX(); }, // Returns the current y position of the viewport with regards to the content pane. getContentPositionY: function() { return contentPositionY(); }, // Returns the width of the content within the scroll pane. getContentWidth: function() { return contentWidth; }, // Returns the height of the content within the scroll pane. getContentHeight: function() { return contentHeight; }, // Returns the horizontal position of the viewport within the pane content. getPercentScrolledX: function() { return contentPositionX() / (contentWidth - paneWidth); }, // Returns the vertical position of the viewport within the pane content. getPercentScrolledY: function() { return contentPositionY() / (contentHeight - paneHeight); }, // Returns whether or not this scrollpane has a horizontal scrollbar. getIsScrollableH: function() { return isScrollableH; }, // Returns whether or not this scrollpane has a vertical scrollbar. getIsScrollableV: function() { return isScrollableV; }, // Gets a reference to the content pane. It is important that you use this method if you want to // edit the content of your jScrollPane as if you access the element directly then you may have some // problems (as your original element has had additional elements for the scrollbars etc added into // it). getContentPane: function() { return pane; }, // Scrolls this jScrollPane down as far as it can currently scroll. If animate isn't passed then the // animateScroll value from settings is used instead. scrollToBottom: function(animate) { positionDragY(dragMaxY, animate); }, // Hijacks the links on the page which link to content inside the scrollpane. If you have changed // the content of your page (e.g. via AJAX) and want to make sure any new anchor links to the // contents of your scroll pane will work then call this function. hijackInternalLinks: function() { hijackInternalLinks(); }, // Removes the jScrollPane and returns the page to the state it was in before jScrollPane was // initialised. destroy: function() { destroy(); } } ); initialise(s); } // Pluginifying code... settings = $.extend({}, $.fn.jScrollPane.defaults, settings); // Apply default speed $.each(['mouseWheelSpeed', 'arrowButtonSpeed', 'trackClickSpeed', 'keyboardSpeed'], function() { settings[this] = settings[this] || settings.speed; }); return this.each( function() { var elem = $(this), jspApi = elem.data('jsp'); if (jspApi) { jspApi.reinitialise(settings); } else { jspApi = new JScrollPane(elem, settings); elem.data('jsp', jspApi); } } ); }; $.fn.jScrollPane.defaults = { showArrows : false, maintainPosition : true, stickToBottom : false, stickToRight : false, clickOnTrack : true, autoReinitialise : false, autoReinitialiseDelay : 500, verticalDragMinHeight : 0, verticalDragMaxHeight : 99999, horizontalDragMinWidth : 0, horizontalDragMaxWidth : 99999, contentWidth : undefined, animateScroll : false, animateDuration : 300, animateEase : 'linear', hijackInternalLinks : false, verticalGutter : 4, horizontalGutter : 4, mouseWheelSpeed : 0, arrowButtonSpeed : 0, arrowRepeatFreq : 50, arrowScrollOnHover : false, trackClickSpeed : 0, trackClickRepeatFreq : 70, verticalArrowPositions : 'split', horizontalArrowPositions : 'split', enableKeyboardNavigation : true, hideFocus : false, keyboardSpeed : 0, initialDelay : 300, // Delay before starting repeating speed : 30, // Default speed when others falsey scrollPagePercent : .8 // Percent of visible area scrolled when pageUp/Down or track area pressed }; })(jQuery,this);
E, logicamente, a biblioteca jQuery.!
Esses são os três arquivos fundamentais para que o seu plugin funcione corretamente. Contudo, o desenvolvedor recomenda o uso dos plugins MouseWheel e MwheelIntent, que permitirão ao usuário utilizar a barra de rolagem através da “rodinha do mouse”.
Na página onde o plugin será aplicado basta adicionar uma chamada para a função jScrollPane(). Não se esqueça de setar, no estilo do seu elemento, como será a rolagem: se será do tipo vertical, horizontal, ambas ou automática. Normalmente, para que as barras sejam exibidas somente se necessário, trabalhe com overflow:auto e seus problemas serão resolvidos!
$(function() { $('#caixa').jScrollPane(); });
Caso você tenha alguma dúvida, consulte o site do desenvolvedor ou faça o download do meu exemplo, com todos os arquivos.