/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la) * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. * * Version: 1.0.4 * */ (function($) { jQuery.fn.extend({ slimScroll: function(options) { var defaults = { wheelStep : 20, width : 'auto', height : '250px', size : '7px', color: '#000', position : 'right', distance : '1px', start : 'top', opacity : .4, alwaysVisible : false, disableFadeOut: false, railVisible : false, railColor : '#333', railOpacity : '0.2', railClass : 'slimScrollRail', barClass : 'slimScrollBar', wrapperClass : 'slimScrollDiv', allowPageScroll : false, scroll : 0, touchScrollStep : 200 }; var o = $.extend(defaults, options); // do it for every element that matches selector this.each(function(){ var isOverPanel, isOverBar, isDragg, queueHide, touchDif, barHeight, percentScroll, lastScroll, divS = '
', minBarHeight = 30, releaseScroll = false; // used in event handlers and for better minification var me = $(this); // ensure we are not binding it again if (me.parent().hasClass('slimScrollDiv')) { // start from last bar position var offset = me.scrollTop(); // find bar and rail bar = me.parent().find('.slimScrollBar'); rail = me.parent().find('.slimScrollRail'); getBarHeight(); // check if we should scroll existing instance if (options) { if ('scrollTo' in options) { // jump to a static point offset = parseInt(o.scrollTo); } else if ('scrollBy' in options) { // jump by value pixels offset += parseInt(o.scrollBy); } // scroll content by the given offset scrollContent(offset, false, true); } return; } // optionally set height to the parent's height o.height = (o.height == 'auto') ? me.parent().innerHeight() : o.height; // wrap content var wrapper = $(divS) .addClass(o.wrapperClass) .css({ position: 'relative', overflow: 'hidden', width: o.width, height: o.height }); // update style for the div me.css({ overflow: 'hidden', width: o.width, height: o.height }); // create scrollbar rail var rail = $(divS) .addClass(o.railClass) .css({ width: o.size, height: '100%', position: 'absolute', top: 0, display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none', 'border-radius': o.size, background: o.railColor, opacity: o.railOpacity, zIndex: 90 }); // create scrollbar var bar = $(divS) .addClass(o.barClass) .css({ background: o.color, width: o.size, position: 'absolute', top: 0, opacity: o.opacity, display: o.alwaysVisible ? 'block' : 'none', 'border-radius' : o.size, BorderRadius: o.size, MozBorderRadius: o.size, WebkitBorderRadius: o.size, zIndex: 99 }); // set position var posCss = (o.position == 'right') ? { right: o.distance } : { left: o.distance }; rail.css(posCss); bar.css(posCss); // wrap it me.wrap(wrapper); // append to parent div me.parent().append(bar); me.parent().append(rail); // make it draggable bar.draggable({ axis: 'y', containment: 'parent', start: function() { isDragg = true; }, stop: function() { isDragg = false; hideBar(); }, drag: function(e) { // scroll content scrollContent(0, $(this).position().top, false); } }); // on rail over rail.hover(function(){ showBar(); }, function(){ hideBar(); }); // on bar over bar.hover(function(){ isOverBar = true; }, function(){ isOverBar = false; }); // show on parent mouseover me.hover(function(){ isOverPanel = true; showBar(); hideBar(); }, function(){ isOverPanel = false; hideBar(); }); // support for mobile me.bind('touchstart', function(e,b){ if (e.originalEvent.touches.length) { // record where touch started touchDif = e.originalEvent.touches[0].pageY; } }); me.bind('touchmove', function(e){ // prevent scrolling the page e.originalEvent.preventDefault(); if (e.originalEvent.touches.length) { // see how far user swiped var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep; // scroll content scrollContent(diff, true); } }); var _onWheel = function(e) { // use mouse wheel only when mouse is over if (!isOverPanel) { return; } var e = e || window.event; var delta = 0; if (e.wheelDelta) { delta = -e.wheelDelta/120; } if (e.detail) { delta = e.detail / 3; } // scroll content scrollContent(delta, true); // stop window scroll if (e.preventDefault && !releaseScroll) { e.preventDefault(); } if (!releaseScroll) { e.returnValue = false; } } function scrollContent(y, isWheel, isJump) { var delta = y; var maxTop = me.outerHeight() - bar.outerHeight(); if (isWheel) { // move bar with mouse wheel delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight(); // move bar, make sure it doesn't go out delta = Math.min(Math.max(delta, 0), maxTop); // scroll the scrollbar bar.css({ top: delta + 'px' }); } // calculate actual scroll amount percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight()); delta = percentScroll * (me[0].scrollHeight - me.outerHeight()); if (isJump) { delta = y; var offsetTop = delta / me[0].scrollHeight * me.outerHeight(); offsetTop = Math.min(Math.max(offsetTop, 0), maxTop); bar.css({ top: offsetTop + 'px' }); } // scroll content me.scrollTop(delta); // ensure bar is visible showBar(); // trigger hide when scroll is stopped hideBar(); } var attachWheel = function() { if (window.addEventListener) { this.addEventListener('DOMMouseScroll', _onWheel, false ); this.addEventListener('mousewheel', _onWheel, false ); } else { document.attachEvent("onmousewheel", _onWheel) } } // attach scroll events attachWheel(); function getBarHeight() { // calculate scrollbar height and make sure it is not too small barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight); bar.css({ height: barHeight + 'px' }); } // set up initial height getBarHeight(); function showBar() { // recalculate bar height getBarHeight(); clearTimeout(queueHide); // when bar reached top or bottom if (percentScroll == ~~ percentScroll) { //release wheel releaseScroll = o.allowPageScroll; // publish approporiate event if (lastScroll != percentScroll) { var msg = (~~percentScroll == 0) ? 'top' : 'bottom'; me.trigger('slimscroll', msg); } } lastScroll = percentScroll; // show only when required if(barHeight >= me.outerHeight()) { //allow window scroll releaseScroll = true; return; } bar.stop(true,true).fadeIn('fast'); if (o.railVisible) { rail.stop(true,true).fadeIn('fast'); } } function hideBar() { // only hide when options allow it if (!o.alwaysVisible) { queueHide = setTimeout(function(){ if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) { bar.fadeOut('slow'); rail.fadeOut('slow'); } }, 1000); } } // check start position if (o.start == 'bottom') { // scroll content to bottom bar.css({ top: me.outerHeight() - bar.outerHeight() }); scrollContent(0, true); } else if (typeof o.start == 'object') { // scroll content scrollContent($(o.start).position().top, null, true); // make sure bar stays hidden if (!o.alwaysVisible) { bar.hide(); } } }); // maintain chainability return this; } }); jQuery.fn.extend({ slimscroll: jQuery.fn.slimScroll }); })(jQuery);