ElementUI源码学习 - Layout布局(row 、col)

  • row.js该组件不同于 button组件,是一个js文件,使用的是render方法
export default {
  name: 'ElRow', //组件名字,作用在 button 组件中,已经说明了
  componentName: 'ElRow', //这个在 col 组件中判断 是否为 col 的父组件,从而给 gutter 间隔
  props: {
    tag: { //自定义元素标签, tag 在 render 函数中定义元素标签,
        //题外话 在vue中使用 is 也是可以改变元素标签的 ps: <p is="span"></p> 在控制台是span标签
        //场景:C端 SEO,要提供语义化的 HTML 标签,这个时候可以使用 tag
      type: String,
      default: 'div'
    },
    gutter: Number, //间隔大小
    type: String, // 布局模式,可选 flex,
    justify: { // flex 布局下的水平排列方式
      type: String,
      default: 'start'
    },
    align: { //flex 布局下的垂直排列方式
      type: String,
      default: 'top'
    }
  },

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

  render(h) {  //render 函数
    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' } //是否为 flex 布局
      ],
      style: this.style //object 样式 由计算属性得出
    }, this.$slots.default); //插入默认插槽
  }
};

[注]

1、计算属性中的 style==》ret.marginLeft = -${this.gutter / 2}px;是为负值的 margin,这样处理是为了 处理 第一个和最后一个colpadding值,因为padding是不可以为负值的,所以采用row的外边距为负值来抵消 第一和最后一个 colpadding

2、可能 element 对于col的定位是作为布局,不然要是在col设置背景色,padding也是会显示其背景色的,解决方案就是在col之下增加元素,将其背景色设置在子元素上面,比如element官方例子

3、render函数传送门

4、布局实现 flexbox,样式如下

@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;
    }
}
  • col.js
export default {
  name: 'ElCol',
  props: {
    span: {
      type: Number,
      default: 24
    },
    tag: {
      type: String,
      default: 'div'
    },
    offset: Number,
    pull: Number,
    push: Number,
    xs: [Number, Object], // 响应式栅格数或者栅格属性对象
    sm: [Number, Object], // (例如: {span: 4, offset: 4})	 同下
    md: [Number, Object],
    lg: [Number, Object],
    xl: [Number, Object]
  },

  computed: {
    gutter() {
      let parent = this.$parent;
      while (parent && parent.$options.componentName !== 'ElRow') {
          /*parent.$options.componentName 也就是 row 中的 componentName 
            迭代:直到找到父元素是 Row 并且获取 Row 的 gutter 属性
          */
        parent = parent.$parent;
      }
      return parent ? parent.gutter : 0;
    }
  },
  render(h) {
    let classList = [];
    let style = {};

    if (this.gutter) { //由计算属性而来,也是通过 row 的 gutter 的值而来 
      style.paddingLeft = this.gutter / 2 + 'px';
      style.paddingRight = style.paddingLeft;
        /*采用padding达到间隔效果*/
    }

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

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

    return h(this.tag, {
      class: ['el-col', classList],
      style
    }, this.$slots.default);
  }
};

【注】

1、['span', 'offset', 'pull', 'push']放在一起都是为了控制其css,如下为其css实现

/*所有的 col 都设置 float: left;来实现了 el-row 从左到右的顺序排列,并且设置了 `box-sizing: border-box` ,这样方便对于宽度进行较为精确的控制,不会受到 padding 和 border 的宽度影响。
*/
[class*="el-col-"] {
  float: left;
  box-sizing: border-box;
}
/*
span 值的实现,当等于 0 的时候,实现方案是 display: none。当不等于 0 的时候,通过 width 设置的百分比。
*/
.el-col-0 {
  display: none;
}
/*在 Sass 中,可以使用 @for 循环来完成
	@for $i from <start> through <end>
	@for $i from <start> to <end>
		$i 表示变量
		start 表示起始值
		end 表示结束值
	这两个的区别是关键字 through 表示包括 end 这个数,而 to 则不包括 end 这个数。
*/
@for $i from 0 through 24 {
  .el-col-#{$i} {
    width: (1 / 24 * $i * 100) * 1%;
  }
/*offset 是通过 margin 来实现的偏移量。*/
  .el-col-offset-#{$i} {
    margin-left: (1 / 24 * $i * 100) * 1%;
  }
/* pull 、push 都是设置的相对定位  设置 right 、left 达到移动的目的*/
  .el-col-pull-#{$i} {
    position: relative;
    right: (1 / 24 * $i * 100) * 1%;
  }

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

响应式布局

参照了 Bootstrap的 响应式设计,预设了五个响应尺寸:xssmmdlgxl。以及分成了 24 小块, Bootstrap 原先是12块,element 增加至24,因为 24 可以组合多种不同布局组合,比如 1 2 3 4 6 8 12 这些组合,能最大限度的满足布局需求

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

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

[注]

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), /*只有  xs 是 max-width */
  '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;
/*
!default表示默认值。给一个未通过!default声明赋值的变量赋值,此时,如果变量已经被赋值,不会再被重新赋值;但是如果变量还没有被赋值,则会被赋予新的值。
*/

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