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时,虽然事件能够触发响应,但并没有实现预期的滚动效果,替换为外围其他标签后正常,时间有限没有深入研究。

posted @ 2020-11-10 16:21  ljzc002  阅读(856)  评论(0编辑  收藏  举报