Element-ui Layout布局(Row和Col组件)的实现


Posted in Vue.js onDecember 06, 2021

我们在实际开发中遇到一些布局的时候会用到Layout布局,这个布局只要配置一些参数就能够达到很好的布局效果甚至可以响应式,那里面的具体是怎么实现的呢,让我们去剖开Element-UI的源码,学习里面的一些细节吧。

基本说明以及用法

Element-UI的Layout布局是通过基础的24分栏,迅速简便地创建布局。根据不同的组合,很快的就能够生成一个很美观的响应式布局。具体的用法如下:

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

由上述的示例代码可以看出Row组件主要是创建每行分栏的布局方式,比如之间的一些间隔、对齐方式等。而Col则创建每个分栏,分栏的长度、偏移量等。我们可以进行自由组合每个分栏,从而达到一种响应式效果。

Row组件的分析

render函数

我们都知道vue除了可以使用template模板编写组件外,有时候我们还可以直接使用render函数去编写一个组件。因为template模板最终也是编译成了render函数。
为什么会有render函数的写法?比如现在有个需求:根据动态的level生成从h1-h6字体大小的标题的时候,我们如果使用template去实现的话我们页面中可能会出现很多类似这样的伪代码:

<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>

但是如果是使用render函数则是比较简单:

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子节点数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

这里还有一个代码优化点是。this.$slots.default存储的就是插槽内容,不需要写那么多遍。

源码分析

Row组件的源码比较简单,因为我们可以通过tag这个prop对其指定一个渲染标签,所以组件是直接使用render渲染函数进行编写。 render函数部分如下:

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);
  }

如上的源码可以得出Row主要是控制class名来进行控制内容布局的。这里有gutter属性能够控制行内列的间隔数。如果说我们设置为gutter=20,那么每个列项都进行左右间距10px,那么就会出现个问题:第一个列项跟最后一个列项会出现左右的间距。那该如何让第一个跟最后一个左右间隔去掉这个10px呢?Row的处理方案是这个行左右各偏-10px,所以用了一个计算属性来设置样式:

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

Col组件的分析

组件分析

Col主要是为了设置每一列的长度以及偏移量。主要的属性是span、offset;同样这个组件也是采用render函数去编写,首先我们看如何通过span、offset去控制列的,源码如下:

render(h) {
    let classList = [];
    let style = {};
    ...

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

    ...

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

从这可以看出,col的列宽是通过不同class名去做控制的。我们找到对应的.scss文件,发现他使用了sass@for循环语句去计算不同格子的宽度:

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

同理offset也是使用相同的逻辑。这样我们就可以根据不同的span、跟offset混合组合不同风布局了,是不是感觉背后的逻辑是如此的简单呢。我们再思考一个问题就是如果我们要控制一组相同的列宽间隔,需要一个个的去做设置么?答案是不用的,我们可以借助上述的Row组件中的gutter属性去做统一设置。那怎么实现的呢?源码如下:

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

我们通过往上遍历父组件,如果父组件的组件名为ElRow,则取到gutter值,然后让组件左右内边距设置对应的值就好了:

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

这样我们就解决了统一列宽设置的问题;

响应式布局

这里我们用到了css3中的媒体查询来进行响应式布局,相应尺寸分别是xs、sm、md、lg 和 xl。使用代码如下:

<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>

说明:xs:<768px 响应式栅格数或者栅格属性对象,sm:≥768px 响应式栅格数或者栅格属性对象,md:≥992px 响应式栅格数或者栅格属性对象,lg:≥1200px 响应式栅格数或者栅格属性对象,xl:≥1920px 响应式栅格数或者栅格属性对象.

背后的逻辑就是不同屏幕尺寸所展示的格子数是不一样的,而且是根据屏幕宽度进行响应式。首先,我们看是如何进行不同的class绑定的:

['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等属性也是可以使用对象。所以会有个处理对象的逻辑;以上的js处理的逻辑比较简单,我们再看一下css是怎么处理这个媒体查询的逻辑的。
在分析css的时候,我们先了解一个概念,那就是sass中的内置方法map-get。map-get($map,$key)函数的作用就是可以通过$key取到对应的value值,可以理解为就是一个映射关系。如果不存在则不会编译对应的css。举个?:

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

第二个是sass内置方法inspect(value),这个方法就是一个返回一个字符串的表示形式,value是一个sass表达式。举个?:

$--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;
  }
}
// 编译后的css

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

好了,我相信聪明的你已经很好的掌握了这两个方法,那我们去看一下element是怎么去实现的吧。
其实上述的第二个例子已经道出一二,我们看一下源码:

$--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) {
  // 循环断点Map,如果存在则返回
  @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) {
  ...
}

这样我们就会在不同的屏幕尺寸下进行不同的长度以及间隔的展示了,这样去写我们的媒体查询是不是很棒呢?

到此这篇关于Element-ui Layout布局(Row和Col组件)的实现的文章就介绍到这了,更多相关Element Layout布局内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Vue.js 相关文章推荐
Vue解决移动端弹窗滚动穿透问题
Dec 15 Vue.js
vue 使用 sortable 实现 el-table 拖拽排序功能
Dec 26 Vue.js
详解template标签用法(含vue中的用法总结)
Jan 12 Vue.js
vue+element table表格实现动态列筛选的示例代码
Jan 14 Vue.js
vue-cli 3如何使用vue-bootstrap-datetimepicker日期插件
Feb 20 Vue.js
Vue项目中如何封装axios(统一管理http请求)
May 02 Vue.js
SSM VUE Axios详解
Oct 05 Vue.js
在vue中import()语法不能传入变量的问题及解决
Apr 01 Vue.js
vue中使用mockjs配置和使用方式
Apr 06 Vue.js
vue自定义右键菜单之全局实现
Apr 09 Vue.js
vue组件冲突之引用另一个组件出现组件不显示的问题
Apr 13 Vue.js
详解gantt甘特图可拖拽、编辑(vue、react都可用 highcharts)
Nov 27 #Vue.js
Vue实现跑马灯样式文字横向滚动
Nov 23 #Vue.js
详解Vue的列表渲染
Nov 20 #Vue.js
详解Vue slot插槽
Nov 20 #Vue.js
详解Vue router路由
Nov 20 #Vue.js
vue中 this.$set的使用详解
如何用vue实现网页截图你知道吗
You might like
全国FM电台频率大全 - 2 天津市
2020/03/11 无线电
Google Voice 短信发送接口PHP开源版(2010.5更新)
2010/07/22 PHP
PHP header()函数常用方法总结
2014/04/11 PHP
php使用json_decode后数字对象转换成了科学计数法的解决方法
2017/02/20 PHP
PHP7 windows支持
2021/03/09 PHP
javascript 尚未实现错误解决办法
2008/11/27 Javascript
js判断是否为数组的函数: isArray()
2011/10/30 Javascript
关于jquery性能最佳实践的讨论,与求教
2012/03/30 Javascript
js中哈希表的几种用法总结
2014/01/28 Javascript
JS中类或对象的定义说明
2014/03/10 Javascript
js实现数字每三位加逗号的方法
2015/02/05 Javascript
jquery自适应布局的简单实例
2016/05/28 Javascript
xmlplus组件设计系列之网格(DataGrid)(10)
2017/05/05 Javascript
JS运动特效之链式运动分析
2018/01/24 Javascript
使用vue-cli脚手架工具搭建vue-webpack项目
2019/01/14 Javascript
js实现网页版贪吃蛇游戏
2020/02/22 Javascript
JavaScript实现指定数量的并发限制的示例代码
2020/03/10 Javascript
vue实现简单跑马灯效果
2020/05/25 Javascript
详解JSON.stringify()的5个秘密特性
2020/05/26 Javascript
JS实现炫酷轮播图
2020/11/15 Javascript
vue 根据选择的月份动态展示日期对应的星期几
2021/02/06 Vue.js
python实现带声音的摩斯码翻译实现方法
2015/05/20 Python
Django自定义插件实现网站登录验证码功能
2017/04/19 Python
python实现发送邮件功能
2017/07/22 Python
pyqt5中QThread在使用时出现重复emit的实例
2019/06/21 Python
Django多数据库配置及逆向生成model教程
2020/03/28 Python
jupyter修改文件名方式(TensorFlow)
2020/04/21 Python
加拿大在线隐形眼镜和眼镜店:VisionPros
2019/10/06 全球购物
Moss Bros官网:英国排名第一的西装店
2020/02/26 全球购物
网上快餐厅创业计划书
2014/02/01 职场文书
婚礼司仪主持词
2014/03/14 职场文书
房屋转让协议书
2014/04/11 职场文书
张思德观后感
2015/06/09 职场文书
导游词之峨眉山
2019/12/16 职场文书
MySQL高速缓存启动方法及参数详解(query_cache_size)
2021/07/01 MySQL
Redis全局ID生成器的实现
2022/06/05 Redis