0x00 简介
前文 📚 Layout (栅格化)布局系统组件源码剖析 详细讲解了组件的代码生成逻辑,本文在其基础上继续深入分析, 耐心读完,相信会对您有所帮助。
更多组件分析详见 👉 📚 Element UI 源码剖析组件总览 。
本专栏的 gitbook
版本地址已经发布 anduril.gitbook.io/learning-el… ,内容同步更新中!
0x01 Column Gutter 实现原理
通过 row
和 col
组件,并通过 col 组件的 span
属性可以自由地组合布局。使用 <el-col :span="12" />
来创建二个等宽的列。 使用 row
组件提供 gutter
属性来指定每一栏之间的间隔,默认单位为 px
。
以下代码创建一个包含二个等宽的列的行,间隔为24px。
<el-row :gutter="24">
<el-col :span="12"><div>col</div></el-col>
<el-col :span="12"><div>col</div></el-col>
</el-row>
页面渲染后效果如下👇:
列与列的间隔距离等于属性值 gutter
,首列左侧 和 尾列的右侧间隔值为 gutter/2
。布局如下👇:
组件col
中的计算属性 gutter
获取父组件 row
的 gutter
值,并在 render()
中基于计算属性 gutter
的值计算列的 padding-left
padding-right
,值为 gutter / 2 + 'px'
。
// packages\col\src\col.js
computed: {
// 获取 el-row 的gutter值
gutter() {
// 父实例 根据 compontName 属性 判断是组件 el-row
let parent = this.$parent;
while (parent && parent.$options.componentName !== 'ElRow') {
parent = parent.$parent;
}
return parent ? parent.gutter : 0;
}
},
render(h) {
let classList = [];
let style = {};
// 通过gutter计算自己的左右2个padding,用于分隔col
if (this.gutter) {
style.paddingLeft = this.gutter / 2 + 'px';
style.paddingRight = style.paddingLeft;
}
// class 计算
return h(this.tag, {
class: ['el-col', classList],
style
}, this.$slots.default);
}
列与列的间隔距离等于属性值 gutter
等于 左列的 padding-right
值 加上 右列的 padding-left
值;首列左侧间隔值为 padding-left
; 尾列的右侧间隔值为 padding-right
。效果如下👇:
其它3等列、4等列、……、24等列依次类推,感觉是不是很好理解!
那组件row
的计算属性 style
设置 margin-left
margin-right
负值用意何在?
// packages\row\src\row.js
computed: {
style() {
const ret = {};
if (this.gutter) {
ret.marginLeft = `-${this.gutter / 2}px`;
ret.marginRight = ret.marginLeft;
}
return ret;
}
},
前文中提到,组件
row
的计算属性style
通过为组件设置负值margin
从而抵消掉为col
组件设置的padding
,也就间接为“行(row)”所包含的“列(column)”抵消掉了padding
。
接下来通过示例、图解的方式对其进行阐释。
在之前的示例中第一行第一列中插入一行(该行中包含两等宽列),代码如下 👇:
<!-- row1 -->
<el-row :gutter="24">
<!-- col1 -->
<el-col :span="12">
<el-row :gutter="24">
<el-col :span="12"><div>col</div></el-col>
<el-col :span="12"><div>col</div></el-col>
</el-row>
</el-col>
<!-- col2 -->
<el-col :span="12"><div>col</div></el-col>
</el-row>
假设组件 row
没有设置负值, margin
值为 0 。
此时第一行第一列中嵌套的两列的间隔比默认的设置多出来一个 padding
值(gutter/2
)。中间两列的间隔就 gutter/2 * 3
。多嵌套一层,间隔就会增大 gutter/2
。实现效果如下👇:
若组件 row
的计算属性 style
通过为组件设置负值 margin
,绝对值为 gutter/2
。嵌套中的 row
宽度会增加gutter
,抵消掉为 col
组件设置的 padding
,相当于此列没有设置 padding
值。实现效果如下👇:
基于此逻辑,不管进行多少层级的嵌套,都能保证列与列之间的间隔一致。代码实际渲染效果如下👇:
0x02 Row 组件样式
组件样式源码 packages\theme-chalk\src\row.scss
使用混合指令 b
、m
、utils-clearfix
嵌套生成组件样式。
// 生成 .el-row
@include b(row) {
position: relative;
box-sizing: border-box;
// 使用清除浮动指令 生成 .el-row::after, .el-row::before
@include utils-clearfix;
// flex布局 生成 .el-row--flex
@include m(flex) {
display: flex;
// 生成 .el-row--flex:after, .el-row--flex:before
&:before,
&:after {
display: none;
}
// 对齐方式
// 生成 .el-row--flex.is-justify-center
@include when(justify-center) {
justify-content: center;
}
// 生成 // 生成 .el-row--flex.is-justify-end
@include when(justify-end) {
justify-content: flex-end;
}
// 生成 .el-row--flex.is-justify-space-between
@include when(justify-space-between) {
justify-content: space-between;
}
// 生成 .el-row--flex.is-justify-space-around
@include when(justify-space-around) {
justify-content: space-around;
}
// 生成 .el-row--flex.is-align-middle
@include when(align-middle) {
align-items: center;
}
// 生成 .el-row--flex.is-align-bottom
@include when(align-bottom) {
align-items: flex-end;
}
}
}
使用 gulpfile.js
编译 scss
文件转换为CSS
,经过浏览器兼容、格式压缩,最后生成 packages\theme-chalk\lib\row.css
,内容格式如下。
.el-row { /*...*/ }
/*...clearfix...*/
.el-row::after, .el-row::before { /*...*/ }
.el-row::after { /*...*/ }
/*...flex...*/
.el-row--flex { /*...*/ }
.el-row--flex:after, .el-row--flex:before { /*...*/ }
/*...justify-content...*/
.el-row--flex.is-justify-center { /*...*/ }
.el-row--flex.is-justify-end { /*...*/ }
.el-row--flex.is-justify-space-between { /*...*/ }
.el-row--flex.is-justify-space-around { /*...*/ }
/*...align-items...*/
.el-row--flex.is-align-middle { /*...*/ }
.el-row--flex.is-align-bottom { /*...*/ }
0x03 Col 组件样式
组件样式源码 packages\theme-chalk\src\col.scss
生成组件样式实现分栏、间隔、偏移、响应式功能。
分栏、间隔、偏移
使用@for
循环生成 0
至 24
对应样式:
span
栅格占据的列数,通过 width 来实现 ,对应样式.el-col-[n]
。offset
栅格左侧的间隔格数,通过 margin-left 实现 ,对应样式.el-col-offset-[n]
。push
栅格向右移动格数,通过 left 实现 ,对应样式.el-col-push-[n]
。pull
栅格向左移动格数,通过 right 实现,对应样式.el-col-pull-[n]
。
组件栅格系统使用24分栏,所有每分栏宽度基准为 (1 / 24 * 100) * 1%
。
[class*="el-col-"] {
float: left;
// 如何计算一个元素的总宽度和总高度
box-sizing: border-box;
}
// 组件不渲染
.el-col-0 {
display: none;
}
@for $i from 0 through 24 {
// 生成 .el-col-[0-24]
.el-col-#{$i} {
width: (1 / 24 * $i * 100) * 1%;
}
// 生成 .el-col-offset-[0-24]
.el-col-offset-#{$i} {
margin-left: (1 / 24 * $i * 100) * 1%;
}
// 生成 .el-col-pull-[0-24]
.el-col-pull-#{$i} {
position: relative;
right: (1 / 24 * $i * 100) * 1%;
}
// 生成 .el-col-push-[0-24]
.el-col-push-#{$i} {
position: relative;
left: (1 / 24 * $i * 100) * 1%;
}
}
响应式布局
系统预设五个响应尺寸:xs
sm
md
lg
xl
。使用指令 res
生成媒体查询,从而实现响应式设计。
// 'xs', 'sm', 'md', 'lg', 'xl'
// 生成 @media only screen and (max-width: 767px) { ... }
@include res(xs) {
// 生成 .el-col-xs-0
.el-col-xs-0 {
display: none;
}
@for $i from 0 through 24 {
// 生成 .el-col-xs-[0-24]
.el-col-xs-#{$i} {
width: (1 / 24 * $i * 100) * 1%;
}
// 生成 .el-col-xs-offset-[0-24]
.el-col-xs-offset-#{$i} {
margin-left: (1 / 24 * $i * 100) * 1%;
}
// 生成 .el-col-xs-pull-[0-24]
.el-col-xs-pull-#{$i} {
position: relative;
right: (1 / 24 * $i * 100) * 1%;
}
// 生成 .el-col-xs-push-[0-24]
.el-col-xs-push-#{$i} {
position: relative;
left: (1 / 24 * $i * 100) * 1%;
}
}
}
// 生成 @media only screen and (min-width: 768px) { ... }
@include res(sm) { /*...*/ }
// 生成 @media only screen and (min-width: 992px) { ... }
@include res(md) { /*...*/ }
// 生成 @media only screen and (min-width: 1200px) { ... }
@include res(lg) { /*...*/ }
// 生成 @media only screen and (min-width: 1920px) { ... }
@include res(xl) { /*...*/ }
📚参考&关联阅读
关注专栏
如果本文对您有所帮助请关注➕、 点赞👍、 收藏⭐!您的认可就是对我的最大支持!
此文章已收录到专栏中 👇,可以直接关注