当前位置: 首页 > Web前端 > vue.js

秘密滚动条--el-scrollbar

时间:2023-03-31 22:28:45 vue.js

el-scrollbar是什么?Element-UI,作为一个非常知名的VueUI组件库,几乎玩过Vue的人都知道。最近在看Element源码的时候,发现了一个有趣的现象。为什么关联列表组件->自动完成组件的autocomplete-suggestions中有一个el-scrollbar组件。这是做什么用的?一番了解后,原来是Element自己写的一个滚动条组件(没有公开发布),屏蔽了原生的滚动条,换成了统一的样式,解决了滚动条的兼容性问题。如何使用?关于el-scrollbar的使用,可以看Github上的issues。这里简单演示一下:在el-scrollbar的默认槽中填入一个列表,并设置最外层包裹元素的高度,这样就会平滑的产生一个滚动条。效果如下:如何实现?先看刚才代码渲染的DOM:可以看到,我们的li是包裹在.el-scrollbar->.&__wrap->.&__view中的,下面还有两个DOM:.is-horizo??ntal和.is-vertical,每个元素都有自己的作用://根元素,包裹所有元素//包裹元素,是可视化视图端口元素,表示元素最终的窗口大小//布局视口元素,表示整个列表(以及它们的宽高),通过调整wrap的scrollTop/left,Displaydifferentviewcontent//默认slot中的内容会放在这里

...
//水平滚动Bar...
//verticalscrollbar
隐藏了原来的scrollbar在理解了wrap/view/bar的概念之后,我们直接看源码:element/packages/scrollbar/src/main.js这个文件是滚动条组件的入口文件,定义了/components/data/接受的一些props,最重要的是:render函数。当调用render函数时,它首先调用scrollbarWidth函数:letgutter=scrollbarWidth();thisgutter表示当前浏览器滚动条的宽度。元素通过scrollbarWidth方法获取这个宽度。点击这个方法,可以看到其实做了三件事:创建一个外层元素,设置宽度,此时获取offsetWidth设置外层元素溢出可见,然后创建一个内层元素,追加到外层(此时会产生滚动条),然后获取内层的offsetWidth。两者相减就是滚动条的宽度/*eslint-disableno-debugger*/importVuefrom'vue';letscrollBarWidth;exportdefaultfunction(){if(Vue.prototype.$isServer)return0;如果(滚动条宽度!==未定义)返回滚动条宽度;//创建外部div,这是一个普通的domconstouter=document.createElement('div');outer.className='el-scrollbar__wrap';外部风格。可见性='隐藏';outer.style.width='100px';outer.style.position='绝对';outer.style.top='-9999px';document.body.appendChild(外部);//获取dom的实际宽度constwidthNoScroll=outer.offsetWidth;//修改outerdom的css,设置为overflow:scroll(默认生成滚动条)outer.style.overflow='scroll';//创建内部div,并附加到外部constinner=document.createElement('div');inner.style.width='100%';outer.appendChild(内部);//计算内部div的实际宽度constwidthWithScroll=inner.offsetWidth;outer.parentNode.removeChild(outer);//计算滚动条的具体宽度,用“无滚动条宽度”减去“有滚动条宽度”scrollBarWidth=widthNoScroll-widthWithScroll;返回滚动条宽度;};获取滚动条的主要目的是隐藏它,这就是render函数接下来要做的事情constgutterStyle=`margin-bottom:${gutterWith};margin-right:${gutterWith};`;//根据传入的wrapStyle类型不同添加gutterStyleif(Array.isArray(this.wrapStyle)){style=toObject(this.wrapStyle);style.marginRight=style.marginBottom=gutterWith;}elseif(typeofthis.wrapStyle==='string'){style+=gutterStyle;}else{style=gutterStyle;}}创建DOM接着是DOM的创建过程,依次创建view/wrap(监听其滚动事件),以及非原生/原生版本的根元素。如果传入native:true,则表示使用scrollbar的nativescrollbar版本。if(!this.native){nodes=([wrap,,]);}else{nodes=([{[view]}]);}wrap窗口滚动时,会执行handleScroll方法更新data中的moveY和moveX属性。这两个会传给滚动条组件Bar,它的translateY()/translateX()会更新。稍后我们将讨论Bar组件。mount/beforeDestroy钩子在挂载时还做了一件事情,就是给视图元素添加一个resize事件的监听器(在beforeDestroy期间取消监听):!this.noresize&&addResizeListener(this.$refs.resize,this。更新);值得注意的是,addResizeListener并不是简单的设置window.resize回调,而是使用了一个新的api来监听DOM元素的resize:ResizeObserverAPI(详见这里的介绍)。一般来说,ResizeObserver可以直接给DOM绑定事件,专门用来观察DOM元素的大小是否发生变化,减少window.resize带来的冗余监听。为了监听一个元素的多个resize事件,element还使用了观察者模式,为DOM元素绑定了一个__resizeListeners__数组。当触发调整大小事件时,将执行整个__resizeListeners__数组的所有回调。调整DOM元素的大小后,将执行更新回调。那么你在更新期间做了什么?update(){让heightPercentage,widthPercentage;constwrap=this.wrap;如果(!换行)返回;//获取新的宽高比heightPercentage=(wrap.clientHeight*100/wrap.scrollHeight);widthPercentage=(wrap.clientWidth*100/wrap.scrollWidth);this.sizeHeight=(heightPercentage<100)?(heightPercentage+'%'):'';this.sizeWidth=(widthPercentage<100)?(widthPercentage+'%'):'';}更新方法负责更新Bar(可能是水平/垂直滚动条)的滑块长度。我们以垂直滚动条为例:首先通过clientHeight*100/scrollHeightRatio获取resizedwrapdisplayheight和totalheight,也就是滚动条滑块长度的比例,然后传给Bar组件表示滚动条以更新滚动条的高度。这时候如果ratio值大于100,就说明不再需要滚动条了,给Bar传递一个空字符串。点击/拖动滚动条至此,我们的滚动条组件已经创建完成,但是当我们点击或拖动滚动条时,这个组件是如何处理的呢?另外看看element/packages/scrollbar/src/bar.js这个组件。Bar组件负责显示滚动条,我们直接看它的render函数:render(h){//move属性用来控制滚动条的滚动位置const{size,move,bar}=this;返回();}我们可以看到重点在clickTrackHandler/clickThumbHandler这两个函数上,分别用来控制滚动条容器被点击时的行为,以及滚动条本身被点击时发生的行为。clickTrackHandler:快速跳转到某一段clickTrackHandler(e){/***0.以垂直滚动条为例:*this.bar.direction="top"/this.bar.client="clientY"/this.bar.offset="offsetHeight"/this.bar.scrollSize="scrollHeight"*1.getBoundingClientRect()[this.bar.direction]返回元素的顶部值(浏览器视口的高度值)*2.使用1从值中减去e.clientY(鼠标当前位置),然后使用Math.abs得到相对值,即鼠标在滚动条容器上的相对偏移量。*3.计算滚动条滑块的一半位置thumbHalf*4.offset-thumbHalf得到具体的偏移量,除以整个条的offsetHeight,得到滑块新位置的百分比。*5.接下来就可以愉快的更新wrap元素的scrollTop来显示新的内容啦~*6.wrap滚动完成后会触发handleScroll方法更新Bar组件的move值,从而更新位置滚动条。*/constoffset=Math.abs(e.target.getBoundingClientRect()[this.bar.direction]-e[this.bar.client]);constthumbHalf=(this.$refs.thumb[this.bar.offset]/2);//根据计算后的偏移量计算滚动条区域占总高度的比例,即滚动拇指的位置constthumbPositionPercentage=((offset-thumbHalf)*100/this.$el[this.bar。抵消]);//将shell的scrollHeight或scrollWidth设置为新值。实现滚动内容的效果this.wrap[this.bar.scroll]=(thumbPositionPercentage*this.wrap[this.bar.scrollSize]/100);},点击ThumbHandler:拖动滚动条滑块更新视图这里是主要是计算拖动时滑块的高度占整个滚动条高度的比例,从而更新wrap元素的scrollTop值。具体代码与clickTrackHandler类似,限于篇幅不再赘述。这里有一个小点,我们给slider元素绑定了onMousedown事件,但是mousemove和mouseup是给document绑定的,这是因为鼠标在移动过程中会比slider移动得更快,所以当slider元素失去了onMousemove事件,绑定mousemove时不能绑定到对应的元素上。总结整个滚动条元素的生命周期,我们可以看到element是如何创建滚动条的,如何监听元素的变化,如何控制滚动条的滑动等等。源码的阅读就到此为止了。如有错误或遗漏,请帮忙指出;如果你有所收获,那是我莫大的荣幸。感谢:element-uiel-scrollbar源码分析ResizeObserverAPI