We will use Layout Layout in the actual development of some of the Layout, the Layout as long as the configuration of some parameters can achieve a good Layout effect or even responsive, where the specific is how to achieve it, let’s go to break open the source of element-UI, learn some of the details inside it.

Basic instructions and usage

The Element-UI Layout is a quick and easy way to create a Layout with a base of 24 columns. Depending on the combination, you can quickly produce a nice responsive layout. The specific usage is as follows:

<el-row>
  <el-col :span="24"><div class="grid-content bg-purple-dark"></div></el-col>
</el-row>
Copy the code

As you can see from the example code above, the Row component mainly creates the layout of each Row column, such as some spacing between them, alignment, and so on. Col creates each column, its length, offset, and so on. We can freely combine each column to achieve a responsive effect.

Row component analysis
  • Render function

We all know that vue can write components using the Template template, sometimes we can write components directly using the Render function. Because the template template also ends up being compiled into the render function. Why is the render function written? For example, there is a requirement to generate h1-H6 font size headings based on dynamic level. If we use template to implement this, we may have a lot of pseudo-code in the page like this:

<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>
Copy the code

But using the render function is simpler:

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // Label name
      this.$slots.default // Array of child nodes)},props: {
    level: {
      type: Number.required: true}}})Copy the code

There’s another code optimization point here. This.$slots.default stores the slot content, so you don’t need to write it so many times.

  • Source code analysis

The source code for the Row component is relatively simple because we can assign a render tag to it using the Prop tag, so the component is written directly using the Render function. The render function is as follows:

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

As you can see from the source code above, Row mainly controls the class name to control the content layout. Here you have the gutter property that controls the number of columns in the row. If we set it to 20 and each column spacing 10px in the gutter, we would have a problem: the first column would have a left-right space from the last. So how do you get the first one to be separated from the last one by 10 pixels? The Row is handled -10px to the left and right of the Row, so we use a calculated property to set the style:

computed: {
    style() {
      const ret = {};
      if (this.gutter) {
        ret.marginLeft = ` -The ${this.gutter / 2}px`;
        ret.marginRight = ret.marginLeft;
      }
      returnret; }},Copy the code
Analysis of Col components
  • Component analysis

Col is used to set the length and offset of each column. The main attributes are span and offset; Also this component is using the render function to write, first we look at how to control columns by span, offset, the source code is as follows:

render(h) {
    let classList = [];
    letstyle = {}; . ['span'.'offset'.'pull'.'push'].forEach(prop= > {
      if (this[prop] || this[prop] === 0) { classList.push( prop ! = ='span'
            ? `el-col-${prop}-The ${this[prop]}`
            : `el-col-The ${this[prop]}`); }}); .return h(this.tag, {
      class: ['el-col', classList],
      style
    }, this.$slots.default);
  }
Copy the code

From this we can see that col column width is controlled by class names. We find the corresponding.scss file and find that it uses the sass@for loop statement to calculate the width of different cells:

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

The same logic applies to offset. In this way, we can mix and combine different wind layouts according to different spans, offsets and offsets. Isn’t the logic behind the feeling so simple? Another question to consider is if we want to control a set of identical column widths, do we need to set them one by one? The answer to this question is no, we can do this with the gutter property in the Row component above. So how does that work? The source code is as follows:

 computed: {
    gutter() {
      let parent = this.$parent;
      while(parent && parent.$options.componentName ! = ='ElRow') {
        parent = parent.$parent;
      }
      return parent ? parent.gutter : 0; }}Copy the code

Let’s go to the gutter value by traversing the parent component up. If the parent component name is ElRow, then let the gutter value be set to the right and left margins of the component:

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

This solves the problem of uniform column width Settings;

  • Responsive layout

Here we use the media query in CSS3 for the responsive layout, the corresponding sizes are XS, SM, MD, LG and XL respectively. Use the following code:

<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>
Copy the code

Description: Xs :<768px responsive grid or grid attribute object, SM :≥768px responsive grid or grid attribute object, MD :≥992px responsive grid or grid attribute object, LG :≥1200px responsive grid or grid attribute object, XL :≥1920px A responsive raster number or raster property object. The logic behind this is that the number of cells displayed varies depending on screen size and is responsive to screen width. First, let’s look at how different class bindings are made:

['xs'.'sm'.'md'.'lg'.'xl'].forEach(size= > {
      if (typeof this[size] === 'number') {
        classList.push(`el-col-${size}-The ${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]}`); }); }});Copy the code

In this case, properties like XS can also use objects. So there’s a logic for dealing with objects; The above js processing logic is relatively simple, let’s take a look at CSS how to handle this media query logic. When analyzing CSS, let’s start with the concept of map-get, the built-in method in SASS. Map-get ($map,$key) is used to obtain the corresponding value by $key. If not, the CSS is not compiled. An 🌰 :

$social-colors: (
    dribble: #ea4c89,
    facebook: #3b5998,
    github: # 171515,
    google: #db4437,
    twitter: #55acee
);
.btn-dribble{
  color: map-get($social-colors,facebook);
}
/ / the compiled
.btn-dribble {
  color: #3b5998;
}
Copy the code

The second is the sASS built-in method inspect(value), which is a representation that returns a string, and value is an SASS expression. An 🌰 :

$--sm: 768px! default;$--md: 992px! default;$--lg: 1200px! default;$--xl: 1920px! default;$--breakpoints: (
  'xs' : (max-width: $--sm - 1),
  'sm' : (min-width: $--sm),
  'md' : (min-width: $--md),
  'lg' : (min-width: $--lg),
  'xl' : (min-width: $--xl));@mixin res($breakpoint) {$query:map-get($--breakpoints.$breakpoint)
  @if not $query {
    @error 'No value found for `#{$breakpoint}`. Please make sure it is defined in `$breakpoints` map.';
  }
  @media #{inspect($query)}
   {
    @content; }}.element {
  color: # 000;
 
  @include res(sm) {
    color: # 333; }}// Compiled CSS

.element {
  color: # 000;
}
@media (min-width: 768px) {
  .element {
    color: # 333; }}Copy the code

Well, I’m sure you’re smart enough to have a good grasp of both methods, so let’s take a look at what Element does. In fact, the second example above has been said, let’s look at the source code:

$--sm: 768px! default;$--md: 992px! default;$--lg: 1200px! default;$--xl: 1920px! default;$--breakpoints: (
  'xs' : (max-width: $--sm - 1),
  'sm' : (min-width: $--sm),
  'md' : (min-width: $--md),
  'lg' : (min-width: $--lg),
  'xl' : (min-width: $--xl));/* Break-points -------------------------- */
@mixin res($key.$map: $--breakpoints) {
  // Loop breakpoint Map, return if present
  @if map-has-key($map.$key) {
    @media only screen and #{inspect(map-get($map, $key))} {
      @content; }}@else {
    @warn "Undefeined points: `#{$map}`"; }}@include res(xs) {
  @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%; }}}@include res(sm) {
  ...
}
@include res(md) {
  ...
}
@include res(lg) {
  ...
}
@include res(xl) {
  ...
}
Copy the code

Wouldn’t it be great to write our media queries with different lengths and intervals for different screen sizes? Well, we’ve unveiled element’s grid layout, and look forward to more.