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

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

(0)

相关推荐

  • js操作滚动条事件实例

    本文实例讲述了js操作滚动条事件的方法.分享给大家供大家参考.具体分析如下: 之前一直很纳闷,如何监视滚动条的事件,今天终于有点明白了. 下边代码,是监听滚动条只要移动,下方的返回顶部的div显示与隐藏的代码 window.onscroll = function () { var t = document.documentElement.scrollTop || document.body.scrollTop; if (t > 0) { $(".cbbfixed").css(&q

  • JS和JQUERY获取页面大小,滚动条位置,元素位置(示例代码)

    js与jquery获得页面大小.滚动条位置.元素位置 复制代码 代码如下: //页面位置及窗口大小 function GetPageSize() {var scrW, scrH; if(window.innerHeight && window.scrollMaxY) {    // Mozilla    scrW = window.innerWidth + window.scrollMaxX;    scrH = window.innerHeight + window.scrollMaxY

  • js判断滚动条是否已到页面最底部或顶部实例

    本文实例讲述了js判断滚动条是否已到页面最底部或顶部的方法.分享给大家供大家参考.具体分析如下: 我们经常会看到很多的网站一个返回顶部效果就是当我们滚动条到指定位置时返回顶部出来了,否则就自动隐藏了,下面就来给大家介绍这种效果实现原理与方法. 当可视区域小于页面的实际高度时,判定为出现滚动条,即: 复制代码 代码如下: if (document.documentElement.clientHeight < document.documentElement.offsetHeight) scroll

  • js滚动条回到顶部的代码

    虽然平滑性处理的不好,但非常适合学习 复制代码 代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="tex

  • js/jquery获取浏览器窗口可视区域高度和宽度以及滚动条高度实现代码

    获取浏览器窗口的可视区域高度和宽度,滚动条高度有需要的朋友可参考一下.IE中,浏览器显示窗口大小只能以下获取: 代码如下复制代码 复制代码 代码如下: document.body.offsetWidth document.body.offsetHeight 在声明了DOCTYPE的浏览器中,可以用以下来获取浏览器显示窗口大小: 代码如下复制代码 复制代码 代码如下: document.documentElement.clientWidth document.documentElement.cli

  • 判断滚动条到底部的JS代码

    判断滚动条到底部,需要用到DOM的三个属性值,即scrollTop.clientHeight.scrollHeight. scrollTop为滚动条在Y轴上的滚动距离. clientHeight为内容可视区域的高度. scrollHeight为内容可视区域的高度加上溢出(滚动)的距离. 从这个三个属性的介绍就可以看出来,滚动条到底部的条件即为scrollTop + clientHeight == scrollHeight. 废话不多少说,赶紧上代码(兼容不同的浏览器). 复制代码 代码如下: /

  • JS JQUERY实现滚动条自动滚到底的方法

    设置页面加载时滚动条自动滚到底的方法: jQuery: 复制代码 代码如下: $(function(){   var h = $(document).height()-$(window).height();   $(document).scrollTop(h); }); JavaScript: 复制代码 代码如下: window.onload = function(){   var h = document.documentElement.scrollHeight || document.bod

  • 当滚动条滚动到页面底部自动加载增加内容的js代码

    1,注册页面滚动事件,window.onscroll = function(){ }; 2,相关获取页面高度.滚动条位置.文档高度的函数: 复制代码 代码如下: //获取滚动条当前的位置 function getScrollTop() { var scrollTop = 0; if (document.documentElement && document.documentElement.scrollTop) { scrollTop = document.documentElement.s

  • JS实现判断滚动条滚到页面底部并执行事件的方法

    需要了解三个dom元素,分别是:clientHeight.offsetHeight.scrollTop. clientHeight:这个元素的高度,占用整个空间的高度,所以,如果一个div有滚动条,那个这个高度则是不包括滚动条没显示出来的下面部分的内容.而只是单纯的DIV的高度. offsetHeight:是指元素内容的高度.依照上面的,那这个高度呢就是DIV内部的高度,包括可见部分及以滚动条下面的不可见部分. scrollTop:这个是什么呢?他可以理解为滚动条可以滚动的长度. 举例,如果一个

  • js实现滚动条滚动到某个位置便自动定位某个tr

    要实现带滚动条的table,定位到某个tr,其实是很简单的,只有几行js代码就可以完成,具体内容如下 js代码 <strong><script type="text/javascript"> function test(){ var $objTr = $("#location"); //找到要定位的地方 tr $objTr.css("background-color","lightgray"); //设

随机推荐