Custom scroll bar
Functional requirements: Create menu content according to data. Click to expand the menu. If the content overflows, a scroll bar will appear. The height of the scroll bar will change with the height of the content. Drag the scroll bar with the mouse, and the content of the menu will also change.
Take a look at the effect:
html structure, simulate data, create outer container:
<!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:"Audi",category:[ {name:"Audi A3",href:""}, {name:"Audi A4L",href:""}, {name:"Audi Q3",href:""}, {name:"Audi Q5L",href:""}, {name:"Audi Q2L",href:""}, {name:"Audi Q7(Imported)",href:""}, {name:"Audi Q8(Import)",href:""}, {name:"Audi Q7 new energy",href:""}, ]}, {name:"alpha-Romeo",category:[ {name:"Stelvio(Import)",href:""}, {name:"Giulia(Import)",href:""}, ]} ]}, {name:"B",category:[ {name:"Benz",category:[ {name:"Benz C level",href:""}, {name:"Benz E level",href:""}, {name:"Benz GLA level",href:""}, {name:"Benz GLC level",href:""}, {name:"Benz A level",href:""}, {name:"Benz E level(Imported)",href:""}, {name:"Benz A Grade (imported)",href:""}, {name:"Benz B Grade (imported)",href:""}, {name:"vito",href:""}, {name:"Benz V level",href:""}, ]}, {name:"BMW",category:[ {name:"BMW 5 Series",href:""}, {name:"BMW 1 Series",href:""}, {name:"BMW X1",href:""}, {name:"BMW X5(Import)",href:""}, {name:"BMW X6(Import)",href:""}, ]}, {name:"Honda",category:[ {name:"Competition",href:""}, {name:"civic",href:""}, {name:"Honda CR-V",href:""}, {name:"Honda XR-V",href:""}, {name:"Honda UR-V",href:""}, {name:"Ai Li Shen",href:""}, {name:"Enjoyment area",href:""}, {name:"INSPIRE",href:""}, {name:"Crider",href:""}, {name:"Accord",href:""}, {name:"Bon Chi",href:""}, ]}, {name:"Buick",category:[ {name:"Excelle",href:""}, {name:"Ying Long",href:""}, {name:"Wei long",href:""}, {name:"Yue Lang",href:""}, {name:"Regal",href:""}, {name:"Lacrosse",href:""}, {name:"Encore",href:""}, {name:"Akeway",href:""}, {name:"Buick GL8",href:""}, {name:"Buick GL6",href:""}, {name:"VELITE",href:""}, ]} ]} ] var container; init(); function init(){ createMenu(arr); createScrollBar(); } function createMenu(arr){ //create menu let menu=new Menu(arr); //Create outermost container 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(){ //Create scroll bar let scrollBar=new ScrollBar(container); scrollBar.appendTo(container); } </script> </body> </html>
Menu.js file, create the folding menu content according to the data:
import Utils from './Utils.js'; export default class Menu{ ul; static SET_BAR_HEIGHT="set_bar_height"; constructor(_list){ this.elem=this.createElem(_list); } createElem(_list){ if(this.elem) return this.elem; //Create the outermost ul container this.ul=Utils.createE("ul",{ listStyle:"none", padding:"0px", margin:"0px", width:"235px", height:"360px", color:"#333", fontSize:"14px", userSelect: "none", position:"absolute" }); //Create li list this.createMenu(_list,this.ul); //ul monitoring click event this.ul.addEventListener("click",e=>this.clickHandler(e)); return this.ul; } appendTo(parent){ Utils.appendTo(this.elem,parent); } //Create a level 1 menu 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,//Control level 1 menu cannot be folded by clicking }) let span=Utils.createE("span",{ marginLeft:"14px", fontSize:"18px" },{ textContent:_list[i].name }) Utils.appendTo(span,li); Utils.appendTo(li,parent); //Create a submenu. The third parameter controls whether the submenu is displayed this.createSubMenu(_list[i].category,li,0); } } //Create submenu createSubMenu(_subList,_parent,_index){ //If there is no submenu, the 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){ //If there is no submenu in the current menu, create a label to jump 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{ //If the current menu has submenus, create a span label 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 the current menu does not have a submenu, skip the following execution if(!_subList[i].category) continue; //Recurse the submenu of the current menu as a parameter this.createSubMenu(_subList[i].category,subLi,1); } Utils.appendTo(subUl,_parent); } clickHandler(e){ //If the currently clicked tab is not li or span, directly jump out 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; //If there is no submenu under Li, directly jump out if(targ.children.length<=1) return; //If the current menu is level 1, directly jump out if(targ.data===1) return; //Control the ul display hidden under the currently clicked Li if(!targ.bool) targ.lastElementChild.style.display="block"; else targ.lastElementChild.style.display="none"; targ.bool=!targ.bool; //Change the span label this.changeSpan(targ); //Throw event, change the height of scroll bar 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="+"; } } }
Scroll bar.js file, create a scroll bar, and operate the scroll bar:
import Utils from './Utils.js'; export default class ScrollBar { bar; conHeight; menuHeight; static SET_BAR_HEIGHT="set_bar_height"; constructor(parent) { this.container = parent; this.elem = this.createElem(); //Listen to the click event of the menu and dynamically change the height of the scroll bar document.addEventListener(ScrollBar.SET_BAR_HEIGHT,e=>this.setBarHeight()); } createElem() { if (this.elem) return this.elem; //Create an outer container for scroll bars 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; //Create scroll bar this.bar = Utils.createE("div", { width: "100%", position: "absolute", left: "0px", top: "0px", borderRadius: "10px", background: "rgba(255,0,0,1)" }) //Set the height of the scroll bar this.setBarHeight(); //Listen for mouse drag events this.mouseHand = e => this.mouseHandler(e); this.bar.addEventListener("mousedown", this.mouseHand); Utils.appendTo(this.bar, _parent); } setBarHeight() { //Height of outer parent container this.conHeight = this.container.clientHeight; //Height of actual content this.menuHeight = this.container.firstElementChild.scrollHeight; //If the height of the actual content is less than the height of the parent container, the scroll bar is hidden if (this.conHeight >= this.menuHeight) this.bar.style.display = "none"; else this.bar.style.display = "block"; //Calculate the height of the scroll bar let h = Math.floor(this.conHeight / this.menuHeight * this.conHeight); this.bar.style.height = h + "px"; } 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": //Note: in the results returned by getBoundingClientRect(), width height contains border var rect = this.container.getBoundingClientRect(); let y = e.clientY - rect.y - this.y; if (y < 0) y = 0; if (y > this.conHeight - this.bar.offsetHeight) y = this.conHeight - this.bar.offsetHeight; this.bar.style.top = y + "px"; //Content rolling this.menuMove(y); break; case "mouseup": document.removeEventListener("mousemove", this.mouseHand); document.removeEventListener("mouseup", this.mouseHand); break; } } menuMove(_y){ //Calculate the scrolling height of content let menuTop=_y/(this.conHeight-this.bar.offsetHeight)*(this.menuHeight-this.conHeight); this.container.firstElementChild.style.top=-menuTop+"px"; } }
Utils.js is a toolkit:
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); } } }
After clicking the fold menu, you need to set the height of the scroll bar accordingly. The folding menu is in the Menu.js file, and the setting of the scroll bar is in the ScrollBar.js file. Events need to be thrown and monitored.