本文实例为大家分享了js实现自定义滚动条组件的具体代码,供大家参考,具体内容如下
功能需求:
1、按照数据结构创建菜单内容,显示在页面中;
2、点击菜单后,显示对应的下级菜单内容,如果整体内容溢出,则出现滚动条;
3、滚动条的高度要随着整体内容高度的改变而改变。
4、鼠标拖动滚动条,整体内容要随着向上滚动。
5、当鼠标滚动时,滚动条和整体内容也要相应滚动。
来看一下效果:
默认状态:
点击菜单,内容溢出后,出现滚动条;
鼠标拖动滚动条,整体内容随着向上滚动:
分析:
这个案例中包括折叠菜单和滚动条两个组件 ,所以可以分开来写,然后整合到一起。
折叠菜单中要考虑多级菜单出现的情况,使用递归来做,数据的结构一定要统一,方便对数据进行处理。
滚动条的创建中,有两个比例等式,一是滚动条的高度/外层div高度=外层div高度/整体内容高度;二是滚动条的位置/(外层div高度-滚动条高度)=内容的scrollTop/(整体内容的高度-外层div高度)
当点击折叠菜单后,需要相应地设置滚动条的高度。折叠菜单是在Menu.js文件中,滚动条的设置是在ScrollBar.js文件中,需要进行抛发、监听事件。
监听菜单鼠标滚动的事件,当鼠标滚动时,判断滚轮方向,设置滚动条和内容的 top 值,也需要用到事件的抛发和监听。
下面附上代码:
html结构,模拟数据,创建外层容器:
<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <title>scrollBar</title> </head> <body> <script type=\"module\"> import Utils from \'./js/Utils.js\'; import Menu from \'./js/Menu.js\'; import ScrollBar from \'./js/ScrollBar.js\'; var arr=[ {name:\"A\",category:[ {name:\"奥迪\",category:[ {name:\"奥迪A3\",href:\"\"}, {name:\"奥迪A4L\",category:[ {name:\"奥迪A4L-1\",href:\"\"} ]}, {name:\"奥迪Q3\",href:\"\"}, {name:\"奥迪Q5L\",href:\"\"}, {name:\"奥迪Q2L\",href:\"\"}, {name:\"奥迪Q7(进口)\",href:\"\"}, {name:\"奥迪Q8(进口)\",href:\"\"}, {name:\"奥迪Q7新能源\",href:\"\"}, ]}, {name:\"阿尔法-罗密欧\",category:[ {name:\"Stelvio(进口)\",href:\"\"}, {name:\"Giulia(进口)\",href:\"\"}, ]} ]}, {name:\"B\",category:[ {name:\"奔驰\",category:[ {name:\"奔驰C级\",href:\"\"}, {name:\"奔驰E级\",href:\"\"}, {name:\"奔驰GLA级\",href:\"\"}, {name:\"奔驰GLC级\",href:\"\"}, {name:\"奔驰A级\",href:\"\"}, {name:\"奔驰E级(进口)\",href:\"\"}, {name:\"奔驰A级(进口)\",href:\"\"}, {name:\"奔驰B级(进口)\",href:\"\"}, {name:\"威霆\",href:\"\"}, {name:\"奔驰V级\",href:\"\"}, ]}, {name:\"宝马\",category:[ {name:\"宝马5系\",href:\"\"}, {name:\"宝马1系\",href:\"\"}, {name:\"宝马X1\",href:\"\"}, {name:\"宝马X5(进口)\",href:\"\"}, {name:\"宝马X6(进口)\",href:\"\"}, ]}, {name:\"本田\",category:[ {name:\"竞瑞\",href:\"\"}, {name:\"思域\",href:\"\"}, {name:\"本田CR-V\",href:\"\"}, {name:\"本田XR-V\",href:\"\"}, {name:\"本田UR-V\",href:\"\"}, {name:\"艾力绅\",href:\"\"}, {name:\"享域\",href:\"\"}, {name:\"INSPIRE\",href:\"\"}, {name:\"凌派\",href:\"\"}, {name:\"雅阁\",href:\"\"}, {name:\"缤智\",href:\"\"}, ]}, {name:\"别克\",category:[ {name:\"凯越\",href:\"\"}, {name:\"英朗\",href:\"\"}, {name:\"威朗\",href:\"\"}, {name:\"阅朗\",href:\"\"}, {name:\"君威\",href:\"\"}, {name:\"君越\",href:\"\"}, {name:\"昂科拉\",href:\"\"}, {name:\"昂科威\",href:\"\"}, {name:\"别克GL8\",href:\"\"}, {name:\"别克GL6\",href:\"\"}, {name:\"VELITE\",href:\"\"}, ]} ]} ] var container; init(); function init(){ createMenu(arr); createScrollBar(); } function createMenu(arr){ //创建菜单 let menu=new Menu(arr); //创建最外层容器 container=Utils.createE(\"div\",{ width:\"235px\", height:\"360px\", border:\"1px solid #ccc\", position:\"relative\", overflow:\"hidden\" }) menu.appendTo(container); Utils.appendTo(container,\"body\") } function createScrollBar(){ //创建滚动条 let scrollBar=new ScrollBar(container); scrollBar.appendTo(container); } </script> </body> </html>
Menu.js文件,根据数据创建折叠菜单内容:
import Utils from \'./Utils.js\'; export default class Menu{ static SET_BAR_HEIGHT=\"set_bar_height\"; static MOUSE_WHEEL_EVENT=\"mouse_wheel_event\"; constructor(_list){ this.elem=this.createElem(_list); } createElem(_list){ if(this.elem) return this.elem; //创建最外层ul容器 let ul=Utils.createE(\"ul\",{ listStyle:\"none\", padding:\"0px\", margin:\"0px\", width:\"235px\", height:\"360px\", color:\"#333\", fontSize:\"14px\", userSelect: \"none\", position:\"absolute\" }); //创建li列表 this.createMenu(_list,ul); //ul监听点击事件 ul.addEventListener(\"click\",e=>this.clickHandler(e)); //ul监听滚轮事件,火狐使用DOMMouseScroll,其它浏览器使用mousewheel ul.addEventListener(\"mousewheel\",e=>this.mouseWheelHandler(e)); ul.addEventListener(\"DOMMouseScroll\",e=>this.mouseWheelHandler(e)); return ul; } appendTo(parent){ Utils.appendTo(this.elem,parent); } //创建一级菜单 createMenu(_list,parent){ for(let i=0;i<_list.length;i++){ let li=Utils.createE(\"li\",{ background:\"#f5f5f5\", borderTop:\"1px solid #ddd\", lineHeight:\"32px\", },{ data:1,//控制一级菜单不能点击折叠 }) let span=Utils.createE(\"span\",{ marginLeft:\"14px\", fontSize:\"18px\" },{ textContent:_list[i].name }) Utils.appendTo(span,li); Utils.appendTo(li,parent); //创建子菜单,第三个参数控制子菜单是否显示 this.createSubMenu(_list[i].category,li,0); } } //创建子菜单 createSubMenu(_subList,_parent,_index){ //如果没有子菜单,则跳出 if(_subList.length===0) return; let subUl=Utils.createE(\"ul\",{ listStyle:\"none\", background:\"#fff\", padding:\"0px\", margin:\"0px\", fontSize:\"14px\", display:_index===0? \"block\" : \"none\" }) for(let i=0;i<_subList.length;i++){ let subLi=Utils.createE(\"li\",{ paddingLeft:\"40px\", position:\"relative\", cursor:\"pointer\" }) if(!_subList[i].category){ //如果当前菜单没有子菜单,则创建a标签,进行跳转 let subA=Utils.createE(\"a\",{ color:\"#333\", textDecoration:\"none\", width:\"100%\", display:\"inline-block\" },{ textContent:_subList[i].name, href:_subList[i].href || \"javascript:void(0)\", target:_subList[i].href ? \"_blank\" : \"_self\" }) Utils.appendTo(subA,subLi); }else{ //如果当前菜单有子菜单,创建span标签 let subSpan=Utils.createE(\"span\",{ position:\"absolute\", left:\"20px\", top:\"8px\", border: \"1px solid #ccc\", display: \"inline-block\", width: \"10px\", height: \"10px\", lineHeight:\"8px\" },{ textContent:_subList[i].category.length>0? \"+\" : \"-\" }) subLi.textContent=_subList[i].name; Utils.appendTo(subSpan,subLi); } Utils.appendTo(subLi,subUl); //如果当前菜单没有子菜单,则跳过下面的执行 if(!_subList[i].category) continue; //将当前菜单的子菜单作为参数,进行递归 this.createSubMenu(_subList[i].category,subLi,1); } Utils.appendTo(subUl,_parent); } clickHandler(e){ //如果当前点击的不是li标签或者span,直接跳出 if(e.target.nodeName!==\"LI\" && e.target.nodeName!==\"SPAN\") return; let targ; if(e.target.nodeName===\"SPAN\") targ=e.target.parentElement; else targ=e.target; //如果当前点击Li下面没有子菜单,直接跳出 if(targ.children.length<=1) return; //如果当前点击的是一级菜单,直接跳出 if(targ.data===1) return; //控制当前点击的Li下的ul显示隐藏 if(!targ.bool) targ.lastElementChild.style.display=\"block\"; else targ.lastElementChild.style.display=\"none\"; targ.bool=!targ.bool; //改变span标签的内容 this.changeSpan(targ); //抛发事件,改变滚动条的高度 var evt=new Event(Menu.SET_BAR_HEIGHT); document.dispatchEvent(evt) } changeSpan(elem){ if(elem.lastElementChild.style.display===\"block\"){ elem.firstElementChild.textContent=\"-\"; }else{ elem.firstElementChild.textContent=\"+\"; } } mouseWheelHandler(e){ //阻止事件冒泡 e.stopPropagation(); //火狐浏览器判断e.detail,e.detail<0时,表示滚轮往下,页面往上 let tag=e.detail,wheelDir; //其他浏览器判断e.deltaY,e.deltaY<0时,表示滚轮往下,页面往上 if(tag===0) tag=e.deltaY; if(tag>0){ //滚轮往下滚动,页面往上走 wheelDir=\"down\"; }else{ wheelDir=\"up\"; } //抛发事件,将滚轮方向传递过去 let evt=new Event(Menu.MOUSE_WHEEL_EVENT); evt.wheelDirection=wheelDir; this.elem.dispatchEvent(evt); } }
ScrollBar.js文件,创建滚动条,对滚动条进行操作:
import Utils from \'./Utils.js\'; import Menu from \'./Menu.js\'; export default class ScrollBar { bar; conHeight; menuHeight; wheelSpeed=6; barTop=0; static SET_BAR_HEIGHT=\"set_bar_height\"; constructor(parent) { this.container = parent; this.menuUl=this.container.firstElementChild; this.elem = this.createElem(); //侦听菜单的点击事件,动态改变滚动条的高度 document.addEventListener(ScrollBar.SET_BAR_HEIGHT,()=>this.setBarHeight()); //ul菜单侦听滚轮事件 this.menuUl.addEventListener(Menu.MOUSE_WHEEL_EVENT,e=>this.mouseWheelHandler(e)); } createElem() { if (this.elem) return this.elem; //创建滚动条的外层容器 let div = Utils.createE(\"div\", { width: \"8px\", height: \"100%\", position: \"absolute\", right: \"0px\", top: \"0px\", }) this.createBar(div); return div; } appendTo(parent) { Utils.appendTo(this.elem,parent); } createBar(_parent) { if(this.bar) return this.bar; //创建滚动条 this.bar = Utils.createE(\"div\", { width: \"100%\", position: \"absolute\", left: \"0px\", top: \"0px\", borderRadius: \"10px\", backgroundColor: \"rgba(255,0,0,.5)\" }) //设置滚动条hover状态的样式 this.bar.addEventListener(\"mouseenter\",e=>this.setMouseStateHandler(e)); this.bar.addEventListener(\"mouseleave\",e=>this.setMouseStateHandler(e)); //设置滚动条的高度 this.setBarHeight(); //侦听鼠标拖动事件 this.mouseHand = e => this.mouseHandler(e); this.bar.addEventListener(\"mousedown\", this.mouseHand); Utils.appendTo(this.bar, _parent); } setBarHeight() { //外层父容器的高度 this.conHeight = this.container.clientHeight; //实际内容的高度 this.menuHeight = this.container.firstElementChild.scrollHeight; //如果实际内容的高度小于父容器的高度,滚动条隐藏 if (this.conHeight >= this.menuHeight) this.bar.style.display = \"none\"; else this.bar.style.display = \"block\"; //计算滚动条的高度 let h = Math.floor(this.conHeight / this.menuHeight * this.conHeight); this.bar.style.height = h + \"px\"; } setMouseStateHandler(e){ //设置滚动条hover状态的样式 if(e.type===\"mouseenter\"){ this.bar.style.backgroundColor=\"rgba(255,0,0,1)\"; }else{ this.bar.style.backgroundColor=\"rgba(255,0,0,.5)\"; } } mouseHandler(e) { switch (e.type) { case \"mousedown\": e.preventDefault(); this.y = e.offsetY; document.addEventListener(\"mousemove\", this.mouseHand); document.addEventListener(\"mouseup\", this.mouseHand); break; case \"mousemove\": //注意:getBoundingClientRect()返回的结果中,width height 都是包含border的 var rect = this.container.getBoundingClientRect(); this.barTop = e.clientY - rect.y - this.y; //滚动条移动 this.barMove(); break; case \"mouseup\": document.removeEventListener(\"mousemove\", this.mouseHand); document.removeEventListener(\"mouseup\", this.mouseHand); break; } } mouseWheelHandler(e){ //滚轮事件 if(e.wheelDirection===\"down\"){ //滚动往下,菜单内容往上 this.barTop+=this.wheelSpeed; }else{ this.barTop-=this.wheelSpeed; } //滚动条移动 this.barMove(); } barMove(){ if (this.barTop < 0) this.barTop = 0; if (this.barTop > this.conHeight - this.bar.offsetHeight) this.barTop = this.conHeight - this.bar.offsetHeight; this.bar.style.top = this.barTop + \"px\"; //菜单内容滚动 this.menuMove(); } menuMove(){ //计算内容的滚动高度 let menuTop=this.barTop/(this.conHeight-this.bar.offsetHeight)*(this.menuHeight-this.conHeight); this.menuUl.style.top=-menuTop+\"px\"; } }
Utils.js文件,是一个工具包:
export default class Utils{ static createE(elem,style,prep){ elem=document.createElement(elem); if(style) for(let prop in style) elem.style[prop]=style[prop]; if(prep) for(let prop in prep) elem[prop]=prep[prop]; return elem; } static appendTo(elem,parent){ if (parent.constructor === String) parent = document.querySelector(parent); parent.appendChild(elem); } static randomNum(min,max){ return Math.floor(Math.random*(max-min)+min); } static randomColor(alpha){ alpha=alpha||Math.random().toFixed(1); if(isNaN(alpha)) alpha=1; if(alpha>1) alpha=1; if(alpha<0) alpha=0; let col=\"rgba(\"; for(let i=0;i<3;i++){ col+=Utils.randomNum(0,256)+\",\"; } col+=alpha+\")\"; return col; } static insertCss(select,styles){ if(document.styleSheets.length===0){ let styleS=Utils.createE(\"style\"); Utils.appendTo(styleS,document.head); } let styleSheet=document.styleSheets[document.styleSheets.length-1]; let str=select+\"{\"; for(var prop in styles){ str+=prop.replace(/[A-Z]/g,function(item){ return \"-\"+item.toLocaleLowerCase(); })+\":\"+styles[prop]+\";\"; } str+=\"}\" styleSheet.insertRule(str,styleSheet.cssRules.length); } static getIdElem(elem,obj){ if(elem.id) obj[elem.id]=elem; if(elem.children.length===0) return obj; for(let i=0;i<elem.children.length;i++){ Utils.getIdElem(elem.children[i],obj); } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
暂无评论内容