好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

  }

  }

}

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

原文链接:https://blog.csdn.net/charissa2017/article/details/104098404

查看更多关于原生js实现自定义滚动条组件的详细内容...

  阅读:61次