/*! 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.3.8 * */ (function($) { $.fn.extend({ slimscroll: function(options) { var defaults = { // width in pixels of the visible scroll area width : 'auto', // height in pixels of the visible scroll area height : '250px', // width in pixels of the scrollbar and rail size : '4px', // scrollbar color, accepts any hex/color value color: '#000', // scrollbar position - left/right position : 'right', // distance in pixels between the side edge and the scrollbar distance : '1px', // default scroll position on load - top / bottom / $('selector') start : 'top', // sets scrollbar opacity opacity : .4, // enables always-on mode for the scrollbar alwaysvisible : false, // check if we should hide the scrollbar when user is hovering over disablefadeout : false, // sets visibility of the rail railvisible : false, // sets rail color railcolor : '#333', // sets rail opacity railopacity : .2, // whether we should use jquery ui draggable to enable bar dragging raildraggable : true, // defautlt css class of the slimscroll rail railclass : 'slimscrollrail', // defautlt css class of the slimscroll bar barclass : 'slimscrollbar', // defautlt css class of the slimscroll wrapper wrapperclass : 'slimscrolldiv', // check if mousewheel should scroll the window if we reach top/bottom allowpagescroll : false, // scroll amount applied to each mouse wheel step wheelstep : 20, // scroll amount applied when user is using gestures touchscrollstep : 200, // sets border radius borderradius: '4px', // sets border radius of the rail railborderradius : '4px' }; 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(o.wrapperclass)) { // start from last bar position var offset = me.scrolltop(); // find bar and rail bar = me.siblings('.' + o.barclass); rail = me.siblings('.' + o.railclass); getbarheight(); // check if we should scroll existing instance if ($.isplainobject(options)) { // pass height: auto to an existing slimscroll object to force a resize after contents have changed if ( 'height' in options && options.height == 'auto' ) { me.parent().css('height', 'auto'); me.css('height', 'auto'); var height = me.parent().parent().height(); me.parent().css('height', height); me.css('height', height); } else if ('height' in options) { var h = options.height; me.parent().css('height', h); me.css('height', h); } 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); } else if ('destroy' in options) { // remove slimscroll elements bar.remove(); rail.remove(); me.unwrap(); return; } // scroll content by the given offset scrollcontent(offset, false, true); } return; } else if ($.isplainobject(options)) { if ('destroy' in options) { return; } } // optionally set height to the parent's height o.height = (o.height == 'auto') ? me.parent().height() : 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.railborderradius, 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.borderradius, borderradius: o.borderradius, mozborderradius: o.borderradius, webkitborderradius: o.borderradius, 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 and no longer dependent on the jqueryui if (o.raildraggable){ bar.bind("mousedown", function(e) { var $doc = $(document); isdragg = true; t = parsefloat(bar.css('top')); pagey = e.pagey; $doc.bind("mousemove.slimscroll", function(e){ currtop = t + e.pagey - pagey; bar.css('top', currtop); scrollcontent(0, bar.position().top, false);// scroll content }); $doc.bind("mouseup.slimscroll", function(e) { isdragg = false;hidebar(); $doc.unbind('.slimscroll'); }); return false; }).bind("selectstart.slimscroll", function(e){ e.stoppropagation(); e.preventdefault(); return 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 if necessary if(!releasescroll) { 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); touchdif = e.originalevent.touches[0].pagey; } }); // set up initial height getbarheight(); // check start position if (o.start === 'bottom') { // scroll content to bottom bar.css({ top: me.outerheight() - bar.outerheight() }); scrollcontent(0, true); } else if (o.start !== 'top') { // assume jquery selector scrollcontent($(o.start).position().top, null, true); // make sure bar stays hidden if (!o.alwaysvisible) { bar.hide(); } } // attach scroll events attachwheel(this); function _onwheel(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; } var target = e.target || e.srctarget || e.srcelement; if ($(target).closest('.' + o.wrapperclass).is(me.parent())) { // scroll content scrollcontent(delta, true); } // stop window scroll if (e.preventdefault && !releasescroll) { e.preventdefault(); } if (!releasescroll) { e.returnvalue = false; } } function scrollcontent(y, iswheel, isjump) { releasescroll = false; 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); // if scrolling down, make sure a fractional change to the // scroll position isn't rounded away when the scrollbar's css is set // this flooring of delta would happened automatically when // bar.css is set below, but we floor here for clarity delta = (y > 0) ? math.ceil(delta) : math.floor(delta); // 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); // fire scrolling event me.trigger('slimscrolling', ~~delta); // ensure bar is visible showbar(); // trigger hide when scroll is stopped hidebar(); } function attachwheel(target) { if (window.addeventlistener) { target.addeventlistener('dommousescroll', _onwheel, false ); target.addeventlistener('mousewheel', _onwheel, false ); } else { document.attachevent("onmousewheel", _onwheel) } } 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' }); // hide scrollbar if content is not long enough var display = barheight == me.outerheight() ? 'none' : 'block'; bar.css({ display: display }); } 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); } } else { releasescroll = false; } 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); } } }); // maintain chainability return this; } }); $.fn.extend({ slimscroll: $.fn.slimscroll }); })(jquery);