Element-ui Layout佈局(Row和Col元件)的實現
目錄
- 基本說明以及用法
- Row元件的分析
- render函式
- 原始碼分析
- Col元件的分析
- 元件分析
- 響應式佈局
我們在實際開發中遇到一些佈局的時候會用到Layout佈局,這個佈局只要配置一些引數就能夠達到很好的佈局效果甚至可以響應式,那裡面的具體是怎麼實現的呢,讓我們去剖開Element-UI的原始碼,學習裡面的一些細節吧。
基本說明以及用法
Element-UI的Layout佈局是通過基礎的24分欄,迅速簡便地建立佈局。根據不同的組合,很快的就能夠生成一個很美觀的響應式佈局。具體的用法如下:
<el-row> <el-col :span="24"><div class="grid-content bg-purple-dark"></div></el-col> </el-row>
由上述的示例程式碼可以看出Row元件主要是建立每行分欄的佈局方式,比如之間的一些間隔、對齊方式等。而Col則建立每個分欄,分欄的長度、偏移量等。我們可以進行自由組合每個分欄,從而達到一種響應式效果。
Row元件的分析
render函式
我們都知道除了可以使用template模板編寫元件外,有時候我們還可以直接使用render函式去編寫一個元件。因為template模板最終也是編譯http://www.cppcns.com成了render函式。
為什麼會有render函式的寫法?比如現在有個需求:根據動態的level生成從h1-h6字型大小的標題的時候,我們如果使用template去實現的話我們頁面中可能會出現很多類似這樣的虛擬碼:
<template> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6> </template>
但是如果是使用render函式則是比較簡單:
Vue.component('anchored-heading',{ render: function (createElement) { return createElement( 'h' + this.level,// 標籤名稱 this.$slots.default // 子節點陣列 ) },props: { level: { type: Number,required: true } } })
這裡還有一個程式碼優化點是。this.$slots.default儲存的就是插槽內容,不需要寫那麼多遍。
原始碼分析
Row元件的原始碼比較簡單,因為我們可以通過tag這個prop對其指定一個渲染標籤,所以元件是直接使用render渲染函式進行編寫。 render函式部分如下:
render(h) { return h(this.tag,{ class: [ 'el-row',this.justify !== 'start' ? `is-justify-${this.justify}` : '',this.align !== 'top' ? `is-align-${thwww.cppcns.comis.align}` : '',{ 'el-row--flex': this.type === 'flex' } ],style: this.style },this.$slots.default); }
如上的原始碼可以得出Row主要是控制class名來進行控制內容佈局的。這裡有gutter屬效能夠控制行內列的間隔數。如果說我們設定為gutter=20,那麼每個列項都進行左右間距10px,那麼就會出現個問題:第一個列項跟最後一個列項會出現左右的間距。那該如何讓第一個跟最後一個左右間隔去掉這個10px呢?Row的處理方案是這個行左右各偏-10px,所以用了一個計算屬性來設定樣式:
computed: { style() { const ret = {}; if (this.gutter) { ret.marginLeft = `-${this.gutter / 2}px`; ret.marginRight = ret.marginLeft; } return ret; } },
Col元件的分析
元件分析
Col主要是為了設定每一列的長度以及偏移量。主要的屬性是span、offset;同樣這個元件也是採用render函式去編寫,首先我們看如何通過span、offset去控制列的,原始碼如下:
render(h) { let classList = []; let style = {}; ... ['span','offset','pull','push'].forEach(prop => { if (this[prop] || this[prop] === 0) { classList.push( prop !== 'span' ? `el-col-${prop}-${this[prop]}` : `el-col-${this[prop]}` ); } }); ... return h(this.tag,{ class: ['el-col',classList],style },this.$slots.default); }
從這可以看出,col的列寬是通過不同clawww.cppcns.comss名去做控制的。我們找到對應的.s檔案,發現他使用了sass@for迴圈語句去計算不同格子的寬度:
@for $i from 0 through 24 { .el-col-#{$i} { width: (1 / 24 * $i * 100) * 1%; } .el-col-offset-#{$i} { margin-left: (1 / 24 * $i * 100) * 1%; } .el-col-pull-#{$i} { position: relative; right: (1 / 24 * $i * 100) * 1%; } .el-col-push-#{$i} { position: relative; left: (1 / 24 * $i * 100) * 1%; } }
同理offset也是使用相同的邏輯。這樣我們就可以根據不同的span、跟offset混合組合不同風佈局了,是不是感覺背後的邏輯是如此的簡單呢。我們再思考一個問題就是如果我們要控制一組相同的列寬間隔,需要一個個的去做設定麼?答案是不用的,我們可以藉助上述的Row元件中的gutter屬性去做統一設定。那怎麼實現的呢?原始碼如下:
computed: { gutter() { let parent = this.$parent; while (parent && parent.$options.componentName !== 'ElRow') { parent = parent.$parent; } return parent ? parent.gutter : 0; } }
我們通過往上遍歷父元件,如果父元件的元件名為ElRow,則取到gutter值,然後讓元件左右內邊距設定對應的值就好了:
if (this.gutter) { style.paddingLeft = this.gutter / 2 + 'px'; style.paddingRight = style.paddingLeft; }
這樣我們就解決了統一列寬設定的問題;
響應式佈局
這裡我們用到了css3中的媒體查詢來進行響應式佈局,相應尺寸分別是xs、sm、md、lg 和 xl。使用程式碼如下:
<el-row :gutter="10"> <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1"><div class="grid-content bg-purple"></div></el-col> <el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11"><div class="grid-content bg-purple-light"></div></el-col> <el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11"><div class="grid-content bg-purple"></div></el-col> <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1"><div class="grid-content bg-purple-light"></div></el-col> </el-row>
說明:xs:<768px 響應式柵格數或者柵格屬性物件,sm:≥768px 響應式柵格數或者柵格屬性物件,md:≥992px 響應式柵格數或者柵格屬性物件,lg:≥1200px 響應式柵格數或者柵格屬性物件,xl:≥1920px 響應式柵格數或者柵格屬性物件.
背後的邏輯就是不同螢幕尺寸所展示的格子數是不一樣的,而且是根據螢幕寬度進行響應式。首先,我們看是如何進行不同的class繫結的:
['xs','sm','md','lg','xl'].forEach(size => { if (typeof this[size] === 'number') { classList.push(`el-col-${size}-${this[size]}`); } else if (typeof this[size] === 'object') { let props = this[size]; Object.keys(props).forEach(prop => { classList.push( prop !== 'span' ? `el-col-${size}-${prop}-${props[prop]}` : `el-col-${size}-${props[prop]}` ); }); } });
這裡面xs等屬性也是可以使用物件。所以會有個處理物件的邏輯;以上的處理的邏輯比較簡單,我們再看一下css是怎麼處理這個媒體查詢的邏輯的。
在分析css的時候,我們先了解一個概念,那就是sass中的內建方法map-get。map-get($map,$key)函式的作用就是可以通過$key取到對應的value值,可以理解為就是一個對映關係。如果不存在則不會編譯對應的css。舉個