原生js實(shí)現(xiàn)自定義滾動(dòng)條組件
本文實(shí)例為大家分享了js實(shí)現(xiàn)自定義滾動(dòng)條組件的具體代碼,供大家參考,具體內(nèi)容如下
功能需求:
1、按照數(shù)據(jù)結(jié)構(gòu)創(chuàng)建菜單內(nèi)容,顯示在頁面中;
2、點(diǎn)擊菜單后,顯示對(duì)應(yīng)的下級(jí)菜單內(nèi)容,如果整體內(nèi)容溢出,則出現(xiàn)滾動(dòng)條;
3、滾動(dòng)條的高度要隨著整體內(nèi)容高度的改變而改變。
4、鼠標(biāo)拖動(dòng)滾動(dòng)條,整體內(nèi)容要隨著向上滾動(dòng)。
5、當(dāng)鼠標(biāo)滾動(dòng)時(shí),滾動(dòng)條和整體內(nèi)容也要相應(yīng)滾動(dòng)。
來看一下效果:
默認(rèn)狀態(tài):
點(diǎn)擊菜單,內(nèi)容溢出后,出現(xiàn)滾動(dòng)條;
鼠標(biāo)拖動(dòng)滾動(dòng)條,整體內(nèi)容隨著向上滾動(dòng):
分析:
- 這個(gè)案例中包括折疊菜單和滾動(dòng)條兩個(gè)組件 ,所以可以分開來寫,然后整合到一起。
- 折疊菜單中要考慮多級(jí)菜單出現(xiàn)的情況,使用遞歸來做,數(shù)據(jù)的結(jié)構(gòu)一定要統(tǒng)一,方便對(duì)數(shù)據(jù)進(jìn)行處理。
- 滾動(dòng)條的創(chuàng)建中,有兩個(gè)比例等式,一是滾動(dòng)條的高度/外層div高度=外層div高度/整體內(nèi)容高度;二是滾動(dòng)條的位置/(外層div高度-滾動(dòng)條高度)=內(nèi)容的scrollTop/(整體內(nèi)容的高度-外層div高度)
- 當(dāng)點(diǎn)擊折疊菜單后,需要相應(yīng)地設(shè)置滾動(dòng)條的高度。折疊菜單是在Menu.js文件中,滾動(dòng)條的設(shè)置是在ScrollBar.js文件中,需要進(jìn)行拋發(fā)、監(jiān)聽事件。
- 監(jiān)聽菜單鼠標(biāo)滾動(dòng)的事件,當(dāng)鼠標(biāo)滾動(dòng)時(shí),判斷滾輪方向,設(shè)置滾動(dòng)條和內(nèi)容的 top 值,也需要用到事件的拋發(fā)和監(jiān)聽。
下面附上代碼:
html結(jié)構(gòu),模擬數(shù)據(jù),創(chuàng)建外層容器:
<!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(進(jìn)口)",href:""}, {name:"奧迪Q8(進(jìn)口)",href:""}, {name:"奧迪Q7新能源",href:""}, ]}, {name:"阿爾法-羅密歐",category:[ {name:"Stelvio(進(jìn)口)",href:""}, {name:"Giulia(進(jìn)口)",href:""}, ]} ]}, {name:"B",category:[ {name:"奔馳",category:[ {name:"奔馳C級(jí)",href:""}, {name:"奔馳E級(jí)",href:""}, {name:"奔馳GLA級(jí)",href:""}, {name:"奔馳GLC級(jí)",href:""}, {name:"奔馳A級(jí)",href:""}, {name:"奔馳E級(jí)(進(jìn)口)",href:""}, {name:"奔馳A級(jí)(進(jìn)口)",href:""}, {name:"奔馳B級(jí)(進(jìn)口)",href:""}, {name:"威霆",href:""}, {name:"奔馳V級(jí)",href:""}, ]}, {name:"寶馬",category:[ {name:"寶馬5系",href:""}, {name:"寶馬1系",href:""}, {name:"寶馬X1",href:""}, {name:"寶馬X5(進(jìn)口)",href:""}, {name:"寶馬X6(進(jìn)口)",href:""}, ]}, {name:"本田",category:[ {name:"競?cè)?,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){ //創(chuàng)建菜單 let menu=new Menu(arr); //創(chuàng)建最外層容器 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(){ //創(chuàng)建滾動(dòng)條 let scrollBar=new ScrollBar(container); scrollBar.appendTo(container); } </script> </body> </html>
Menu.js文件,根據(jù)數(shù)據(jù)創(chuàng)建折疊菜單內(nèi)容:
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; //創(chuàng)建最外層ul容器 let ul=Utils.createE("ul",{ listStyle:"none", padding:"0px", margin:"0px", width:"235px", height:"360px", color:"#333", fontSize:"14px", userSelect: "none", position:"absolute" }); //創(chuàng)建li列表 this.createMenu(_list,ul); //ul監(jiān)聽點(diǎn)擊事件 ul.addEventListener("click",e=>this.clickHandler(e)); //ul監(jiān)聽滾輪事件,火狐使用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); } //創(chuàng)建一級(jí)菜單 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,//控制一級(jí)菜單不能點(diǎn)擊折疊 }) let span=Utils.createE("span",{ marginLeft:"14px", fontSize:"18px" },{ textContent:_list[i].name }) Utils.appendTo(span,li); Utils.appendTo(li,parent); //創(chuàng)建子菜單,第三個(gè)參數(shù)控制子菜單是否顯示 this.createSubMenu(_list[i].category,li,0); } } //創(chuàng)建子菜單 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){ //如果當(dāng)前菜單沒有子菜單,則創(chuàng)建a標(biāo)簽,進(jìn)行跳轉(zhuǎn) 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{ //如果當(dāng)前菜單有子菜單,創(chuàng)建span標(biāo)簽 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); //如果當(dāng)前菜單沒有子菜單,則跳過下面的執(zhí)行 if(!_subList[i].category) continue; //將當(dāng)前菜單的子菜單作為參數(shù),進(jìn)行遞歸 this.createSubMenu(_subList[i].category,subLi,1); } Utils.appendTo(subUl,_parent); } clickHandler(e){ //如果當(dāng)前點(diǎn)擊的不是li標(biāo)簽或者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; //如果當(dāng)前點(diǎn)擊Li下面沒有子菜單,直接跳出 if(targ.children.length<=1) return; //如果當(dāng)前點(diǎn)擊的是一級(jí)菜單,直接跳出 if(targ.data===1) return; //控制當(dāng)前點(diǎn)擊的Li下的ul顯示隱藏 if(!targ.bool) targ.lastElementChild.style.display="block"; else targ.lastElementChild.style.display="none"; targ.bool=!targ.bool; //改變span標(biāo)簽的內(nèi)容 this.changeSpan(targ); //拋發(fā)事件,改變滾動(dòng)條的高度 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時(shí),表示滾輪往下,頁面往上 let tag=e.detail,wheelDir; //其他瀏覽器判斷e.deltaY,e.deltaY<0時(shí),表示滾輪往下,頁面往上 if(tag===0) tag=e.deltaY; if(tag>0){ //滾輪往下滾動(dòng),頁面往上走 wheelDir="down"; }else{ wheelDir="up"; } //拋發(fā)事件,將滾輪方向傳遞過去 let evt=new Event(Menu.MOUSE_WHEEL_EVENT); evt.wheelDirection=wheelDir; this.elem.dispatchEvent(evt); } }
ScrollBar.js文件,創(chuàng)建滾動(dòng)條,對(duì)滾動(dòng)條進(jìn)行操作:
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(); //偵聽菜單的點(diǎn)擊事件,動(dòng)態(tài)改變滾動(dòng)條的高度 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; //創(chuàng)建滾動(dòng)條的外層容器 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; //創(chuàng)建滾動(dòng)條 this.bar = Utils.createE("div", { width: "100%", position: "absolute", left: "0px", top: "0px", borderRadius: "10px", backgroundColor: "rgba(255,0,0,.5)" }) //設(shè)置滾動(dòng)條hover狀態(tài)的樣式 this.bar.addEventListener("mouseenter",e=>this.setMouseStateHandler(e)); this.bar.addEventListener("mouseleave",e=>this.setMouseStateHandler(e)); //設(shè)置滾動(dòng)條的高度 this.setBarHeight(); //偵聽鼠標(biāo)拖動(dòng)事件 this.mouseHand = e => this.mouseHandler(e); this.bar.addEventListener("mousedown", this.mouseHand); Utils.appendTo(this.bar, _parent); } setBarHeight() { //外層父容器的高度 this.conHeight = this.container.clientHeight; //實(shí)際內(nèi)容的高度 this.menuHeight = this.container.firstElementChild.scrollHeight; //如果實(shí)際內(nèi)容的高度小于父容器的高度,滾動(dòng)條隱藏 if (this.conHeight >= this.menuHeight) this.bar.style.display = "none"; else this.bar.style.display = "block"; //計(jì)算滾動(dòng)條的高度 let h = Math.floor(this.conHeight / this.menuHeight * this.conHeight); this.bar.style.height = h + "px"; } setMouseStateHandler(e){ //設(shè)置滾動(dòng)條hover狀態(tài)的樣式 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()返回的結(jié)果中,width height 都是包含border的 var rect = this.container.getBoundingClientRect(); this.barTop = e.clientY - rect.y - this.y; //滾動(dòng)條移動(dòng) this.barMove(); break; case "mouseup": document.removeEventListener("mousemove", this.mouseHand); document.removeEventListener("mouseup", this.mouseHand); break; } } mouseWheelHandler(e){ //滾輪事件 if(e.wheelDirection==="down"){ //滾動(dòng)往下,菜單內(nèi)容往上 this.barTop+=this.wheelSpeed; }else{ this.barTop-=this.wheelSpeed; } //滾動(dòng)條移動(dòng) 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"; //菜單內(nèi)容滾動(dòng) this.menuMove(); } menuMove(){ //計(jì)算內(nèi)容的滾動(dòng)高度 let menuTop=this.barTop/(this.conHeight-this.bar.offsetHeight)*(this.menuHeight-this.conHeight); this.menuUl.style.top=-menuTop+"px"; } }
Utils.js文件,是一個(gè)工具包:
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); } } }
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 原生js實(shí)現(xiàn)自定義滾動(dòng)條
- js實(shí)現(xiàn)滾動(dòng)條自動(dòng)滾動(dòng)
- Vue.js桌面端自定義滾動(dòng)條組件之美化滾動(dòng)條VScroll
- JS自定義滾動(dòng)條效果
- JavaScript實(shí)現(xiàn)簡易聊天對(duì)話框(加滾動(dòng)條)
- js滾輪事件 js自定義滾動(dòng)條的實(shí)現(xiàn)
- JS實(shí)現(xiàn)滾動(dòng)條觸底加載更多
- 使用js實(shí)現(xiàn)一個(gè)簡單的滾動(dòng)條過程解析
- layer.js open 隱藏滾動(dòng)條的例子
- vue 純js監(jiān)聽滾動(dòng)條到底部的實(shí)例講解
- JavaScript 獲取滾動(dòng)條位置并將頁面滑動(dòng)到錨點(diǎn)
相關(guān)文章
淺析Javascript的自動(dòng)分號(hào)插入(ASI)機(jī)制
我們大家都知道在寫java和c時(shí),必須要在語句后加分號(hào),否則編譯通不過。而js不同,存在自動(dòng)分好插入機(jī)制,下文簡稱ASI。它會(huì)給源代碼的 token 流自動(dòng)插入分號(hào)。下面這篇文章我們就來談?wù)凧avascript的自動(dòng)分號(hào)插入(ASI)機(jī)制。2016-09-09layui 數(shù)據(jù)表格 點(diǎn)擊分頁按鈕 監(jiān)聽事件的實(shí)例
今天小編就為大家分享一篇layui 數(shù)據(jù)表格 點(diǎn)擊分頁按鈕 監(jiān)聽事件的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09關(guān)于JavaScript的gzip靜態(tài)壓縮方法
關(guān)于JavaScript的gzip靜態(tài)壓縮方法...2007-01-01Array.prototype.concat不是通用方法反駁[譯]
ECMAScript 5.1規(guī)范中指出,數(shù)組方法concat是通用的(generic).本文反駁了這一結(jié)論,因?yàn)閷?shí)際上并不是這樣的2012-09-09JS實(shí)現(xiàn)的base64加密、md5加密及sha1加密詳解
這篇文章主要介紹了JS實(shí)現(xiàn)的base64加密、md5加密及sha1加密的方法,結(jié)合實(shí)例形式詳細(xì)分析了JavaScript各種常見加密方法與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-04-04