Element UI 组件源码分析之Layout拾遗

1,045 阅读2分钟

0x00 简介

前文 📚 Layout (栅格化)布局系统组件源码剖析 详细讲解了组件的代码生成逻辑,本文在其基础上继续深入分析, 耐心读完,相信会对您有所帮助。

更多组件分析详见 👉 📚 Element UI 源码剖析组件总览

本专栏的 gitbook 版本地址已经发布 anduril.gitbook.io/learning-el… ,内容同步更新中!

0x01 Column Gutter 实现原理

通过 rowcol 组件,并通过 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>

页面渲染后效果如下👇:

image.png

列与列的间隔距离等于属性值 gutter,首列左侧 和 尾列的右侧间隔值为 gutter/2。布局如下👇:

image.png

组件col中的计算属性 gutter获取父组件 rowgutter 值,并在 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。效果如下👇:

image.png

其它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 。实现效果如下👇:

image.png

若组件 row 的计算属性 style 通过为组件设置负值 margin,绝对值为 gutter/2。嵌套中的 row宽度会增加gutter,抵消掉为 col 组件设置的 padding,相当于此列没有设置 padding 值。实现效果如下👇:

image.png

基于此逻辑,不管进行多少层级的嵌套,都能保证列与列之间的间隔一致。代码实际渲染效果如下👇:

image.png


0x02 Row 组件样式

组件样式源码 packages\theme-chalk\src\row.scss 使用混合指令 bmutils-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循环生成 024 对应样式:

  • 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) { /*...*/ }

📚参考&关联阅读

"媒体查询",MDN

关注专栏

如果本文对您有所帮助请关注➕、 点赞👍、 收藏⭐!您的认可就是对我的最大支持!

此文章已收录到专栏中 👇,可以直接关注