4

一天一个 Element 组件 - Row & Col

 2 years ago
source link: https://shiningdan.github.io/2020/01/12/%E4%B8%80%E5%A4%A9%E4%B8%80%E4%B8%AA-Element-%E7%BB%84%E4%BB%B6-Row-Col/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文是 Element 的组件源码学习系列。

项目源码:ElemeFE/element | GitHub,Tag:v2.13.0

Row & Col 组件使用文档:Layout 布局

.vue 文件:/packages/col

.vue 文件:/packages/row

.scss 文件:/packages/theme-chalk/col.scss

.scss 文件:/packages/theme-chalk/row.scss

.d.ts 文件:/types/col.d.ts

.d.ts 文件:/types/row.d.ts

Props

首先我们来看一下 el-row 的 Props,了解使用这个组件的入参:

{
render(h) {
return h(this.tag, {
class: [
'el-row',
this.justify !== 'start' ? `is-justify-${this.justify}` : '',
this.align !== 'top' ? `is-align-${this.align}` : '',
{ 'el-row--flex': this.type === 'flex' }
],
style: this.style
}, this.$slots.default);
},
props: {
tag: {
type: String,
default: 'div'
},
gutter: Number,
type: String,
justify: {
type: String,
default: 'start'
},
align: {
type: String,
default: 'top'
}
}
}

首先,看这个组件使用的是 render 方法,还是之前比较流行的写法,现在的模板大部分都使用 template 标签了。

tag 是用来自定义元素标签的,默认情况下使用的是 <div> 元素。

el-rowel-col 本质上是 CSS flexbox 的属性封装,所以能使用 flexbox 的标签,都可以作为 tag 属性传入,来替换 <div> 元素。常见的场景,是 C 端 SEO,要提供语义化的 HTML 标签,这个使用就可以使用 tag

gutter

gutter 用来指定每一栏之间的间隔,默认是 0。

el-row 上设置了 gutter 属性后,会影响该 row 下面所有 el-col 元素,我们来看一下怎么实现的:

首先,我们来看一下 el-col 的相关代码:

computed: {
gutter() {
let parent = this.$parent;
while (parent && parent.$options.componentName !== 'ElRow') {
parent = parent.$parent;
}
return parent ? parent.gutter : 0;
}
},
render(h) {
let style = {};

if (this.gutter) {
style.paddingLeft = this.gutter / 2 + 'px';
style.paddingRight = style.paddingLeft;
}
}

一个 el-row 组件的 gutter 属性,是通过它的父元素,或者祖先元素中最近的一个 el-row 组件决定的。首先,拿到最近的 el-row 组件,然后获得该组件的 gutter 配置。

在渲染的时候,由于 gutter 指的是两个 col 组件之间的总距离,所以映射到每一个 el-col 上,设置为 padding-leftpadding-right 分为为二分之一。

注意!!如果第一个 el-col 的左侧也有二分之一 gutter 的长度,是不符合我们的预期的。因为第一个 el-col 应该是最左边没有边距的。

所以我们需要在 el-row 上面做文章,将第一个 el-colpaddingLeft 抵消掉。由于 padding 的值,不能设置为负数,参考 padding | MDN,所以我们只能将 el-row 的外边距设置为负数,来抵消第一个 el-colpaddingLeft

computed: {
style() {
const ret = {};
if (this.gutter) {
ret.marginLeft = `-${this.gutter / 2}px`;
ret.marginRight = ret.marginLeft;
}
return ret;
}
},

我本人对于 gutterel-col 上的实现方案,有一些疑问。我觉得 gutter 的实现方案,在 el-col 上,应该使用 marginLeftmarginRight。因为如果对 el-col 设置了背景颜色的话,使用 padding 的实现方法,间隔栏也会被染色,这个是不符合预期的。

当然这个问题也很好解决。需要我们对 el-col 的定位,是负责布局控制。而业务相关的设置,比如背景颜色,不应该直接设置在 el-col 上,而是在 el-col 中再包裹 div 元素,设置在 div 元素上,则间隔栏就不会被染色了。

span & offset & pull & push

span 属性是用来控制一个 el-col 栅格的宽度的。我们来看一下实现方案:

['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]}`
);
}
});

这里把 span & offset & pull & push 放在一起写,是因为这几个属性,背后的原理都是一样的,控制 HTML 元素的 CSS 属性。下面我们来一起看一下相关的 SCSS 实现:

[class*="el-col-"] {
float: left;
box-sizing: border-box;
}

.el-col-0 {
display: none;
}

@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%;
}
}

我们来依次看下:

  1. 所有的 el-col 都设置了 float: left,来实现在一个 el-row 中从左到右的顺序排列。并且设置了 box-sizing: border-box,这样方便对于宽度进行较为精确的控制,不会受到 paddingborder 的宽度影响。
  2. span 值的实现,当等于 0 的时候,实现方案是 display: none。当不等于 0 的时候,通过 width 设置的百分比。
  3. offset 是通过 margin 来实现的偏移量。

type=flex

通过在 el-row 上设置 type=flex,可以使用 flexbox 的布局能力。我们来看一下具体的实现方案:

render(h) {
return h(this.tag, {
class: [
'el-row',
this.justify !== 'start' ? `is-justify-${this.justify}` : '',
this.align !== 'top' ? `is-align-${this.align}` : '',
{ 'el-row--flex': this.type === 'flex' }
],
style: this.style
}, this.$slots.default);
}

即,通过设置 type=flex 来开启 flexbox 布局,然后设置 justify 来配置 CSS justify-content 属性,以及 设置 align 来配置 align-items 属性。

@include m(flex) {
display: flex;
&:before,
&:after {
display: none;
}

@include when(justify-center) {
justify-content: center;
}
@include when(justify-end) {
justify-content: flex-end;
}
@include when(justify-space-between) {
justify-content: space-between;
}
@include when(justify-space-around) {
justify-content: space-around;
}

@include when(align-middle) {
align-items: center;
}
@include when(align-bottom) {
align-items: flex-end;
}
}

响应式布局

参照了 Bootstrap 的 响应式设计,预设了五个响应尺寸:xssmmdlgxl

我们来看一下怎么实现的:

['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 属性来举例。

xs:<768px 响应式栅格数或者栅格属性对象

首先,当 el-col 设置了 xs 属性后,通过上面的 JS 代码,给 el-col 添加了相关的 CSS 类。我们来看一下这个类是怎么实现的呢。

@include res(xs) {
.el-col-xs-0 {
display: none;
}
@for $i from 0 through 24 {
.el-col-xs-#{$i} {
width: (1 / 24 * $i * 100) * 1%;
}

.el-col-xs-offset-#{$i} {
margin-left: (1 / 24 * $i * 100) * 1%;
}

.el-col-xs-pull-#{$i} {
position: relative;
right: (1 / 24 * $i * 100) * 1%;
}

.el-col-xs-push-#{$i} {
position: relative;
left: (1 / 24 * $i * 100) * 1%;
}
}
}

在这个 @include 里面,好像和 span 的设置没有什么不同,都是对于 el-col 的宽度进行修改。那 xs 的设置,如何触发祥响应式相关的设置呢?关键在 res(xs)res 这个函数上。

col.scss 中,一开始引入了两个文件:

@import "./common/var.scss";
@import "./mixins/mixins.scss";

res 函数的定义就可以在这两个文件中找到:

// ./mixins/mixins.scss
@mixin res($key, $map: $--breakpoints) {
// 循环断点Map,如果存在则返回
@if map-has-key($map, $key) {
@media only screen and #{inspect(map-get($map, $key))} {
@content;
}
} @else {
@warn "Undefeined points: `#{$map}`";
}
}

// ./common/var.scss
$--breakpoints: (
'xs' : (max-width: $--sm - 1),
'sm' : (min-width: $--sm),
'md' : (min-width: $--md),
'lg' : (min-width: $--lg),
'xl' : (min-width: $--xl)
);

$--sm: 768px !default;
$--md: 992px !default;
$--lg: 1200px !default;
$--xl: 1920px !default;

当调用 res(xs) 的时候,$key 就是 xs$map 使用的是默认值 $--breakpoints,也就是 ./common/var.scss 中预先定义的响应式布局中常用的几种宽度。然后 res 函数会通过设置 @media only screen 的 CSS 属性,来实现响应式的能力。所以,el-col 的实现,本质上也是通过设置 @media 属性来实现的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK