原生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)擊菜單后,顯示對應(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)一,方便對數(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)條,對滾動(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)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. Gitlab CI-CD自動(dòng)化部署SpringBoot項(xiàng)目的方法步驟2. JS sort方法基于數(shù)組對象屬性值排序3. ASP中解決“對象關(guān)閉時(shí),不允許操作。”的詭異問題……4. JAVA上加密算法的實(shí)現(xiàn)用例5. Django-migrate報(bào)錯(cuò)問題解決方案6. ajax請求添加自定義header參數(shù)代碼7. ASP刪除img標(biāo)簽的style屬性只保留src的正則函數(shù)8. 使用Python和百度語音識(shí)別生成視頻字幕的實(shí)現(xiàn)9. 淺談SpringMVC jsp前臺(tái)獲取參數(shù)的方式 EL表達(dá)式10. 基于javascript處理二進(jìn)制圖片流過程詳解
