/* copyright (c) 2006 kelvin luck (kelvin at kelvinluck dot com || http://www.kelvinluck.com) * dual licensed under the mit (http://www.opensource.org/licenses/mit-license.php) * and gpl (http://www.opensource.org/licenses/gpl-license.php) licenses. * * see http://kelvinluck.com/assets/jquery/jscrollpane/ * $id: jscrollpane.js 33 2008-12-10 22:55:28z kelvin.luck $ */ /** * replace the vertical scroll bars on any matched elements with a fancy * styleable (via css) version. with js disabled the elements will * gracefully degrade to the browsers own implementation of overflow:auto. * if the mousewheel plugin has been included on the page then the scrollable areas will also * respond to the mouse wheel. * * @example jquery(".scroll-pane").jscrollpane(); * * @name jscrollpane * @type jquery * @param object settings hash with options, described below. * scrollbarwidth - 在像素宽度所产生的滚动 * scrollbarmargin - 离开滚动条的一侧,以像素为单位的空间量 * wheelspeed - 以像素为单位,该小组将在滚动鼠标滚轮的速度 * showarrows - 是否为用户显示的箭头滚动 * arrowsize - 上下箭头中间滚动条距顶部高度,如果showarrows= true * animateto - 当动画时调用scrollto和scrollby * dragminheight - 允许拖动栏的最小高度 * dragmaxheight - 允许拖动栏的最大高度 * animateinterval - 以毫秒为单位的时间间隔来更新动画的jscrollpane(默认为100) * animatestep - 动画过程滚动距离(默认3) * maintainposition- 无论你想滚动窗格中的内容,以保持它的立场,当你重新初始化它 - 所以它不会滚动,当您添加更多的内容(默认为true) * scrollbaronleft - 显示在左边的滚动条? (需要样式表的变化,看到examples.html) * reinitialiseonimageload - 当jscrollpane中应自动重新初始化本身包含的任何图像加载时 * @return jquery * @cat plugins/jscrollpane * @author kelvin luck (kelvin at kelvinluck dot com || http://www.kelvinluck.com) */ (function($) { $.jscrollpane = { active : [] }; $.fn.jscrollpane = function(settings) { settings = $.extend({}, $.fn.jscrollpane.defaults, settings); var rf = function() { return false; }; return this.each( function() { var $this = $(this); // 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] $this.css('overflow', 'hidden'); var paneele = this; if ($(this).parent().is('.jscrollpanecontainer')) { var currentscrollposition = settings.maintainposition ? $this.position().top : 0; var $c = $(this).parent(); var panewidth = $c.innerwidth(); var paneheight = $c.outerheight(); var trackheight = paneheight; $('>.jscrollpanetrack, >.jscrollarrowup, >.jscrollarrowdown', $c).remove(); $this.css({'top':0}); } else { var currentscrollposition = 0; this.originalpadding = $this.css('paddingtop') + ' ' + $this.css('paddingright') + ' ' + $this.css('paddingbottom') + ' ' + $this.css('paddingleft'); this.originalsidepaddingtotal = (parseint($this.css('paddingleft')) || 0) + (parseint($this.css('paddingright')) || 0); var panewidth = $this.innerwidth(); var paneheight = $this.innerheight(); var trackheight = paneheight; $this.wrap( $('
').attr( {'classname':'jscrollpanecontainer'} ).css( { 'height':paneheight+'px', 'width':panewidth+'px' } ) ); // deal with text size changes (if the jquery.em plugin is included) // and re-initialise the scrollpane so the track maintains the // correct size $(document).bind( 'emchange', function(e, cur, prev) { $this.jscrollpane(settings); } ); } if (settings.reinitialiseonimageload) { // code inspired by jquery.onimagesload: http://plugins.jquery.com/project/onimagesload // except we re-initialise the scroll pane when each image loads so that the scroll pane is always up to size... // 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? var $imagestoload = $.data(paneele, 'jscrollpaneimagestoload') || $('img', $this); var loadedimages = []; if ($imagestoload.length) { $imagestoload.each(function(i, val) { $(this).bind('load', function() { if($.inarray(i, loadedimages) == -1){ //don't double count images loadedimages.push(val); //keep a record of images we've seen $imagestoload = $.grep($imagestoload, function(n, i) { return n != val; }); $.data(paneele, 'jscrollpaneimagestoload', $imagestoload); settings.reinitialiseonimageload = false; $this.jscrollpane(settings); // re-initialise } }).each(function(i, val) { if(this.complete || this.complete===undefined) { //needed for potential cached images this.src = this.src; } }); }); }; } var p = this.originalsidepaddingtotal; var csstoapply = { 'height':'auto', 'width':panewidth - settings.scrollbarwidth - settings.scrollbarmargin - p + 'px' } if(settings.scrollbaronleft) { csstoapply.paddingleft = settings.scrollbarmargin + settings.scrollbarwidth + 'px'; } else { csstoapply.paddingright = settings.scrollbarmargin + 'px'; } $this.css(csstoapply); var contentheight = $this.outerheight(); var percentinview = paneheight / contentheight; if (percentinview < .99) { var $container = $this.parent(); $container.append( $('
').attr({'classname':'jscrollpanetrack'}).css({'width':settings.scrollbarwidth+'px'}).append( $('
').attr({'classname':'jscrollpanedrag'}).css({'width':settings.scrollbarwidth+'px'}).append( $('
').attr({'classname':'jscrollpanedragtop'}).css({'width':settings.scrollbarwidth+'px'}), $('
').attr({'classname':'jscrollpanedragbottom'}).css({'width':settings.scrollbarwidth+'px'}) ) ) ); var $track = $('>.jscrollpanetrack', $container); var $drag = $('>.jscrollpanetrack .jscrollpanedrag', $container); if (settings.showarrows) { var currentarrowbutton; var currentarrowdirection; var currentarrowinterval; var currentarrowinc; var whilearrowbuttondown = function() { if (currentarrowinc > 4 || currentarrowinc%4==0) { positiondrag(dragposition + currentarrowdirection * mousewheelmultiplier); } currentarrowinc ++; }; var onarrowmouseup = function(event) { $('html').unbind('mouseup', onarrowmouseup); currentarrowbutton.removeclass('jscrollactivearrowbutton'); clearinterval(currentarrowinterval); }; var onarrowmousedown = function() { $('html').bind('mouseup', onarrowmouseup); currentarrowbutton.addclass('jscrollactivearrowbutton'); currentarrowinc = 0; whilearrowbuttondown(); currentarrowinterval = setinterval(whilearrowbuttondown, 100); }; $container .append( $('') .attr({'href':'javascript:;', 'classname':'jscrollarrowup'}) .css({'width':settings.scrollbarwidth+'px'}) .html('scroll up') .bind('mousedown', function() { currentarrowbutton = $(this); currentarrowdirection = -1; onarrowmousedown(); this.blur(); return false; }) .bind('click', rf), $('') .attr({'href':'javascript:;', 'classname':'jscrollarrowdown'}) .css({'width':settings.scrollbarwidth+'px'}) .html('scroll down') .bind('mousedown', function() { currentarrowbutton = $(this); currentarrowdirection = 1; onarrowmousedown(); this.blur(); return false; }) .bind('click', rf) ); var $uparrow = $('>.jscrollarrowup', $container); var $downarrow = $('>.jscrollarrowdown', $container); if (settings.arrowsize) { trackheight = paneheight - settings.arrowsize - settings.arrowsize; $track .css({'height': trackheight+'px', top:settings.arrowsize+'px'}) } else { var toparrowheight = $uparrow.height(); settings.arrowsize = toparrowheight; trackheight = paneheight - toparrowheight - $downarrow.height(); $track .css({'height': trackheight+'px', top:toparrowheight+'px'}) } } var $pane = $(this).css({'position':'absolute', 'overflow':'visible'}); var currentoffset; var maxy; var mousewheelmultiplier; // store this in a seperate variable so we can keep track more accurately than just updating the css property.. var dragposition = 0; var dragmiddle = percentinview*paneheight/2; // pos function borrowed from tooltip plugin and adapted... var getpos = function (event, c) { var p = c == 'x' ? 'left' : 'top'; return event['page' + c] || (event['client' + c] + (document.documentelement['scroll' + p] || document.body['scroll' + p])) || 0; }; var ignorenativedrag = function() { return false; }; var initdrag = function() { ceaseanimation(); currentoffset = $drag.offset(false); currentoffset.top -= dragposition; maxy = trackheight - $drag[0].offsetheight; mousewheelmultiplier = 2 * settings.wheelspeed * maxy / contentheight; }; var onstartdrag = function(event) { initdrag(); dragmiddle = getpos(event, 'y') - dragposition - currentoffset.top; $('html').bind('mouseup', onstopdrag).bind('mousemove', updatescroll); if ($.browser.msie) { $('html').bind('dragstart', ignorenativedrag).bind('selectstart', ignorenativedrag); } return false; }; var onstopdrag = function() { $('html').unbind('mouseup', onstopdrag).unbind('mousemove', updatescroll); dragmiddle = percentinview*paneheight/2; if ($.browser.msie) { $('html').unbind('dragstart', ignorenativedrag).unbind('selectstart', ignorenativedrag); } }; var positiondrag = function(desty) { desty = desty < 0 ? 0 : (desty > maxy ? maxy : desty); dragposition = desty; $drag.css({'top':desty+'px'}); var p = desty / maxy; $pane.css({'top':((paneheight-contentheight)*p) + 'px'}); $this.trigger('scroll'); if (settings.showarrows) { $uparrow[desty == 0 ? 'addclass' : 'removeclass']('disabled'); $downarrow[desty == maxy ? 'addclass' : 'removeclass']('disabled'); } }; var updatescroll = function(e) { positiondrag(getpos(e, 'y') - currentoffset.top - dragmiddle); }; var dragh = math.max(math.min(percentinview*(paneheight-settings.arrowsize*2), settings.dragmaxheight), settings.dragminheight); $drag.css( {'height':dragh+'px'} ).bind('mousedown', onstartdrag); var trackscrollinterval; var trackscrollinc; var trackscrollmousepos; var dotrackscroll = function() { if (trackscrollinc > 8 || trackscrollinc%4==0) { positiondrag((dragposition - ((dragposition - trackscrollmousepos) / 2))); } trackscrollinc ++; }; var onstoptrackclick = function() { clearinterval(trackscrollinterval); $('html').unbind('mouseup', onstoptrackclick).unbind('mousemove', ontrackmousemove); }; var ontrackmousemove = function(event) { trackscrollmousepos = getpos(event, 'y') - currentoffset.top - dragmiddle; }; var ontrackclick = function(event) { initdrag(); ontrackmousemove(event); trackscrollinc = 0; $('html').bind('mouseup', onstoptrackclick).bind('mousemove', ontrackmousemove); trackscrollinterval = setinterval(dotrackscroll, 100); dotrackscroll(); }; $track.bind('mousedown', ontrackclick); $container.bind( 'mousewheel', function (event, delta) { initdrag(); ceaseanimation(); var d = dragposition; positiondrag(dragposition - delta * mousewheelmultiplier); var dragoccured = d != dragposition; return !dragoccured; } ); var _animatetoposition; var _animatetointerval; function animatetoposition() { var diff = (_animatetoposition - dragposition) / settings.animatestep; if (diff > 1 || diff < -1) { positiondrag(dragposition + diff); } else { positiondrag(_animatetoposition); ceaseanimation(); } } var ceaseanimation = function() { if (_animatetointerval) { clearinterval(_animatetointerval); delete _animatetoposition; } }; var scrollto = function(pos, preventani) { if (typeof pos == "string") { $e = $(pos, $this); if (!$e.length) return; pos = $e.offset().top - $this.offset().top; } $container.scrolltop(0); ceaseanimation(); var destdragposition = -pos/(paneheight-contentheight) * maxy; if (preventani || !settings.animateto) { positiondrag(destdragposition); } else { _animatetoposition = destdragposition; _animatetointerval = setinterval(animatetoposition, settings.animateinterval); } }; $this[0].scrollto = scrollto; $this[0].scrollby = function(delta) { var currentpos = -parseint($pane.css('top')) || 0; scrollto(currentpos + delta); }; initdrag(); scrollto(-currentscrollposition, true); // deal with it when the user tabs to a link or form element within this scrollpane $('*', this).bind( 'focus', function(event) { var $e = $(this); // loop through parents adding the offset top of any elements that are relatively positioned between // the focused element and the jscrollpanecontainer so we can get the true distance from the top // of the focused element to the top of the scrollpane... var eletop = 0; while ($e[0] != $this[0]) { eletop += $e.position().top; $e = $e.offsetparent(); } var viewporttop = -parseint($pane.css('top')) || 0; var maxvisibleeletop = viewporttop + paneheight; var eleinview = eletop > viewporttop && eletop < maxvisibleeletop; if (!eleinview) { var destpos = eletop - settings.scrollbarmargin; if (eletop > viewporttop) { // element is below viewport - scroll so it is at bottom. destpos += $(this).height() + 15 + settings.scrollbarmargin - paneheight; } scrollto(destpos); } } ) if (location.hash) { scrollto(location.hash); } // use event delegation to listen for all clicks on links and hijack them if they are links to // anchors within our content... $(document).bind( 'click', function(e) { $target = $(e.target); if ($target.is('a')) { var h = $target.attr('href'); if (h.substr(0, 1) == '#') { scrollto(h); } } } ); $.jscrollpane.active.push($this[0]); } else { $this.css( { 'height':paneheight+'px', 'width':panewidth-this.originalsidepaddingtotal+'px', 'padding':this.originalpadding } ); // remove from active list? $this.parent().unbind('mousewheel'); } } ) }; $.fn.jscrollpane.defaults = { scrollbarwidth : 10, scrollbarmargin : 5, wheelspeed : 18, showarrows : false, arrowsize : 0, animateto : false, dragminheight : 1, dragmaxheight : 99999, animateinterval : 100, animatestep: 3, maintainposition: true, scrollbaronleft: false, reinitialiseonimageload: false }; // clean up the scrollto expandos $(window) .bind('unload', function() { var els = $.jscrollpane.active; for (var i=0; i