Pure js to realize the effect of custom scroll bar

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.

58 original articles published, 29 praised, 20000 visitors+
Private letter follow

Keywords: Javascript less

Added by Steffen on Tue, 28 Jan 2020 17:43:38 +0200