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中通过render函数给子组件设置ref操作
Nov 17 Vue.js
在Vue中使用CSS3实现内容无缝滚动的示例代码
Nov 27 Vue.js
详解Vue中的自定义指令
Dec 07 Vue.js
Vue 简单实现前端权限控制的示例
Dec 25 Vue.js
vue 页面跳转的实现方式
Jan 12 Vue.js
Vue仿Bibibili首页的问题
Jan 21 Vue.js
详解vue3中组件的非兼容变更
Mar 03 Vue.js
详解Vue的sync修饰符
May 15 Vue.js
如何理解Vue简单状态管理之store模式
May 15 Vue.js
vue-cli4.5.x快速搭建项目
May 30 Vue.js
Vue中使用import进行路由懒加载的原理分析
Apr 01 Vue.js
vue-cli3.0修改打包后的文件名和文件地址,打包后本地运行报错解决
Apr 06 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
PR值查询 | PageRank 查询
2006/12/20 PHP
PHP远程连接MYSQL数据库非常慢的解决方法
2008/07/05 PHP
PHP Socket 编程
2010/04/09 PHP
PhpMyAdmin出现export.php Missing parameter: what /export_type错误解决方法
2012/08/09 PHP
PHP获取栏目的所有子级和孙级栏目的ID号示例
2014/04/01 PHP
PHP判断网络文件是否存在的方法
2015/03/12 PHP
php实现转换html格式为文本格式的方法
2016/05/16 PHP
php实现的debug log日志操作类实例
2016/07/12 PHP
使用js判断TextBox控件值改变然后出发事件
2014/03/07 Javascript
JavaScript中的异常捕捉介绍
2014/12/31 Javascript
详解Javacript和AngularJS中的Promises
2016/02/09 Javascript
Bootstrap免费字体和图标网站(值得收藏)
2017/03/16 Javascript
给Easyui-Datebox设置隐藏或者不可用的解决方法
2017/05/26 Javascript
自定义类似于jQuery UI Selectable 的Vue指令v-selectable
2017/08/23 jQuery
vue实现可视化可拖放的自定义表单的示例代码
2019/03/20 Javascript
微信小程序mpvue点击按钮获取button值的方法
2019/05/29 Javascript
一个手写的vue放大镜效果
2019/08/09 Javascript
解决layer.confirm选择完之后消息框不消失的问题
2019/09/16 Javascript
Vue实现开心消消乐游戏算法
2019/10/22 Javascript
vue请求服务器数据后绑定不上的解决方法
2019/10/30 Javascript
详解vite2.0配置学习(typescript版本)
2021/02/25 Javascript
用Django实现一个可运行的区块链应用
2018/03/08 Python
Python创建一个空的dataframe,并循环赋值的方法
2018/11/08 Python
python远程连接MySQL数据库
2019/04/19 Python
django 读取图片到页面实例
2020/03/27 Python
keras的backend 设置 tensorflow,theano操作
2020/06/30 Python
django template实现定义临时变量,自定义赋值、自增实例
2020/07/12 Python
pytorch 实现L2和L1正则化regularization的操作
2021/03/03 Python
英国最大的笔记本电脑直销专家:Laptops Direct
2019/07/20 全球购物
认识深刻的检讨书
2014/02/16 职场文书
人民教师的自我评价分享
2014/02/21 职场文书
放飞梦想演讲稿200字
2014/08/26 职场文书
期末复习计划
2015/01/19 职场文书
电力企业职工培训心得体会
2016/01/11 职场文书
Go timer如何调度
2021/06/09 Golang
Spring Security使用单点登录的权限功能
2022/04/03 Java/Android