手机版

深入分析元素滚动条滚动组件源代码

时间:2021-08-27 来源:互联网 编辑:宝哥软件园 浏览:

Scrollbar组件根目录包括index.js文件和src文件夹。index.js是注册Vue插件的地方,所以没什么好说的。不太懂的童鞋可以看看Vue官方文档中的插件。src目录中的内容是scrollbar组件的核心代码,它的入口文件是main.js

在开始分析源代码之前,先说一下自定义滚动条的原理,这样可以更好的理解。

如图所示,黑色环绕是一个滚动可显示区域,我们的滚动内容就是在这个区域滚动。视图是实际的滚动内容,包装器可显示区域之外的内容将被隐藏。右边的轨道是滚动条的滚动滑块向上和向下滚动的轨道

当包装中的内容溢出时,将生成每个浏览器的本机滚动条。要实现自定义滚动条,必须取消原生滚动条。假设我们在包装之外包装另一层div,并将这个div的样式设置为overflow:hidden。同时,我们设置了一个负值marginRight,即换行的marginBottom,刚好等于原生滚动条的宽度。此时,由于父容器的溢出3360隐藏属性,可以隐藏本机滚动条。然后,我们明确将自定义滚动条定位到wrap容器的右下方,加上滚动、拖拽事件等滚动逻辑,就可以实现自定义滚动条了。

接下来,我们从main.js门户开始,详细分析element如何实现这些逻辑。

对象直接从main.js文件中导出,该文件使用render函数来呈现滚动条组件。组件与外部暴露的界面如下:

Props: { native: boolean,//是否使用本机滚动(即只隐藏本机滚动条,不使用用户定义的滚动条)wrapStyle: {},//在内联换行中自定义Wrapclass 3360 {}的样式。//按类名自定义包装容器的样式。//通过内联自定义视图容器的样式。//按类名自定义视图容器的样式。Noresize :布尔值。//如果容器大小不变,最好设置为优化性能。tag: {//视图容器是用哪个标记呈现的,默认为div type: String,默认为:' div'}}。可以看到,这是整个ScrollBar组件的暴露界面,主要包括自定义换行、视图样式界面和noresize界面,用于优化性能。

然后让我们分析渲染函数:

render(){ let gutter=scrollbarWidth();//通过scrollbarWidth()方法让style=this.wrapStyle获取浏览器原生滚动条的宽度;if(gutter){ const gutter with=`-$ { gutter } px `;//定义要应用于包装容器的边距宽度和边距宽度。该值是上面获得的浏览器滚动条宽度的负值。Const装订线样式=` margin-bottom 3360 $ {装订线with };margin-right : $ { gutterWith };`;//这部分主要是根据接口wrapStyle传入的样式的数据类型来处理样式,最终得到的样式可能是一个对象,也可能是一个字符串if (array。ISARRAY(这个。wrap style(){ style=to object(this。wrap style);style . margin right=style . margin bottom=gutterWith;} else if(type of this . wrap style==' string '){ style=Gutterstyle;} else { style=gutterStyle}} .}这段代码中最重要的知识点是获取浏览器原生滚动条宽度的方法。为此,element专门定义了一个方法scrllbarWidth,从外部导入,从' element-ui/src/utils/scroll bar-width ';让我们看看这个函数:

从“Vue”导入Vue让滚动条宽度导出默认函数(){ if (Vue.prototype.$isServer)返回0;if (scrollBarWidth!==未定义)返回scrollBarWidthconst outer=文档。创建元素(' div ');外部。类名=' El-滚动条_ _换行';外部。风格。可见性='隐藏';外部。风格。宽度=' 100像素';外部。风格。position=“绝对”;外部。风格。top='-9999 px ';document.body.appendChild(外部);常量宽度no croll=外部。用.抵消;外部。风格。溢出=“滚动”;const inner=文档。创建元素(' div ');内心。风格。宽度=' 100% ';outer.appendChild(内部);croll=内部的常量宽度。用.抵消;outer.parentNode.removeChild(外部);滚动条宽度=宽度无croll-宽度带croll;返回scroll bar width };其实也很简单,就是动态创建一个身体的子元素外部,给固定宽度100像素,并且将泛滥设置为滚动,这样包就产生滚动条了,这个时候再动态创建一个外面的的子元素内心的,将其宽度设置为100%。由于外面的有滚动条存在内侧的宽度必然不可能等于外面的的宽度,此时用外面的的宽度减去内部的的宽度,得出的就是浏览器滚动条的宽度了。是不是也很简单啊,最后记得从身体中销毁动态创建外面的元素哦。

回过头来我们接着看提出函数,在根据浏览器滚动条宽度及wrapStyle动态生成样式变量风格之后,接下来就是在提出函数中生成滚动条组件的超文本标记语言了。

//生成视角节点,并且将默认时间内容插入到视角节点下const view=h(this.tag,{ class :[' El-滚动条_ _ view ',this.viewClass],style: this.viewStyle,ref: 'resize'},this .$老虎机。默认);//生成包节点,并且给包绑定卷起事件const wrap=(div ref=' wrap ' style={ style } onScroll={ this。handlescroll } class={[this。换行类,' el-scrollbar__wrap ',装订线? ' El-滚动条_ _ wrap-hidden-default ']} {[view]}/div);接着是根据当地的来组装包装,查看生成整个超文本标记语言节点树了。

让节点;if(!this.native) { nodes=([ wrap,Bar move={ this。movex } size={ this。尺寸宽度}/条形,条形垂直移动={这个。movey } size={ this。sizeheight }/[Bar]);} else { nodes=([div ref=' wrap ' class={[this。wrap类,' El-滚动条_ _ wrap ']} style={ style } {[view]}/div]);}返回h('div ',{ class: 'el-scrollbar' },节点);可以看到如果当地的为假的,则使用自定义的滚动条,如果为没错,则不使用自定义滚动条。简化上面的提出函数生成的超文本标记语言如下:

div class=' El-滚动条' div class=' El-滚动条_ _环绕' div class=' El-滚动条_ _视图'这个$slots .默认/div /div栏垂直移动={ this.moveY }大小={ this.sizeHeight }/Bar移动={ this.moveX }大小={ this.sizeWidth } //div最外层的埃尔滚动条设置了飞越:隐藏,用来隐藏包中产生的浏览器原生滚动条。使用滚动条组建时,写在滚动条组件中的内容都将通过狭槽分发到视角内部。另外这里使用移动,大小和垂直的三个接口调用了酒吧组件,这个组件就是原理图上的轨道和拇指了。下面我们来看一下酒吧组件:

props: { vertical:布尔值,//当前酒吧组件是否为垂直滚动条尺寸:字符串,//百分数,当前酒吧组件的拇指长度/track长度的百分比move: Number //滚动条向下/向右发生变压器:翻译的值},酒吧组件的行为都是由这三个接口来进行控制的,在前面的分析中,我们可以看到,在卷动条中调用酒吧组件时,分别传入了这三个道具。那么父组件是如何初始化以及更新这三个参数的值,从而达到更新酒吧组件的呢。首先在安装好的钩子中调用更新方法对大小进行初始化:

update(){ let height percent,widthPercentageconst wrap=this.wrapif(!包装)返回;height percent=(wrap . client height * 100/wrap . scroll height);宽度百分比=(wrap . client width * 100/wrap . scrollwidth);this . sizeheight=(height percent 100)?(高百分比“%”):“”;this . size width=(width percent 100)?(宽度百分比“%”):“”;}可以看到,这里的核心内容是计算拇指的长高百分比/宽百分比。这里,wrap.clientheight/wrap.scrollheight用来得到拇指长度的百分比。为什么呢?

根据我们之前画的滚动条示意图分析,thumb在track中上下滚动,可滚动区视图在可视区换行中上下滚动。拇指和轨迹的这种相对关系可以看作是环绕和视图相对关系的微型模型(微型反应),滚动条的意义是反映视图和环绕之间的相对运动关系。从另一个角度来看,我们可以把视图中的滚动看作视图中的上下滚动。这不是放大的滚动条吗?

根据这个相似度,我们可以得到一个比例关系:wrap.clientheight/wrap.scrollheight=thumb.clientheight/track.clientheight.在这里,我们不需要指定thumb.clientHeight的值,只需要根据thumb的比例设置thumb的css高度的百分比即可。ClientHeight/track。ClientHeight。

另外需要注意的一点是,当比值大于等于100%时,即wrap.clientHeight(容器高度)大于等于wrap.scrollHeight(滚动高度)时,此时不需要滚动条,因此大小设置为空字符串。

接下来我们来看看move,它是滚动条滚动位置的更新。

handleScroll(){ const wrap=this . wrap;this . movey=((wrap . scroll top * 100)/wrap . client height);this . movex=((wrap . scrolleft * 100)/wrap . client width);}moveX/moveY用于控制滚动条的滚动位置。将此值传递给Bar组件后,将在Bar组件的renderThumbstyle函数中调用renderThumbstyle方法,将其转换为trumb的样式:transform 3360 Translatex($ { movex } %)/transform 3360 Translatey($ { movey } %)。根据之前分析过的类似关系,当wrap.scrollTop正好等于wrap.clientHeight时,拇指应该按照自己的长度向下滚动,也就是transform: translateY(100%)。因此,当wrap滚动时,拇指向下滚动的距离正好是transform : translatey(wrap . scroll top/wrap . client height)。这就是换行滚动函数handleScroll中的逻辑。

现在我们已经完全理解了scrollBar组件中的所有逻辑,让我们看看Bar组件在收到道具后是如何处理的。

render(h) { const { size,move,bar }=thisreturn(div class={[' El-scroll bar _ _ bar ',' is-' bar . key]} onMousedown={ this . clicktrackhandler } div ref=' thumb ' class=' El-scroll bar _ _ thumb ' onMousedown={ this . clickthumbhandler } style={ renderThumbStyle({ size,move,bar })}/div/div);}render函数获取父组件传递的大小。移动后,thumb由renderThumbStyle生成,onMousedown事件分别绑定到track和thumb。

单击拇指处理程序(e){ this . start drag(e);//记录this.y,this.y=鼠标下点到拇指底部的距离//记录this.x,This.x=鼠标下点到拇指左侧的距离this [this。bar . axis]=(e . current target[this。bar . offset]-(e . current target . getboundingclient()[this。bar . direction]));},//开始拖动函数Start drag(e){ e . stop immediatepropagation();//标志位,表示目前开始拖动这个。光标向下=真;//将mousemove和mouseup事件绑定在(document,' mousemove ',this。mousemovedocumenthandler);on(document,‘mouse up’,this . mouseupdocumenthandler);//解决bugdocument。onselectstart=()=false在拖动过程中被页面内容选中;},mousemovedocumenthandler(e){//if(this。光标下移===false)返回;//刚才记录的this.y(this.x)的值const pre page=this[this。酒吧。轴];if(!prevPage)返回;//鼠标按下位置在轨迹中的偏移量,即鼠标按下点到轨迹顶部(左侧)的距离constoffset=((this。$ El . getboundingclientcorrect()[这。这个。bar . client])*-1);//鼠标按点到拇指顶部(左侧)的距离为const thumbclickposition=(this。$ refs.thumb [this。bar . offset]-pre page);//当前拇指顶部(左侧)到轨道顶部(左侧)的距离,即拇指向下(右侧)偏移距离占轨道高度(宽度)的百分比Const thumb position percent=((offset-thumb click position)* 100/this。$ El[这个。bar . offset]);//wrap . scroll height/wrap . scroll left *缩略图百分比获取wrap.scrolltop/wrap.scrollleft//when wrap . scroll top(wrap . scroll left)更改后,将触发绑定在父组件wrap上的onScroll事件,//这样将重新计算moveX/moveY的值,这样。包装这个。bar.scroll]=(指位百分比*这个。包装这个。bar.scrollsize]/100)将在拇指的滚动位置重新呈现。},mouseUpDocumentHandler(e) {//拖动完成后,将标志位设置为false this.cursorDown=false//清除上次拖动记录的this.y(this.x)值。this[this . bar . axis]=0;//解除页面绑定的mousemove事件关闭(文档,“mousemove”,这。mousemovedocumenthandler);//清空函数文档,onselectstart=由onselectstart事件绑定的null}以上代码是thumb滚动条拖动的全部处理逻辑。整个思路是动态计算拇指顶(左)到轨迹顶(左)到轨迹高(宽)的距离的百分比,然后用这个百分比动态改变wrap.scrollTop的值,从而触发页面滚动和滚动条位置的重新计算,达到滚动效果。

上图方便大家理解( )'

track的onMousedown和trumb的逻辑类似。有两点需要注意:mousemove和mouseup事件不会绑定到track的onMousedown事件回调中的页面,因为track相当于click事件。我们计算拇指顶部到轨迹顶部的方法是从鼠标点击点到轨迹顶部的距离减去拇指高度的一半,因为拇指中点正好在鼠标点击轨迹后点击点的位置。至此,对整个滚动条源代码的分析就结束了。回过头来看,滚动条的实现其实并不难,主要是要明确各种滚动关系,拇指的长度,以及如何通过换行和视图的关系来确定滚动位置。这一部分可能相当复杂。不了解的同学建议做一些关于手绘动画的研究。只要他们明白这个滚动原理,就会很容易实现。

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

版权声明:深入分析元素滚动条滚动组件源代码是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。