本文实例为大家分享了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);
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/charissa2017/article/details/104098404