JavaScript实现竖向滚动条的一种思路
设计目标:希望复刻浏览器原生竖向滚动条的功能,并且能做一些个性化配置
测试页面:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="MyScrolly.js"></script> 7 </head> 8 <body> 9 <div id="div_allbase" style="width: 800px;height: 600px;background-color: beige;overflow-y: hidden"> 10 <div id="div_outer" style="width: 700px;height: 500px;background-color: cornflowerblue;overflow-y: auto"> 11 <div id="div_inner1" style="width: 600px;height: 300px;background-color: darkseagreen"> 12 111111111111111111111111111111111111111111111111111111111111111 13 </div> 14 <div id="div_inner2" style="width: 600px;height: 300px;margin-top: 50px;background-color: darkseagreen""> 15 222222222222222222222222222222222222222222222222222222222222222 16 </div> 17 <div id="div_inner3" style="width: 600px;height: 300px;margin-top: 50px;background-color: darkseagreen""> 18 333333333333333333333333333333333333333333333333333333333333333 19 </div> 20 </div> 21 </div> 22 </body> 23 <script> 24 var myScroll=new MyScrolly(document.getElementById("div_outer")//要添加滚动条的元素 25 ,{parentelem:document.getElementById("div_allbase")})//配置参数 26 myScroll.func_count(myScroll);//在innerHTML发生变化或onresize之后重新计算dragbar的长度
//使用这种方式可以为页面中的多个元素配置不同的滚动条样式 27 </script> 28 </html>
代码实现:
1 function MyScrolly(elem,obj_p) 2 { 3 if(elem) 4 { 5 obj_p=obj_p||{}; 6 this.elem=elem; 7 this.func_render=obj_p.func_render||MyScrolly.defaultRender;//滚动条的dom结构 8 this.func_count=obj_p.func_count||MyScrolly.countChildren;//在页面发生变化时滚动条的变化方式 9 this.func_render(elem,this); 10 this.parentelem=obj_p.parentelem||elem;//支持拖拽、释放、禁止选择等动作的外围元素范围,默认设置为document可能效果更好 11 //this.clientY0=this.elem.clientY;//没有用到 12 this.last_clientY=-1; 13 //elem.onload 14 var that=this; 15 //使用页面观察器观察dom变化!!<-兼容性如何??<-html5,并且会导致this被替换为MutationObserver对象,并且不好控制调用条件 16 // var MutationObserver=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver; 17 // //var mo=new MutationObserver(that.countChildren); 18 // var mo=new MutationObserver(function(records){ 19 // that.func_count(that); 20 // }); 21 // this.mo=mo; 22 // var option={ 23 // childList:true, 24 // subtree:true, 25 // } 26 //mo.observe(this.elem,option); 27 28 this.div2.onpointerdown=function (event) {//按下scrollbar 29 that.picked=true; 30 that.last_clientY=event.clientY;//取相对定位的参考点 31 that.parentelem.onselectstart=function(event){//防止在上下滑动时选中div中的文本 32 event.returnValue=false; 33 return false; 34 } 35 } 36 this.parentelem.onpointerup=function (event) { 37 that.picked=false; 38 that.parentelem.onselectstart=null; 39 //that.parentelem.onmousewheel=null; 40 } 41 this.parentelem.onpointerleave=function (event) { 42 that.picked=false; 43 that.parentelem.onselectstart=null; 44 that.parentelem.onmousewheel=null; 45 } 46 this.parentelem.onpointermove=function (event) {//拖动效果在div_allbase范围内均有效 47 if(that.picked==true&&(that.last_clientY>=0)) 48 { 49 //event.preventDefault();//阻止默认的行为发生 50 51 var int_clientY=event.clientY-that.last_clientY;//因为比较难定位elem的初始位置(也许elem自身会发生移动或变形),这里使用相对变化量 52 that.last_clientY=event.clientY; 53 var top_div2=parseInt(that.div2.style.top);//滑块上端到滑轨上端的距离,关于div2等属性的含义见defaultRender方法 54 var int1=top_div2+int_clientY; 55 if((that.outer_height-that.height_scrollbar)<int1) 56 {//如果过于靠下 57 int1=that.outer_height-that.height_scrollbar 58 } 59 if(int1<0) 60 {//如果过于靠上 61 int1=0 62 } 63 64 // if((that.outer_height-that.height_scrollbar)>=(top_div2+int_clientY)&&((top_div2+int_clientY)>=0)) 65 // { 66 that.div2.style.top=int1+"px";//移动滑块 67 var int2=(int1)/(that.outer_height/that.inner_height) 68 that.elem.scrollTop=int2;//滚动元素内容 69 that.div1.style.top=int2+"px";//移动滑轨 70 //console.log(that.elem.scrollTop); 71 // } 72 73 74 } 75 } 76 //this.parentelem.onclick=function(event){ 77 this.parentelem.onmouseenter=function(event){//鼠标滚轮,这里没有兼容火狐 78 that.parentelem.onmousewheel=function(event){ 79 if(that.last_clientY<0) 80 { 81 that.last_clientY=0; 82 } 83 if((that.last_clientY>=0)) 84 { 85 var int_clientY=-event.wheelDelta; 86 var top_div2=parseInt(that.div2.style.top); 87 var int1=top_div2+int_clientY; 88 if((that.outer_height-that.height_scrollbar)<int1) 89 { 90 int1=that.outer_height-that.height_scrollbar 91 } 92 if(int1<0) 93 { 94 int1=0 95 } 96 97 that.div2.style.top=int1+"px"; 98 var int2=(int1)/(that.outer_height/that.inner_height) 99 that.elem.scrollTop=int2; 100 that.div1.style.top=int2+"px"; 101 //console.log(that.elem.scrollTop); 102 103 } 104 } 105 } 106 107 } 108 else { 109 return false; 110 } 111 112 113 } 114 //MyScrolly.prototype 115 //计算容器内部组件的实际高度,并就此调整滚动条显示效果 116 //MyScrolly.prototype.countChildren=function(records){ 117 MyScrolly.countChildren=function(that){ 118 //this=that; 119 var arr=that.elem.childNodes;//如果使用MutationObserver,则这里的this是MutationObserver对象!! 120 var len=arr.length; 121 var sum_height=0; 122 // for(var i=0;i<len;i++)//假设除了滚动条之外的所有元素都是纵向排列的!!《-这里需要递归排列!!?? 123 // {累加元素内部children的高度 124 // var obj=arr[i]; 125 // if(obj.className!="div_myscroll1") 126 // { 127 // var int=obj.offsetHeight; 128 // if(int)//有些textnode的高度可能是undefined!! 129 // { 130 // sum_height+=int; 131 // } 132 // 133 // } 134 // } 135 //考虑到margin,换一种测量思路 136 for(var i=len-1;i>0;i--) 137 { 138 var obj=arr[i]; 139 if(obj.className!="div_myscroll1") 140 { 141 var int=obj.offsetHeight; 142 if(int)//有些textnode的高度可能是undefined!! 143 { 144 sum_height+=int; 145 sum_height+=obj.offsetTop; 146 break; 147 } 148 149 } 150 } 151 that.inner_height=sum_height;//元素内容高度 152 that.outer_height=that.elem.offsetHeight;//元素本身高度 153 console.log("重新测量高度"+that.outer_height+"/"+that.inner_height); 154 that.div2.style.top="0px";//滑块复位 155 that.elem.scrollTop=0; 156 that.clientY0=0; 157 that.picked=false; 158 that.last_clientY=-1;//这里还应该加上取消监听的代码 159 if(that.inner_height<=that.outer_height)//如果不需要显示滚动条 160 { 161 that.div1.style.display="none"; 162 } 163 else { 164 that.div1.style.display="block"; 165 var int=that.outer_height*(that.outer_height/that.inner_height); 166 that.height_scrollbar=int; 167 that.div2.style.height=int+"px"; 168 } 169 } 170 //默认的滚动条样式,也可以在这里使用图片等自定义样式 171 MyScrolly.defaultRender=function(elem,that) 172 { 173 elem.style.position="relative"; 174 elem.style.overflowX="hidden"; 175 elem.style.overflowY="hidden";//取消浏览器自带的滚动条 176 var div1=document.createElement("div");//滑轨 177 div1.className="div_myscroll1"; 178 div1.style.width="10px"; 179 div1.style.backgroundColor="rgb(245,245,245)"; 180 div1.style.position="absolute"; 181 div1.style.right="0px"; 182 div1.style.top="0px"; 183 div1.style.height="100%"; 184 div1.style.zIndex=elem.style.zIndex+10; 185 div1.style.display="none"; 186 that.div1=div1; 187 var div2=document.createElement("div");//滑块 188 div2.className="div_myscroll2"; 189 div2.style.width="10px"; 190 div2.style.backgroundColor="rgb(226,226,226)"; 191 div2.style.position="absolute"; 192 div2.style.right="0px"; 193 div2.style.top="0px"; 194 //div1.style.height="100%"; 195 div2.style.zIndex=elem.style.zIndex+20; 196 that.div2=div2; 197 div1.appendChild(div2); 198 elem.appendChild(div1); 199 }
20201113补充,在实际使用中发现三个问题:
一、如果容器中包含img标签,则需要等待所有img标签加载完毕后计算内容高度,否则img标签高度只会表示为24px(Chrome下的默认加载图标高度),建议为每个img标签设置onload和onerror监听,搭配记录总img数量的计数器确保所有img标签加载完毕后计算内容高度。(为标签设置innerHTML后即可用getElement计算img标签个数)
二、如果将滑轨和容器内容放在同一层次,则需要注意在用innerHTML=""清空容器内容时也会将滚动条一起清理掉,需要在内容填充完毕后重绘滚动条。特别的,如果容器中的标签是否换行受到滑轨宽度影响,则计算内容offsetHeight值时会丢失换行增加的部分,最终导致算出的滚动长度小于真实内容长度,故此建议将变化的内容放在一个尺寸随内容变化的内部容器中,然后让这个内部容器与滑轨平级。
三、在实际使用时发现将parentElem设为document时,虽然事件能够触发响应,但并没有实现预期的滚动效果,替换为外围其他标签后正常,时间有限没有深入研究。