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