js模拟滚动条(不依赖jquery)
转载请注明: TheViper http://www.cnblogs.com/TheViper
当页面中有很多滚动条,它们相互嵌套,很不好看,这时就会模拟滚动条,并给这个滚动条好看的样式,使得页面美观。
模拟滚动条很多时候是去用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里的方法及冗余代码
1 define('drag',['avalon-min'],function(avalon){ 2 function getBoundary(container, target) { 3 var borderTopWidth = 0, borderRightWidth = 0, borderBottomWidth = 0, borderLeftWidth = 0, cOffset = avalon(container) 4 .offset(), cOffsetTop = cOffset.top, cOffsetLeft = cOffset.left, tOffset = avalon(target) 5 .offset(); 6 borderTopWidth = parseFloat(avalon.css(container,'borderTopWidth')); 7 borderRightWidth = parseFloat(avalon.css(container,'borderRightWidth')); 8 borderBottomWidth = parseFloat(avalon.css(container,'borderBottomWidth')); 9 borderLeftWidth = parseFloat(avalon.css(container,'borderLeftWidth')); 10 cOffsetTop = cOffsetTop - tOffset.top + parseFloat(avalon(target).css('top')); 11 cOffsetLeft = cOffsetLeft - tOffset.left + parseFloat(avalon(target).css('left')); 12 return { 13 top : cOffsetTop + borderTopWidth, 14 right : cOffsetLeft + avalon(container).outerWidth() - avalon(target).outerWidth() 15 - borderRightWidth, 16 left : cOffsetLeft + borderLeftWidth, 17 bottom : cOffsetTop + avalon(container).outerHeight() - avalon(target).outerHeight() 18 - borderBottomWidth 19 }; 20 } 21 var drag = function(target, options) { 22 var defaults = { 23 axis:null, 24 container:null, 25 handle:null, 26 ondragmove:null 27 }; 28 var o =avalon.mix(defaults,options), 29 doc = target.ownerDocument, 30 win = doc.defaultView || doc.parentWindow, 31 originHandle=target, 32 isIE =!-[1,], 33 handle = isIE ? target :doc, 34 container = o.container ?o.container: null, 35 count = 0, 36 drag = this, 37 axis = o.axis, 38 isMove = false, 39 boundary, zIndex, originalX, originalY, 40 clearSelect = 'getSelection' in win ? function(){ 41 win.getSelection().removeAllRanges(); 42 } : function(){ 43 try{ 44 doc.selection.empty(); 45 } 46 catch( e ){}; 47 }, 48 down = function( e ){ 49 o.isDown = true; 50 var newTarget = target, 51 left, top, offset; 52 o.width = avalon(target).outerWidth(); 53 o.height = avalon(target).outerHeight(); 54 o.handle = handle; 55 left = avalon(newTarget).css( 'left' ); 56 top = avalon(newTarget).css( 'top' ); 57 offset = avalon(newTarget).offset(); 58 drag.left = left = parseInt( left ); 59 drag.top = top = parseInt( top ); 60 drag.offsetLeft = offset.left; 61 drag.offsetTop = offset.top; 62 originalX = e.pageX - left; 63 originalY = e.pageY - top; 64 if( (!boundary && container)){ 65 boundary = getBoundary(container, newTarget ); 66 } 67 if( axis ){ 68 if( axis === 'x' ){ 69 originalY = false; 70 } 71 else if( axis === 'y' ){ 72 originalX = false; 73 } 74 } 75 if( isIE ){ 76 handle.setCapture(); 77 } 78 avalon.bind(handle,'mousemove',move); 79 avalon.bind(handle,'mouseup',up); 80 if( isIE ){ 81 avalon.bind(handle,'losecapture',up); 82 } 83 e.stopPropagation(); 84 e.preventDefault(); 85 }, 86 move = function( e ){ 87 if( !o.isDown ){ 88 return; 89 } 90 count++; 91 if( count % 2 === 0 ){ 92 return; 93 } 94 var currentX = e.pageX, 95 currentY = e.pageY, 96 style = target.style, 97 x, y, left, right, top, bottom; 98 clearSelect(); 99 isMove = true; 100 if( originalX ){ 101 x = currentX - originalX; 102 if( boundary ){ 103 left = boundary.left; 104 right = boundary.right; 105 x = x < left ? left : 106 x > right ? right : 107 x; 108 } 109 drag.left = x; 110 drag.offsetLeft = currentX - e.offsetX; 111 style.left = x + 'px'; 112 } 113 if( originalY ){ 114 y = currentY - originalY; 115 if( boundary ){ 116 top = boundary.top; 117 bottom = boundary.bottom; 118 y = y < top ? top : 119 y > bottom ? bottom : 120 y; 121 } 122 drag.top = y; 123 drag.offsetTop = currentY - e.offsetY; 124 style.top = y + 'px'; 125 } 126 o.ondragmove.call(this,drag); 127 e.stopPropagation(); 128 }, 129 up = function( e ){ 130 o.isDown = false; 131 if( isIE ){ 132 avalon.unbind(handle,'losecapture' ); 133 } 134 avalon.unbind( handle,'mousemove'); 135 avalon.unbind( handle,'mouseup'); 136 if( isIE ){ 137 handle.releaseCapture(); 138 } 139 e.stopPropagation(); 140 }; 141 avalon(originHandle).css( 'cursor', 'pointer' ); 142 avalon.bind( originHandle,'mousedown', down ); 143 drag.refresh=function(){ 144 boundary=getBoundary(container,target); 145 }; 146 }; 147 return drag; 148 });
另外在最后暴露的drag上加了一个refresh()方法,作用是在resize时,需要更新滚动条可以拖动的范围。这个方法在scrollbar的更新视图中会用到。
drag.refresh=function(){ boundary=getBoundary(container,target); };
还有在滚动条拖动过程move中,添加一个钩子,允许从外面添加一个监听函数,拖动时会触发监听函数,并传入drag参数。
o.ondragmove.call(this,drag);
然后是scrollbar.js
1 define('scrollbar',['avalon-min','drag'],function(avalon,drag){ 2 function scrollbar(wrap,scrollbar,height_per_scroll){//容器,滚动条,每次滚轮移动的距离 3 this.scroll_height=0;//滚动条高度 4 this.dragger=null;//drag组件实例 5 wrap.scrollTop=0; 6 //position()获取目标节点的所有父节点中,离目标节点最近且position属性是relative,absolute或fixed的父亲节点与目标节点的位置差, 7 //由于是相对的,所以不用考虑滚动条 8 var self=this,wrap_top=avalon(wrap).position().top; 9 function ondragmove(drag){//drag组件拖动时的监听函数,更新容器视图 10 wrap.scrollTop=(parseFloat(scrollbar.style.top)-wrap_top)* 11 (wrap.scrollHeight -wrap.clientHeight)/(wrap.clientHeight-self.scroll_height); 12 }; 13 function setScrollPosition(o) {//更新滚动条位置 14 scrollbar.style.top =o.scrollTop*wrap.clientHeight/wrap.scrollHeight+wrap_top+ 'px'; 15 } 16 function inti_events(){ 17 avalon.bind(wrap,'mousewheel',function(e){ 18 if(e.wheelDelta < 0) 19 wrap.scrollTop+=height_per_scroll; 20 else 21 wrap.scrollTop-=height_per_scroll; 22 setScrollPosition(wrap); 23 e.preventDefault(); 24 }); 25 self.dragger=new drag(scrollbar,{container:wrap,axis:'y',ondragmove:ondragmove}); 26 window.onresize=function(){ 27 self.refresh_views(); 28 self.dragger.refresh(); 29 }; 30 } 31 this.refresh_views=function(){//更新组件所有部分视图,并暴露供外部调用 32 //容器高度这里设置成浏览器可视部分-容器垂直方向位置,没有考虑容器有border,padding,margin.可根据相应场景修改 33 wrap.style.height=document.documentElement.clientHeight-wrap_top+'px'; 34 self.scroll_height=wrap.clientHeight*wrap.clientHeight/wrap.scrollHeight; 35 //容器高度等于滚动条高度,隐藏滚动条 36 if(self.scroll_height==wrap.clientHeight) 37 scrollbar.style.display='none'; 38 else 39 scrollbar.style.display='block'; 40 scrollbar.style.height=self.scroll_height+'px'; 41 setScrollPosition(wrap); 42 } 43 function init(){ 44 self.refresh_views(); 45 inti_events(); 46 } 47 init(); 48 } 49 return scrollbar; 50 });
可以看到,在resize时,调用了drag组件的refresh方法,更新滚动条可以拖动的范围。这里暴露了refresh_views()方法,以应对外部需要手动更新视图的情况。比如,聊天分组的折叠和展开。
注意这里没有考虑容器存在margin,padding,border的情况,如果需要请自行修改。
这样就完成了简易滚动条。代码很简单,如果出问题需要fix bug或定制的话,也很容易。
最后附上下载
posted on 2015-06-14 14:46 TheViper_ 阅读(1293) 评论(0) 编辑 收藏 举报