分享一则JavaScript滚动条插件源码
这是过年的时候自己写的js滚动条插件的源码,做出的效果自己并不满意,正因为做的并不满意所以回头重新巩固和深入学习js,这个插件有如下几个不太满意的地方:
内容的过度效果,可以参阅QQ客户端最近会话列表里的滚动条,它的滚动非常的平滑,简单的说就是缺少动画过渡效果。
并不算完美的兼容性,在IE6、7下的style仍然有点缺憾。
样式的不完美,例如鼠标悬浮才显示滚动条,移除后隐藏这种效果都没有写。
内部结构的混乱,需要调整内容结构。
滚动条那个图片毕竟不是美工,自己切图切的真是恶心到爆了...囧
总体来说还是可以看的,还是缺少一个动画。在写这个插件意识到自己的插件用到了一些比较基础的函数,于是想到把这些函数应该封装起来,最近仍然在深入学习js,把手头上这本书看完就应该着手写这个基础函数的插件了,当然,动画引擎必不可少。话不多说,源码在此(注意:本插件完整版的是有图片的,请在文末附件中下载完整的插件):
CSS
.lf_Scroll,.lf_Scrollli{padding:0;margin:0;list-style:none;font:14px/24px"HelveticaNeue",Helvetica,Arial,'MicrosoftYahei',sans-serif;outline:none;} .lf_Scroll{cursor:pointer;width:10px;position:absolute;right:0;top:0;filter:alpha(opacity=50);-moz-opacity:0.5;-khtml-opacity:0.5;opacity:0.5;} .lf_ScrollFocus{filter:alpha(opacity=100);-moz-opacity:1;-khtml-opacity:1;opacity:1;} .lfs_Top,.lfs_Center,.lfs_Bottom{background:url('ScrollBar.gif');width:10px;height:10px;} .lfs_Top{background-position:1px0px;} .lfs_Center{background-position:center0;height:100px;} .lfs_Bottom{background-position:-22px0;} /*Developersconfig*/ .rollDiv{height:100%;width:100%;overflow:hidden;position:relative;}
JavaScript
/* *ThispluginisdefinedonthesimulationWebpagescrollbar,pleaseinsertafterbindingforDOMevents * *Commentversion:1.0.0 *Author:linkfly *Sina:为你聚焦半世纪| cnblogs:http://www.cnblogs.com/silin6/|Email:linkFly6@live.com *date:2014-02-0502:38:35 * * *DuallicensedundertheMITandGPLlicenses: *http://www.opensource.org/licenses/mit-license.php *http://www.gnu.org/licenses/gpl.html * */ (function(window,undefined){ //配置参数信息 varconfig={ auto:true, height:'auto', width:'auto' }; varlinkFlyScroll=function(dom,options){ ///<summary> /// 1:生成模拟滚动条对象,【请在本对象工作之后再为您指定的对象绑定事件,否则您之前绑定的事件将不会进行工作】 /// 1.1-linkFlyScroll(dom)-在指定的dom上生成滚动条对象 /// 1.2-linkFlyScroll(dom,options)-生成滚动条对象,同时提供一系列的参数允许您自定义配置该对象的工作模型 ///</summary> ///<paramname="dom"type="StringOrelement"> /// 传入js的dom对象,或者为string类型的该对象ID ///</param> ///<paramname="options"type="Json"> /// 自定义配置该对象的工作模型,有如下选项: /// [可选]auto(Boolean):当内容并未达到容器的高度的时候,是否自动隐藏滚动条,默认为true(是) /// [可选]height(IntOrString):默认单位为px,可以为int和String.值为auto则默认采用css的高度 /// [可选]width(IntOrString):默认单位为px,可以为int和String.值为auto则默认采用css的宽度 ///</param> ///<returnstype="linkFlyScroll"/> if(typeof(dom)==='string'){ dom=document.getElementById(dom); } //没有指定dom和没有查找到有效的dom //linkFlyScroll("")、linkFlyScroll(null)、linkFlyScroll(undefined) if(!dom||!dom.nodeType) returnthis; //创建容器对象 varscrollObj=document.createElement('div'); //深度克隆内容对象,并未包含事件,所以需要等到linkFlyScroll对象工作完毕后才可以为该dom对象绑定事件 varcloneObj=dom.cloneNode(true); scrollObj.className='rollDiv'; scrollObj.appendChild(cloneObj); //替换页面上当前对象 dom.parentNode.replaceChild(scrollObj,dom); returnnewlinkFlyScroll.prototype.init(scrollObj,options?options:{}); }; linkFlyScroll.prototype.init=function(dom,options){ ///<summary> /// 1:本对象才是真正意义上工作的对象,特殊的工作方式是因为可能存在linkFlyScroll的静态调用和实例化调用 /// 1.1-init(dom,options)-在指定的dom上生成滚动条对象 ///</summary> ///<paramname="dom"type="element"> /// dom对象 ///</param> ///<paramname="options"type="Json"> /// 自定义配置该对象的工作模型,有如下选项: /// [可选]auto(Boolean):当内容并未达到容器的高度的时候,是否自动隐藏滚动条,默认为true(是) /// [可选]height(IntOrString):默认单位为px,可以为int和String.值为auto则默认采用css的高度 /// [可选]width(IntOrString):默认单位为px,可以为int和String.值为auto则默认采用css的宽度 ///</param> ///<returnstype="linkFlyScroll"/> /* *本对象包含以下属性: *isDrag:是否正在拖拽滚动条 *startTop:(工作中)滚动条开始滚动位置 *endTop:(工作中)滚动条结束滚动位置 *topLimit:滚动条顶部极限位置 *bottomLimit:滚动条底部极限位置 *context:内容Dom *scrollRadix:滚动基数 *target:容器Dom */ //当前this对象,为防止this指针在环境中会经常改变(例如绑定事件的时候),所以将当前对象保存起来 varcurrScroll=this; //DOMElement if(dom.nodeType){ //保存容器和内容DOM currScroll.target=dom; currScroll.context=dom.firstChild; //合并配置参数 currScroll.options=tool.extend(config,options); if(currScroll.options.width!=='auto'){ dom.style.width=tool.convertValue(currScroll.options.width); } if(currScroll.options.height!=='auto'){ dom.style.height=tool.convertValue(currScroll.options.height); } //查找到有效的dom while(currScroll.context.nodeType!=1){ currScroll.context=currScroll.context.nextSibling; } //创建滚动条dom currScroll.scrollUl=document.createElement('ul'); currScroll.scrollUl.className='lf_Scroll'; currScroll.scrollUl.appendChild(tool.setClass('li','lfs_Top')); currScroll.scrollUl.appendChild(tool.setClass('li','lfs_Center')); currScroll.scrollUl.appendChild(tool.setClass('li','lfs_Bottom')); currScroll.context.style.position='relative'; //先呈现在页面上才可以读取位置数据 dom.appendChild(currScroll.scrollUl); this.change(); tool.addScrollEvent(currScroll.context,function(e){ //绑定鼠标滚轮事件,3px滚动单位 if(e.wheel>0){//滚轮向上滚动 varcurrTop=currScroll.endTop-=3; currScroll.scrollEvent.call(currScroll,currTop); }else{//滚轮向下滚动 varcurrTop=currScroll.endTop+=3; currScroll.scrollEvent.call(currScroll,currTop); } }); //需要处理禁止文字在拖动的时候被选中 TODO //鼠标点下事件,需要判断是否是左键点击,目前右键也会实现滚动 TODO tool.addEvent(currScroll.scrollUl,'mousedown',function(e){ mouseDown.call(currScroll,e); }); //追加事件,为了更好的用户体验在body上实现监听 tool.addEvent(document.body,'mousemove',function(e){ if(currScroll.isDrag){ //获取当前鼠标位置 varposition=tool.getMousePos(e); //当前滚动条top位置 varcurrTop=(currScroll.endTop+position.y-currScroll.startTop); //call是为了让this指针准确的指向本工作对象 currScroll.scrollEvent.call(currScroll,currTop); } returnfalse; }); //追加鼠标释放事件,为了准确的捕捉到释放事件在body上监听 tool.addEvent(document.body,'mouseup',function(){ mouseUp.call(currScroll,[]); }); varmouseDown=function(e){ ///<summary> /// 1:鼠标按下事件 /// 1.1-mouseDown(e)-滚动条中鼠标按下滚动条事件 ///</summary> ///<paramname="e"type="Event"> /// Event对象 ///</param> ///<returnstype="linkFlyScroll"/> currScroll.isDrag=true; //获取当前鼠标y位置 currScroll.startTop=tool.getMousePos(e).y; tool.addClass(currScroll.scrollUl,'lf_ScrollFocus'); returnfalse; }; varmouseUp=function(){ ///<summary> /// 1:鼠标释放事件 /// 1.1-mouseUp()-滚动条中鼠标释放滚动条事件 ///</summary> ///<returnstype="linkFlyScroll"/> currScroll.isDrag=false; currScroll.endTop=currScroll.scrollUl.style.top?parseInt(currScroll.scrollUl.style.top):0; tool.removeClass(currScroll.scrollUl,'lf_ScrollFocus'); returnfalse; }; currScroll.scrollEvent=function(currTop){ ///<summary> /// 1:滚动事件(核心),传入需要滚动的坐标即可(滚动条top) /// 1.1-scrollEvent(currTop)-核心滚动事件 ///</summary> ///<paramname="currTop"type="Int"> /// 滚动条顶部距离上一层容器的top值 ///</param> ///<returnstype="void"/> if(currTop<=currScroll.topLimit||currTop<0){//顶部极限 currTop=currScroll.topLimit; }elseif(currTop>=currScroll.bottomLimit){//底部极限 currTop=currScroll.bottomLimit; } //滚动条偏移效果 currScroll.scrollUl.style.top=currTop+'px'; vartempTop=parseInt(currScroll.context.style.top?currScroll.context.style.top:0); //debugcode // document.getElementById('postionInfo').innerHTML='currTop:'+currTop+'滚动基数:'+currScroll.scrollRadix+'bottomLimit:'+currScroll.bottomLimit+'endTop:'+currScroll.endTop+'startTop:'+currScroll.startTop+"Y:"+currTop+"offsetTop:"+currScroll.scrollUl.offsetTop+"compute:"+(currTop*currScroll.scrollRadix*-1)+'px'; //textcode //内容滚动:当前滚动条top*基数取负数 currScroll.context.style.top=currTop*currScroll.scrollRadix*-1+'px'; }; returncurrScroll; }; }; linkFlyScroll.prototype.init.prototype.change=function(){ ///<summary> /// 1:滚动条内容改变函数 /// 1.1-change()-本函数代表刷新本滚动条对象的数据,在某些情况下,内容的数据是一直在变化的,可以调用本函数对当前滚动条对象刷新数据 ///</summary> ///<returnstype="linkFlyScroll"/> /* *linkFlyScroll包含的属性主要在本函数中初始化或重新定义: *isDrag:是否正在拖拽滚动条 *startTop:(工作中)滚动条开始滚动位置 *endTop:(工作中)滚动条结束滚动位置 *topLimit:滚动条顶部极限位置 *bottomLimit:滚动条底部极限位置 *context:内容Dom *scrollRadix:滚动基数 *target:容器Dom */ //重置或读取一系列数据 varcurrScroll=this; currScroll.isDrag=false, currScroll.startTop=0, currScroll.endTop=(currScroll.scrollUl.style.top?parseInt(currScroll.scrollUl.style.top):0), currScroll.topLimit=currScroll.target.scrollTop, currScroll.bottomLimit=currScroll.target.clientHeight, currScroll.scrollRadix=10; //得出滚动条的高度:内容高度*(容器高度/内容高度=容器占内容百分比) varscrollPx=currScroll.target.clientHeight*(currScroll.target.clientHeight/currScroll.context.offsetHeight); //滚动条高度 currScroll.scrollUl.childNodes[1].style.height=scrollPx+'px'; if(currScroll.context.clientHeight<=currScroll.target.clientHeight&&currScroll.options.auto){ currScroll.scrollUl.style.display='none'; }else{ currScroll.scrollUl.style.display='block'; //当滚动条显示,修正最大位置数据 currScroll.bottomLimit-=currScroll.scrollUl.offsetHeight; } //设置滚动条滚动基数(滚动条没滚动1px内容滚动像素):(内容高度-容器高度[因为当前容器已经显示了一屏])/滚动条top(滚动条空白可滚动高度) currScroll.scrollRadix=(currScroll.context.offsetHeight-currScroll.target.clientHeight)/currScroll.bottomLimit; returncurrScroll; }; linkFlyScroll.prototype.init.prototype.roll=function(value){ ///<summary> /// 1:滚动条偏移方法 /// 1.1-roll(value)-滚动条滚动方法 ///</summary> ///<paramname="value"type="Int"> /// 滚动条目标滚动的百分比 ///</param> ///<returnstype="linkFlyScroll"/> varcurrScroll=this; if(typeof(value)!=='number'){ returncurrScroll; } varcurrTop=(currScroll.bottomLimit-currScroll.topLimit)*value/100; currScroll.scrollEvent(currTop); currScroll.endTop=currTop; returncurrScroll; }; /* *工具类 */ vartool={ setClass:function(element,className){ ///<summary> /// 1:设置元素节点的class属性 /// 1.1-setClass(element,className)-设置元素节点的class属性,如没有该节点则创建该节点,并返回修改后的节点对象 ///</summary> ///<paramname="element"type="ElementOrString"> /// 传入String则创建该节点,否则修改该节点 ///</param> ///<paramname="className"type="String"> /// ClassName ///</param> ///<returnstype="Element"/> if(typeofelement==='string'){ element=document.createElement(element); } element.className=className; returnelement; }, hasClass:function(element,className){ ///<summary> /// 1:判断元素是否有class /// 1.1-hasClass(element,className)-判断元素是否有class,在业务中异常(基本没有该情况的发生)和有该class返回true,否则返回false ///</summary> ///<paramname="element"type="ElementOrString"> /// 节点对象 ///</param> ///<paramname="className"type="String"> /// ClassName ///</param> ///<returnstype="Element"/> if(!element||element.nodeType!==1)//让异常通过,外面不进行处理 returntrue; varelementClassName=element.className; if(elementClassName.length<1){ returnfalse; } if(elementClassName==className||elementClassName.match(newRegExp("(^|\\s)"+className+"(\\s|$)"))){ returntrue; } returnfalse; }, addClass:function(element,className){ ///<summary> /// 1:为元素【追加】class /// 1.1-addClass(element,className)-为元素【追加】class,并返回修改后的class ///</summary> ///<paramname="element"type="ElementOrString"> /// 节点对象 ///</param> ///<paramname="className"type="String"> /// ClassName ///</param> ///<returnstype="Element"/> if(!tool.hasClass(element,className)){ if(element.className.length<1){ element.className=className; }else{ element.className+=''+className; } } returnelement; }, removeClass:function(element,className){ ///<summary> /// 1:为元素移除class /// 1.1-addClass(element,className)-为元素移除class,并返回修改后的class ///</summary> ///<paramname="element"type="ElementOrString"> /// 节点对象 ///</param> ///<paramname="className"type="String"> /// ClassName ///</param> ///<returnstype="Element"/> if(tool.hasClass(element,className)){ varreg=newRegExp("(^|\\s)"+className+"(\\s|$)"); element.className=element.className.replace(reg,''); } returnelement; }, css:function(element,key){ ///<summary> /// 1:获取元素css指定的属性值 /// 1.1-css(element,className)-获取元素css指定的属性值 ///</summary> ///<paramname="element"type="ElementOrString"> /// 节点对象 ///</param> ///<paramname="key"type="String"> /// 要获取的css属性 ///</param> ///<returnstype="String"/> returnelement.currentStyle?element.currentStyle[key]:document.defaultView.getComputedStyle(element,false)[key]; }, addEvent:function(element,type,fn){ ///<summary> /// 1:为元素追加事件 /// 1.1-css(element,type,fn)-为元素追加事件,函数中this指向事件源 ///</summary> ///<paramname="element"type="ElementOrString"> /// 节点对象 ///</param> ///<paramname="type"type="String"> /// 追加的事件名,不含字符on ///</param> ///<paramname="fn"type="Function"> /// 事件对象 ///</param> ///<returnstype="void"/> if(element.attachEvent){ element['e'+type+fn]=fn; element[type+fn]=function(){element['e'+type+fn](window.event);} element.attachEvent('on'+type,element[type+fn]); }elseif(element.addEventListener){ element.addEventListener(type,fn,false); } }, // removeEvent:function(element,type,fn){ // ///<summary> // /// 1:为元素删除事件,本函数并未用到 // /// 1.1-removeEvent(element,type,fn)-为元素删除事件 // ///</summary> // ///<paramname="element"type="ElementOrString"> // /// 节点对象 // ///</param> // ///<paramname="type"type="String"> // /// 删除的事件名 // ///</param> // ///<paramname="key"type="String"> // /// 删除的事件的函数名 // ///</param> // ///<returnstype="void"/> // if(element.detachEvent){ // element.detachEvent('on'+type,element[type+fn]); // element[type+fn]=null; // }elseif(element.removeEventListener){ // element.removeEventListener(type,fn,false); // } // }, addScrollEvent:function(element,fn){ ///<summary> /// 1:追加ScrollEvent事件 /// 1.1-addScrollEvent(element,fn)-在元素上追加ScrollEvent事件(特殊事件,在元素上鼠标滚轮滚动事件) ///</summary> ///<paramname="element"type="ElementOrString"> /// 元素节点 ///</param> ///<paramname="fn"type="Function"> /// 事件方法 ///</param> ///<returnstype="void"/> varbindScrollFn=function(e){ e=e||window.event; //判断滚轮滚动方向:Firefox和其他浏览器不同 e.wheel=(e.wheelDelta?e.wheelDelta:-e.detail)>0?1:-1;//通过事件判断鼠标滚轮反向,1是向上,-1是向下 //阻止浏览器默认行为 if(e.preventDefault){//ff e.preventDefault(); }else{ e.returnValue=false;//ie } fn.call(element,e); } if(document.addEventListener){ //ff element.addEventListener('DOMMouseScroll',bindScrollFn,false); //w3c element.addEventListener('mousewheel',bindScrollFn,false); }else//ie { element.attachEvent('onmousewheel',bindScrollFn); } }, getEvent:function(){ ///<summary> /// 1:获取Event对象 /// 1.1-getEvent()-在无参数的情况下获取Event对象,同时兼容性处理IE和FF ///</summary> ///<returnstype="Event"/> if(document.all){ returnwindow.event; } func=getEvent.caller; while(func!=null){ vararg0=func.arguments[0]; if(arg0){ if((arg0.constructor==Event||arg0.constructor==MouseEvent)||(typeof(arg0)=="object"&&arg0.preventDefault&&arg0.stopPropagation)){ returnarg0; } } func=func.caller; } returnnull; }, getMousePos:function(ev){ ///<summary> /// 1:获取当前鼠标坐标 /// 1.1-getMousePos(ev)-获取当前鼠标坐标,兼容性处理,返回的对象格式:{x:鼠标x坐标,y:鼠标y坐标} ///</summary> ///<paramname="ev"type="Event"> /// Event事件对象 ///</param> ///<returnstype="Json"/> if(!ev){ ev=currScroll.getEvent(); } if(ev.pageX||ev.pageY){ return{ x:ev.pageX, y:ev.pageY }; } if(document.documentElement&&document.documentElement.scrollTop){ return{ x:ev.clientX+document.documentElement.scrollLeft-document.documentElement.clientLeft, y:ev.clientY+document.documentElement.scrollTop-document.documentElement.clientTop }; } elseif(document.body){ return{ x:ev.clientX+document.body.scrollLeft-document.body.clientLeft, y:ev.clientY+document.body.scrollTop-document.body.clientTop }; } }, extend:function(oldObj,newObj){ ///<summary> /// 1:将两个对象进行合并 /// 1.1-extend(oldObj,newObj)-将两个对象合并,并返回合并后的对象,采用clone的方式实现,所以不会对两个对象产生任何影响 ///</summary> ///<paramname="oldObj"type="Object"> /// 要合并的对象A,该对象作为基础对象,将新对象的同名属性覆盖到基础对象中 ///</param> ///<paramname="newObj"type="Object"> /// 要合并的对象B ///</param> ///<returnstype="Object"/> vartempObj=tool.clone(oldObj); for(varkeyinnewObj){ if(newObj.hasOwnProperty(key)&&!tempObj.hasOwnProperty(key)){ tempObj[key]=newObj[key]; } } returntempObj; }, clone:function(obj){ ///<summary> /// 1:克隆一个对象 /// 1.1-clone(obj)-克隆一个对象,并返回克隆后的新对象,该对象的原型是被克隆的对象 ///</summary> ///<paramname="obj"type="Object"> /// 要克隆的对象 ///</param> ///<returnstype="Object"/> functionClone(){} Clone.prototype=obj; varnewObj=newClone(); for(varkeyinnewObj){ if(typeofnewObj[key]=="object"){ newObj[key]=tool.clone(newObj[key]); } } returnnewObj; }, convertValue:function(value){ ///<summary> /// 1:将数值转换为有效的数值 /// 1.1-convertValue(value)-将Json配置的css数值转换为有效的数值,请保证value的值不为"auto" ///</summary> ///<paramname="value"type="Object"> /// 要转换的数值 ///</param> ///<returnstype="Object"/> varreg=/^\d+$/g; if(typeof(value)==='number'||reg.test(value)){ returnvalue+'px'; }else returnvalue; } }; //注册到window下 window.linkFlyScroll=linkFlyScroll; //注册到window.so命名空间下 if(!window.so){ window.so={}; } window.so.scroll=window.linkFlyScroll; })(window);
代码示例
<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <htmlxmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <linkhref="linkFlyScroll/linkFlyRollCss.css"rel="stylesheet"type="text/css"/> <scriptsrc="linkFlyScroll/linkFlyScroll-1.0.0.js"type="text/javascript"></script> <scripttype="text/javascript"> window.onload=function(){ varconfig={ auto:true,//当内容并未达到容器的高度的时候,是否自动隐藏滚动条 height:'100',//滚动条对象工作高度(超过该高度则显示滚动条),auto取对象当前高 width:'auto'//滚动条对象工作宽度 }; varscrollObj=so.scroll('obj',config); // scrollObj.change();//当滚动条内容改变后,需要刷新滚动条的显示,则调用本方法 // scrollObj.roll(value);//把滚动条定位到某一点上,value为相对于滚动条对象的百分比 }; </script> </head> <body> <divid="obj"> <div> 当前,企业管理领域刮起一股新的“时尚风”,一些巨头企业纷纷为自己“瘦身”,向更智慧和灵动的业务转型。据了解,甲骨文软件正越来越多地把客户的主要维护成本向咨询顾问和第三方供应商转移。 “在中国本土,90%的甲骨文公司业务是通过这些合作伙伴开展的。此外,为了进一步确保甲骨文的收入,CEO埃里森还购买了夏威夷的一个小岛。”CraigGuarente说道。 作为全球副总裁,Guarente非常清楚甲骨文的各项战略。Guarente具有16年的工作经历,曾在合同管理、软件许可证管理、软件审计方面有丰富经验。2011年离开甲骨文后,加入了Palisade公司,该公司的主要业务是帮助甲骨文客户提供软件承包、审计介入和许可证“优化”等业务。 Guarente表示,Palisade公司业务发展非常迅速。作为第三方机构,Palisade帮助客户赢得了大