Welcome 微信登录

首页 / 脚本样式 / JavaScript / 原生js实现模拟滚动条

当页面中有很多滚动条,它们相互嵌套,很不好看,这时就会模拟滚动条,并给这个滚动条好看的样式,使得页面美观。
模拟滚动条很多时候是去用jquery插件,然后写几行代码就搞定了。不过随着mvvm的快速发展,很多时候都懒得用jquery了,这就是本文的动机,本屌力求用简单的不依赖jquery只依赖mvvm(avalon) api的代码,完成一个简易的滚动条。
要求:
1.鼠标滚轮可以让滚动条工作,界面滚动
2.鼠标可以拖动滚动条并让界面滚动
3.页面resize时,滚动条根据页面尺寸变化,仍然可以工作
效果:

很显然,这个组件是基于拖动drag的,本屌又不想重新写,就只有改下ui框架的drag了,这里改的是easy js ui的drag组件。用easy js是因为注释比较多,代码简洁。
本屌把easy js ui的drag组件里的相应方法换成avalon api里的方法,删掉prototype里的方法及冗余代码
define("drag",["avalon-min"],function(avalon){function getBoundary(container, target) {var borderTopWidth = 0, borderRightWidth = 0, borderBottomWidth = 0, borderLeftWidth = 0, cOffset = avalon(container).offset(), cOffsetTop = cOffset.top, cOffsetLeft = cOffset.left, tOffset = avalon(target).offset();borderTopWidth = parseFloat(avalon.css(container,"borderTopWidth"));borderRightWidth = parseFloat(avalon.css(container,"borderRightWidth"));borderBottomWidth = parseFloat(avalon.css(container,"borderBottomWidth"));borderLeftWidth = parseFloat(avalon.css(container,"borderLeftWidth"));cOffsetTop = cOffsetTop - tOffset.top + parseFloat(avalon(target).css("top"));cOffsetLeft = cOffsetLeft - tOffset.left + parseFloat(avalon(target).css("left"));return {top : cOffsetTop + borderTopWidth,right : cOffsetLeft + avalon(container).outerWidth() - avalon(target).outerWidth()- borderRightWidth,left : cOffsetLeft + borderLeftWidth,bottom : cOffsetTop + avalon(container).outerHeight() - avalon(target).outerHeight()- borderBottomWidth};}var drag = function(target, options) {var defaults = {axis:null,container:null,handle:null,ondragmove:null};var o =avalon.mix(defaults,options),doc = target.ownerDocument,win = doc.defaultView || doc.parentWindow,originHandle=target,isIE =!-[1,],handle = isIE ? target :doc,container = o.container ?o.container: null, count = 0,drag = this,axis = o.axis,isMove = false, boundary, zIndex, originalX, originalY,clearSelect = "getSelection" in win ? function(){win.getSelection().removeAllRanges();} : function(){try{doc.selection.empty();}catch( e ){};},down = function( e ){o.isDown = true;var newTarget = target,left, top, offset;o.width = avalon(target).outerWidth();o.height = avalon(target).outerHeight();o.handle = handle;left = avalon(newTarget).css( "left" );top = avalon(newTarget).css( "top" ); offset = avalon(newTarget).offset();drag.left = left = parseInt( left );drag.top = top = parseInt( top );drag.offsetLeft = offset.left;drag.offsetTop = offset.top;originalX = e.pageX - left;originalY = e.pageY - top; if( (!boundary && container)){boundary = getBoundary(container, newTarget ); } if( axis ){if( axis === "x" ){originalY = false;}else if( axis === "y" ){originalX = false;}}if( isIE ){handle.setCapture();}avalon.bind(handle,"mousemove",move);avalon.bind(handle,"mouseup",up);if( isIE ){avalon.bind(handle,"losecapture",up);}e.stopPropagation();e.preventDefault();},move = function( e ){if( !o.isDown ){return;}count++;if( count % 2 === 0 ){return;}var currentX = e.pageX,currentY = e.pageY,style = target.style,x, y, left, right, top, bottom;clearSelect();isMove = true;if( originalX ){x = currentX - originalX;if( boundary ){left = boundary.left;right = boundary.right;x = x < left ? left : x > right ? right :x;}drag.left = x;drag.offsetLeft = currentX - e.offsetX;style.left = x + "px";}if( originalY ){y = currentY - originalY;if( boundary ){top = boundary.top;bottom = boundary.bottom;y = y < top ? top : y > bottom ? bottom :y;}drag.top = y;drag.offsetTop = currentY - e.offsetY;style.top = y + "px";}o.ondragmove.call(this,drag);e.stopPropagation();},up = function( e ){o.isDown = false;if( isIE ){avalon.unbind(handle,"losecapture" );}avalon.unbind( handle,"mousemove");avalon.unbind( handle,"mouseup");if( isIE ){handle.releaseCapture();}e.stopPropagation();}; avalon(originHandle).css( "cursor", "pointer" );avalon.bind( originHandle,"mousedown", down );drag.refresh=function(){boundary=getBoundary(container,target);};};return drag;});
另外在最后暴露的drag上加了一个refresh()方法,作用是在resize时,需要更新滚动条可以拖动的范围。这个方法在scrollbar的更新视图中会用到。
drag.refresh=function(){boundary=getBoundary(container,target);}; 
还有在滚动条拖动过程move中,添加一个钩子,允许从外面添加一个监听函数,拖动时会触发监听函数,并传入drag参数。
o.ondragmove.call(this,drag);
然后是scrollbar.js
define("scrollbar",["avalon-min","drag"],function(avalon,drag){function scrollbar(wrap,scrollbar,height_per_scroll){//容器,滚动条,每次滚轮移动的距离this.scroll_height=0;//滚动条高度this.dragger=null;//drag组件实例wrap.scrollTop=0;//容器的位置要减去浏览器最外面的默认滚动条垂直方向位置var self=this,wrap_top=avalon(wrap).offset().top-avalon(document).scrollTop();function ondragmove(drag){//drag组件拖动时的监听函数,更新容器视图wrap.scrollTop=(parseFloat(scrollbar.style.top)-wrap_top)*(wrap.scrollHeight -wrap.clientHeight)/(wrap.clientHeight-self.scroll_height);};function setScrollPosition(o) {//更新滚动条位置scrollbar.style.top =o.scrollTop*wrap.clientHeight/wrap.scrollHeight+wrap_top+ "px";}function inti_events(){avalon.bind(wrap,"mousewheel",function(e){if(e.wheelDelta < 0)wrap.scrollTop+=height_per_scroll;elsewrap.scrollTop-=height_per_scroll;setScrollPosition(wrap);e.preventDefault(); });self.dragger=new drag(scrollbar,{container:wrap,axis:"y",ondragmove:ondragmove});window.onresize=function(){self.refresh_views();self.dragger.refresh();};}this.refresh_views=function(){//更新组件所有部分视图,并暴露供外部调用//容器高度这里设置成浏览器可视部分-容器垂直方向位置,没有考虑容器有border,padding,margin.可根据相应场景修改wrap.style.height=document.documentElement.clientHeight-wrap_top+"px";self.scroll_height=wrap.clientHeight*wrap.clientHeight/wrap.scrollHeight;//容器高度等于滚动条高度,隐藏滚动条if(self.scroll_height==wrap.clientHeight)scrollbar.style.display="none";elsescrollbar.style.display="block";scrollbar.style.height=self.scroll_height+"px";setScrollPosition(wrap);}function init(){self.refresh_views();inti_events();}init();}return scrollbar;});
可以看到,在resize时,调用了drag组件的refresh方法,更新滚动条可以拖动的范围。这里暴露了refresh_views()方法,以应对外部需要手动更新视图的情况。比如,聊天分组的折叠和展开。

这样就完成了简易滚动条。代码很简单,如果出问题需要fix bug或定制的话,也很容易。
以上所述上就是本文的全部内容了,希望大家能够喜欢。