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 相关文章推荐
快速解决vue2+vue-cli3项目ie兼容的问题
Nov 17 Vue.js
vue实现下载文件流完整前后端代码
Nov 17 Vue.js
Vue +WebSocket + WaveSurferJS 实现H5聊天对话交互的实例
Nov 18 Vue.js
element-plus一个vue3.xUI框架(element-ui的3.x 版初体验)
Dec 02 Vue.js
vue祖孙组件之间的数据传递案例
Dec 07 Vue.js
在vue项目中封装echarts的步骤
Dec 25 Vue.js
VUE实现吸底按钮
Mar 04 Vue.js
vue中data改变后让视图同步更新的方法
Mar 29 Vue.js
Vue过滤器(filter)实现及应用场景详解
Jun 15 Vue.js
vue实现锚点定位功能
Jun 29 Vue.js
vue 自定义组件添加原生事件
Apr 21 Vue.js
vue二维数组循环嵌套方式 循环数组、循环嵌套数组
Apr 24 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
php面向对象 字段的声明与使用
2012/06/14 PHP
解析PHP缓存函数的使用说明
2013/05/10 PHP
PHP垃圾回收机制引用计数器概念分析
2013/06/24 PHP
php使用get_class_methods()函数获取分类的方法
2016/07/20 PHP
php基于闭包实现函数的自调用(递归)实例分析
2016/11/11 PHP
Aliyun Linux 编译安装 php7.3 tengine2.3.2 mysql8.0 redis5的过程详解
2020/10/20 PHP
写的htc的数据表格
2007/01/20 Javascript
js监听表单value的修改同步问题,跨浏览器支持
2009/12/31 Javascript
分享XmlHttpRequest调用Webservice的一点心得
2012/07/20 Javascript
js正则表达exec与match的区别说明
2014/01/29 Javascript
在JavaScript中使用timer示例
2014/05/08 Javascript
在HTML中插入JavaScript代码的示例
2015/06/03 Javascript
javascript中Date()函数在各浏览器中的显示效果
2015/06/18 Javascript
全面解析DOM操作和jQuery实现选项移动操作代码分享
2016/06/07 Javascript
jQuery实现的简单对话框拖动功能示例
2018/06/05 jQuery
angularjs 的数据绑定实现原理
2018/07/02 Javascript
Vue条件循环判断+计算属性+绑定样式v-bind的实例
2018/09/18 Javascript
探秘vue-rx 2.0(推荐)
2018/09/21 Javascript
Vue实现一个无限加载列表功能
2018/11/13 Javascript
微信小程序自定义toast的实现代码
2018/11/16 Javascript
js中innerText/textContent和innerHTML与target和currentTarget的区别
2019/01/21 Javascript
vue实现多组关键词对应高亮显示功能
2019/07/25 Javascript
使用p5.js临摹动态图形
2019/10/23 Javascript
基于VSCode调试网页JavaScript代码过程详解
2020/07/20 Javascript
详解uniapp的全局变量实现方式
2021/01/11 Javascript
python 测试实现方法
2008/12/24 Python
Python线程创建和终止实例代码
2018/01/20 Python
pycharm运行scrapy过程图解
2019/11/22 Python
html5中监听canvas内部元素点击事件的三种方法
2019/04/28 HTML / CSS
公司董事长职责
2013/12/12 职场文书
组工干部演讲稿
2014/09/02 职场文书
2015年计划生育协会工作总结
2015/05/13 职场文书
怎样写好工作计划
2019/04/10 职场文书
Promise面试题详解之控制并发
2021/05/14 面试题
实体类或对象序列化时,忽略为空属性的操作
2021/06/30 Java/Android
Java实现贪吃蛇游戏的示例代码
2022/09/23 Java/Android