原生js实现自定义滚动条组件
本文实例为大家分享了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); } }}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持好吧啦网。
相关文章: