原生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);

}

}

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是 原生js实现自定义滚动条组件 的全部内容, 来源链接: utcz.com/p/219430.html

回到顶部