亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

深入分析element ScrollBar滾動組件源碼

2019-11-19 12:14:18
字體:
來源:轉載
供稿:網友

scrollbar組件根目錄下包括index.js文件和src文件夾,index.js是用來注冊Vue插件的地方,沒什么好說的,不了解的童鞋可以看一下Vue官方文檔中的插件,src目錄下的內容才是scrollbar組件的核心代碼,其入口文件是main.js。

在開始分析源碼之前,我們先來說一下自定義滾動條的原理,方便大家更好的理解。

如圖,黑色wrap為滾動的可顯示區域,我們的滾動內容就是在這個區域中滾動,view是實際的滾動內容,超出wrap可顯示區域的內容都將被隱藏。右側track是滾動條的滾動滑塊thumb上下滾動的軌跡

當wrap中的內容溢出的時候,就會產生各瀏覽器的原生滾動條,要實現自定義滾動條,我們必須將原生滾動條消滅掉。假設我們給wrap外面再包一層div,并且把這個div的樣式設為 overflow:hidden ,同時我們給wrap的marginRight,marginBottom設置一個負值,值得大小正好等于原生滾動條的寬度,那么這個時候由于父容器的overflow:hidden屬性,正好就可以將原生滾動條隱藏掉。然后我們再將自定義的滾動條絕對定位到wrap容器的右側和下側,并加上滾動、拖拽事件等滾動邏輯,就可以實現自定義滾動條了。

接下來我們從main.js入口開始,詳細分析一下element是如何實現這些邏輯的。

main.js文件中直接導出一個對象,這個對象采用render函數的方式渲染scrollbar組件,組件對外暴漏的接口如下:

props: { native: Boolean, // 是否采用原生滾動(即只是隱藏掉了原生滾動條,但并沒有使用自定義的滾動條) wrapStyle: {}, // 內聯方式 自定義wrap容器的樣式 wrapClass: {}, // 類名方式 自定義wrap容器的樣式 viewClass: {}, // 內聯方式 自定義view容器的樣式 viewStyle: {}, // 類名方式 自定義view容器的樣式 noresize: Boolean, // 如果 container 尺寸不會發生變化,最好設置它可以優化性能 tag: { 				// view容器用那種標簽渲染,默認為div type: String, default: 'div' }}

可以看到,這就是整個ScrollBar組件對外暴露的接口,主要包括了自定義wrap,view樣式的接口,以及用來優化性能的noresize接口。

然后我們再來分析一下render函數:

render(){	let gutter = scrollbarWidth(); // 通過scrollbarWidth()方法 獲取瀏覽器原生滾動條的寬度 let style = this.wrapStyle; if (gutter) { const gutterWith = `-${gutter}px`;  // 定義即將應用到wrap容器上的marginBottom和marginRight,值為上面求出的瀏覽器滾動條寬度的負值 const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`; // 這一部分主要是根據接口wrapStyle傳入樣式的數據類型來處理style,最終得到的style可能是對象或者字符串 if (Array.isArray(this.wrapStyle)) {  style = toObject(this.wrapStyle);  style.marginRight = style.marginBottom = gutterWith; } else if (typeof this.wrapStyle === 'string') {  style += gutterStyle; } else {  style = gutterStyle; } }  ...}

這一塊代碼中最重要的知識點就是獲取瀏覽器原生滾動條寬度的方式了,為此element專門定義了一個方法scrllbarWidth,這個方法是從外部導入進來的 import scrollbarWidth from 'element-ui/src/utils/scrollbar-width'; ,我們一起來看一下這個函數:

import Vue from 'vue';let scrollBarWidth;export default function() { if (Vue.prototype.$isServer) return 0; if (scrollBarWidth !== undefined) return scrollBarWidth; const outer = document.createElement('div'); outer.className = 'el-scrollbar__wrap'; outer.style.visibility = 'hidden'; outer.style.width = '100px'; outer.style.position = 'absolute'; outer.style.top = '-9999px'; document.body.appendChild(outer); const widthNoScroll = outer.offsetWidth; outer.style.overflow = 'scroll'; const inner = document.createElement('div'); inner.style.width = '100%'; outer.appendChild(inner); const widthWithScroll = inner.offsetWidth; outer.parentNode.removeChild(outer); scrollBarWidth = widthNoScroll - widthWithScroll; return scrollBarWidth;};

其實也很簡單,就是動態創建一個body的子元素outer,給固定寬度100px,并且將overflow設置為scroll,這樣wrap就產生滾動條了,這個時候再動態創建一個outer的子元素inner,將其寬度設置為100%。由于outer有滾動條存在,inner的寬度必然不可能等于outer的寬度,此時用outer的寬度減去inner的寬度,得出的就是瀏覽器滾動條的寬度了。是不是也很簡單啊,最后記得從body中銷毀動態創建outer元素哦。

回過頭來我們接著看render函數,在根據瀏覽器滾動條寬度及wrapStyle動態生成樣式變量style之后,接下來就是在render函數中生成ScrollBar組件的 HTML了。

// 生成view節點,并且將默認slots內容插入到view節點下const view = h(this.tag, { class: ['el-scrollbar__view', this.viewClass], style: this.viewStyle, ref: 'resize'}, this.$slots.default);// 生成wrap節點,并且給wrap綁定scroll事件const wrap = ( <div 	ref="wrap" 	style={ style }		onScroll={ this.handleScroll }		class={ [this.wrapClass, 'el-scrollbar__wrap', gutter ? '' : 'el-scrollbar__wrap--hidden-default'] }> 		{ [view] }	</div>);

接著是根據native來組裝wrap,view生成整個HTML節點樹了。

let nodes;if (!this.native) { nodes = ([ wrap, <Bar 	move={ this.moveX }			size={ this.sizeWidth }></Bar>,		<Bar  vertical  move={ this.moveY }  size={ this.sizeHeight }></Bar>	]);} else { nodes = ([ <div  ref="wrap"  class={ [this.wrapClass, 'el-scrollbar__wrap'] }			style={ style }> 			 { [view] }		</div>	]);}return h('div', { class: 'el-scrollbar' }, nodes);

可以看到如果native為false,則使用自定義的滾動條,如果為true,則不使用自定義滾動條。簡化上面的render函數生成的HTML如下:

<div class="el-scrollbar"> <div class="el-scrollbar__wrap"> <div class="el-scrollbar__view"> 	this.$slots.default </div> </div> <Bar vertical move={ this.moveY } size={ this.sizeHeight } /> <Bar move={ this.moveX } size={ this.sizeWidth } /></div>

最外層的el-scrollbar設置了overflow:hidden,用來隱藏wrap中產生的瀏覽器原生滾動條。使用ScrollBar組建時,寫在ScrollBar組件中的內容都將通過slot分發到view內部。另外這里使用move,size和vertical三個接口調用了Bar組件,這個組件就是原理圖上的Track和Thumb了。下面我們來看一下Bar組件:

props: { vertical: Boolean, // 當前Bar組件是否為垂直滾動條 size: String, // 百分數,當前Bar組件的thumb長度 / track長度的百分比  move: Number // 滾動條向下/向右發生transform: translate的值},

Bar組件的行為都是由這三個接口來進行控制的,在前面的分析中,我們可以看到,在scrollbar中調用Bar組件時,分別傳入了這三個props。那么父組件是如何初始化以及更新這三個參數的值,從而達到更新Bar組件的呢。首先在mounted鉤子中調用update方法對size進行初始化:

update() { let heightPercentage, widthPercentage; const wrap = this.wrap; if (!wrap) return; heightPercentage = (wrap.clientHeight * 100 / wrap.scrollHeight); widthPercentage = (wrap.clientWidth * 100 / wrap.scrollWidth); this.sizeHeight = (heightPercentage < 100) ? (heightPercentage + '%') : ''; this.sizeWidth = (widthPercentage < 100) ? (widthPercentage + '%') : '';}

可以看到,這里核心的內容就是計算thumb的長度heightPercentage/widthPercentage。這里使用wrap.clientHeight / wrap.scrollHeight得出了thumb長度的百分比。這是為什么呢

分析前面我們畫的那張scrollbar的原理圖,thumb在track中上下滾動,可滾動區域view在可視區域wrap中上下滾動,可以將thumb和track的這種相對關系看作是wrap和view相對關系的一個 微縮模型 (微縮反應),而滾動條的意義就是用來反映view和wrap的這種相對運動關系的。從另一個角度,我們可以將view在wrap中的滾動反過來看成是wrap在view中的上下滾動,這不就是一個放大版的滾動條嗎?

根據這種相似性,我們可以得出一個比例關系: wrap.clientHeight / wrap.scrollHeight = thumb.clientHeight / track.clientHeight。在這里,我們并不需要求出具體的thumb.clientHeight的值,只需要根據thumb.clientHeight / track.clientHeight的比值,來設置thumb 的css高度的百分比就可以了。

另外還有一個需要注意的地方,就是當這個比值大于等于100%的時候,也就是wrap.clientHeight(容器高度)大于等于 wrap.scrollHeight(滾動高度)的時候,此時就不需要滾動條了,因此將size置為空字符串。

接下來我們再來看一下move,也就是滾動條滾動位置的更新。

handleScroll() { const wrap = this.wrap; this.moveY = ((wrap.scrollTop * 100) / wrap.clientHeight); this.moveX = ((wrap.scrollLeft * 100) / wrap.clientWidth);}

moveX/moveY用來控制滾動條的滾動位置,當這個值傳給Bar組件時,Bar組件render函數中會調用 renderThumbStyle 方法將它轉化為trumb的樣式 transform: translateX(${moveX}%) / transform: translateY(${moveY}%) 。由之前分析的相似關系可知,當wrap.scrollTop正好等于wrap.clientHeight的時候,此時thumb應該向下滾動它自身長度的距離,也就是transform: translateY(100%)。所以,當wrap滾動的時候,thumb應該向下滾動的距離正好是 transform: translateY(wrap.scrollTop / wrap.clientHeight )。這就是wrap滾動函數handleScroll中的邏輯所在。

現在我們已經完全弄清楚了scrollbar組件中的所有邏輯,接下來我們再看看Bar組件在接收到props之后是如何處理的。

render(h) { const { size, move, bar } = this; return ( <div  class={ ['el-scrollbar__bar', 'is-' + bar.key] }  onMousedown={ this.clickTrackHandler } >  <div  ref="thumb"  class="el-scrollbar__thumb"  onMousedown={ this.clickThumbHandler }  style={ renderThumbStyle({ size, move, bar }) }>  </div> </div> );}

render函數獲取父組件傳遞的size,move之后,通過 renderThumbStyle 來生成thumb,并且給track和thumb分別綁定了onMousedown事件。

clickThumbHandler(e) { this.startDrag(e); // 記錄this.y , this.y = 鼠標按下點到thumb底部的距離 // 記錄this.x , this.x = 鼠標按下點到thumb左側的距離 this[this.bar.axis] = (e.currentTarget[this.bar.offset] - (e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]));}, // 開始拖拽函數startDrag(e) { e.stopImmediatePropagation(); // 標識位, 標識當前開始拖拽 this.cursorDown = true; // 綁定mousemove和mouseup事件 on(document, 'mousemove', this.mouseMoveDocumentHandler); on(document, 'mouseup', this.mouseUpDocumentHandler);  // 解決拖動過程中頁面內容選中的bug document.onselectstart = () => false;}, mouseMoveDocumentHandler(e) { // 判斷是否在拖拽過程中, if (this.cursorDown === false) return; // 剛剛記錄的this.y(this.x) 的值 const prevPage = this[this.bar.axis]; if (!prevPage) return; // 鼠標按下的位置在track中的偏移量,即鼠標按下點到track頂部(左側)的距離 const offset = ((this.$el.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]) * -1); // 鼠標按下點到thumb頂部(左側)的距離 const thumbClickPosition = (this.$refs.thumb[this.bar.offset] - prevPage); // 當前thumb頂部(左側)到track頂部(左側)的距離,即thumb向下(向右)偏移的距離 占track高度(寬度)的百分比 const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 / this.$el[this.bar.offset]);	// wrap.scrollHeight / wrap.scrollLeft * thumbPositionPercentage得到wrap.scrollTop / wrap.scrollLeft // 當wrap.scrollTop(wrap.scrollLeft)發生變化的時候,會觸發父組件wrap上綁定的onScroll事件, // 從而重新計算moveX/moveY的值,這樣thumb的滾動位置就會重新渲染 this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);},mouseUpDocumentHandler(e) { // 當拖動結束,將標識位設為false this.cursorDown = false; // 將上一次拖動記錄的this.y(this.x)的值清空 this[this.bar.axis] = 0; // 取消頁面綁定的mousemove事件 off(document, 'mousemove', this.mouseMoveDocumentHandler); // 清空onselectstart事件綁定的函數 document.onselectstart = null;}

上面的代碼就是thumb滾動條拖拽的所有處理邏輯,整體思路就是在拖拽thumb的過程中,動態的計算thumb頂部(左側)到track頂部(左側)的距離占track本身高度(寬度)的百分比,然后利用這個百分比動態改變wrap.scrollTop的值,從而觸發頁面滾動以及滾動條位置的重新計算,實現滾動效果。

上一個圖方便大家理解吧( ̄亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

国产一区二区成人| 亚洲精品成人网| 日本精品久久久| 亚洲成人精品av| 国产精品日韩在线| 国产免费一区二区三区在线能观看| 亚洲日韩欧美视频| 51精品国产黑色丝袜高跟鞋| 91午夜理伦私人影院| 日韩精品视频免费专区在线播放| 97在线视频免费看| www.欧美精品| 欧美日韩一区二区在线| 欧美放荡办公室videos4k| 国产精品亚洲综合天堂夜夜| 成人亚洲激情网| 国内揄拍国内精品少妇国语| 欧美寡妇偷汉性猛交| 日韩中文在线视频| 国产日韩精品在线观看| 欧美成人午夜激情在线| 国产丝袜精品视频| 欧美多人爱爱视频网站| 亚洲深夜福利视频| 日韩福利在线播放| 亚洲性日韩精品一区二区| 欧美性xxxx在线播放| 久久成人这里只有精品| 国产精品视频免费在线观看| 国内精品中文字幕| 欧美高清视频在线观看| 亚洲aa中文字幕| 日韩中文字幕精品| 成人免费看黄网站| 亚洲欧美日本精品| 国产精品国产三级国产aⅴ9色| 91精品视频免费| 欧美精品久久久久久久| 国产精品一区二区三区久久| 日本视频久久久| 欧美成人免费播放| 91国自产精品中文字幕亚洲| 日韩欧美国产中文字幕| 欧美性猛交xxx| 亚洲男人的天堂在线| 国产噜噜噜噜久久久久久久久| 欧美一区二区三区免费视| 一区二区三区无码高清视频| 久久久精品在线观看| 成人a在线视频| 欧美日韩精品在线观看| 91国内精品久久| 国产精品99导航| 欧美激情综合亚洲一二区| 91精品国产高清久久久久久| 91成人天堂久久成人| 日韩欧美亚洲一二三区| 国产精品视频公开费视频| 亚洲久久久久久久久久久| 中文字幕国产亚洲| 精品五月天久久| 亚洲一区二区三区乱码aⅴ| 日韩亚洲精品电影| 日本精品免费一区二区三区| 色综合天天综合网国产成人网| 92看片淫黄大片看国产片| 欧美精品一本久久男人的天堂| 日韩精品在线免费观看视频| 久久精品免费播放| www.亚洲一区| 一个色综合导航| 红桃视频成人在线观看| 欧美丝袜美女中出在线| 精品视频一区在线视频| 国产精品视频内| 亚洲摸下面视频| 91精品国产高清久久久久久| 中文字幕亚洲欧美日韩2019| 亚洲成年人在线| 欧美精品18videos性欧美| 国产婷婷97碰碰久久人人蜜臀| 欧美日韩成人在线视频| 精品人伦一区二区三区蜜桃免费| 色七七影院综合| 欧美一级bbbbb性bbbb喷潮片| 久久免费视频网站| 不卡在线观看电视剧完整版| 92国产精品久久久久首页| 久久综合国产精品台湾中文娱乐网| 久久综合伊人77777| 亚洲综合国产精品| 久久精品精品电影网| 国产成人97精品免费看片| 久久久久久久久国产| 国产欧美精品日韩精品| 国产精品久久久久久久久久三级| 日本免费久久高清视频| 欧美国产日韩一区二区三区| 91av在线免费观看| 国产亚洲人成网站在线观看| 欧美精品日韩三级| 清纯唯美亚洲激情| 91精品成人久久| 91香蕉国产在线观看| 91av在线视频观看| 成人黄色av网| 97视频在线观看成人| 日本高清不卡的在线| 亚洲a一级视频| 亚洲一区二区中文字幕| 亚洲无av在线中文字幕| 久久久亚洲影院| 91色p视频在线| 国产精品羞羞答答| 91久久国产综合久久91精品网站| 青青久久av北条麻妃黑人| 日韩精品视频在线观看免费| 成人午夜高潮视频| 91精品国产免费久久久久久| 免费99精品国产自在在线| 亚洲成人av在线| 韩国福利视频一区| 97在线观看免费高清| 久青草国产97香蕉在线视频| 日韩电影视频免费| 欧美精品国产精品日韩精品| 精品日本美女福利在线观看| 97精品一区二区视频在线观看| 精品福利樱桃av导航| 亚洲精品电影网| 亚洲综合在线播放| 成人性生交大片免费观看嘿嘿视频| 91黑丝在线观看| 性欧美xxxx视频在线观看| 亚洲精品天天看| 日韩一区二区精品视频| 亚洲欧美在线磁力| 8x拔播拔播x8国产精品| 欧美限制级电影在线观看| 国内精品久久久久久影视8| 成人亚洲欧美一区二区三区| 亚洲成年人在线播放| 91网站在线看| 国产欧美日韩免费看aⅴ视频| 国产视频久久久久久久| 福利视频一区二区| 亚洲国产精品va在看黑人| 自拍偷拍亚洲区| 亚洲一区二区三区在线视频| 深夜福利日韩在线看| 欧美成人激情图片网| 91精品一区二区| 精品久久久久久国产91| 国产精自产拍久久久久久| 亚洲午夜激情免费视频| 日韩精品在线视频| 日韩有码在线视频| 大量国产精品视频| 超碰精品一区二区三区乱码| 国产成人精品日本亚洲| 91精品视频免费| 国产一区二区日韩精品欧美精品| 亚洲少妇中文在线| 中文字幕亚洲图片|