Element源码系列——Row以及Col组件

Element源码系列——Row以及Col组件

序言

官网中对Layout 布局的定义是: 通过基础的 24 分栏,迅速简便地创建布局.

Element中的栅格系统也与Bootstrap类似,组件开发的目的都是为了解决基本布局定位的问题.

Layout主要组件为Row与Col,其中Row为行布局容器,Col为列布局容器.

话不多说,先来看看Row的部分.

Row

Row是行布局容器,从功能上来说它的作用是控制内部元素的排列方式.我们边看代码边说.

name: 'ElRow',

componentName: 'ElRow',

// Row组件中的props
props: {
    tag: {
      type: String,    // 通过传入不同的tag来让row生成不同的标签
      default: 'div'
    },
    gutter: Number,    // gutter翻译是排水沟,非常贴切,它的作用是控制Row中元素的间隔
    type: String,      // type主要是控制是否以flex进行布局
    justify: {         // flex元素中水平排列规则
      type: String,
      default: 'start'
    },
    align: {
      type: String,    // flex元素中垂直排列规则
      default: 'top'
    }
}

将props与CSS3中的flex布局相结合,Row的用法就非常清晰了

参数说明类型可选值默认值
gutter栅格间隔number0
type布局模式,可选 flex,现代浏览器下有效string
justifyflex 布局下的水平排列方式stringstart/end/center/space-around/space-betweenstart
alignflex 布局下的垂直排列方式stringtop/middle/bottomtop
tag自定义元素标签string*div

Row的代码很精简,但是有两个细节咱们需要处理.

1.通过gutter设置元素间隔时,由于设置子元素的padding-left与padding-right来控制间隔,导致首尾也会有间隔.

通过控制Row的margin-left和margin-right为负值可以解决.

computed: {
    style() {
      const ret = {};

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

2.因为需要自定义渲染HTML标签,所以常规的template写法不可用了,需要使用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' } // flex布局样式
      ],
      style: this.style
    }, this.$slots.default);
}

到此Row的代码就结束了,一个组件的使用体验完全取决于程序猿的细节把控,千万不要忽视细节!

Col

Col要比Row稍微复杂一些,我们先从简单的一步一步来看.

同样的,先看下props中有哪些属性:

name: 'ElCol',

component: 'ElCol',

props: {
    span: {                 // 控制col在父元素中占的比例
        type: Number,
        default: 24
    },
    tag: {
        type: String,       // 自定义渲染的标签
        default: 'div'
    },
    offset: Number,         // 栅格左侧的间隔格数
    pull: Number,           // 栅格向右移动格数
    push: Number,           // 栅格向左移动格数
    xs: [Number, Object],   // <768px 响应式栅格数或者栅格属性对象
    sm: [Number, Object],   // ≥768px 响应式栅格数或者栅格属性对象
    md: [Number, Object],   // ≥992px 响应式栅格数或者栅格属性对象
    lg: [Number, Object],   // ≥1200px 响应式栅格数或者栅格属性对象
    xl: [Number, Object]    // ≥1920px 响应式栅格数或者栅格属性对象
}

从props可以看出主要分为元素基本大小与定位控制,响应式控制功能点,而响应式控制是基于第一个功能点为基础.

所以我们先看看span、offset、pull、push是如何被使用的.

// col.js文件
render(h) {
    let classList = [];
    // 这里逻辑是通过比对props对象,生成对应的CSS规则
    ['span', 'offset', 'pull', 'push'].forEach(prop => {
          if (this[prop] || this[prop] === 0) {
            classList.push(
              prop !== 'span'
                ? `el-col-${prop}-${this[prop]}` // e.g el-col-offset-6
                : `el-col-${this[prop]}`         // e.g el-col-5
            );
          }
    });

    ....
}
// 具体对应样式文件col.scss
// scss可以通过@for实现循环添加样式
// 栅格化具体计算公式 (1 / 24 * $i * 100) * 1%
@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%;
  }
}

同样的,响应式的原理相差不多,差别在于响应式支持传入一个对象.

let classList = [];

['xs', 'sm', 'md', 'lg', 'xl'].forEach(size => {
      // 这里分为传入对象以及数字两种情况
      if (typeof this[size] === 'number') {
        classList.push(`el-col-${size}-${this[size]}`); // e.g el-col-xs-4
      } 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]}` // e.g el-col-xs-offset-4
              : `el-col-${size}-${props[prop]}`         // e.g el-col-xs-4
          );
        });
      }
});

如何优雅的使用scss写出响应式:

// var.scss

// 定义断点值的变量
$--sm: 768px !default;
$--md: 992px !default;
$--lg: 1200px !default;
$--xl: 1920px !default;

// 定义断点对象
$--breakpoints: (
  'xs' : (max-width: $--sm),
  'sm' : (min-width: $--sm),
  'md' : (min-width: $--md),
  'lg' : (min-width: $--lg),
  'xl' : (min-width: $--xl)
);

// 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 "Undefined points: `#{$map}`";
  }
}

// col.scss
@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%;
    }
  }
}

最后,有个小细节处理下.

Row中的gutter是负责每个元素的间隔,它是统一的数值.并且需要传入到Col中使用,用来生成子元素的pading-left与padding-right.如果让用户传入的话本身不太合理,并且也增加组件使用的复杂性.

// 反向递归查找第一个名为ElRow的父组件
computed: {
    gutter() {
      let parent = this.$parent;
      while (parent && parent.$options.componentName !== 'ElRow') {
        parent = parent.$parent;
      }
      return parent ? parent.gutter : 0;
    }
},
render(h) {
    let style = {};
    // 通过父组件的gutter生成自生的padding-left与padding-right
    if (this.gutter) {
        style.paddingLeft = this.gutter / 2 + 'px';
        style.paddingRight = style.paddingLeft;
    }

    return h(this.tag, { // render返回渲染
      class: ['el-col', classList],
      style
    }, this.$slots.default);
}
总结

Row和Col就结束了,组件还是比较简单的,但是其中的细节处理与思想才是重点,希望可以帮到你!

感谢您的阅读!

再次感谢element团队的贡献!

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,让我们来一步步实现这个功能。 首先,我们需要安装 Element UI,可以通过 npm 安装: ``` npm install element-ui --save ``` 然后在 main.js 中引入并使用: ```js import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI) ``` 接下来,我们来实现时间线组件。可以在组件中使用 el-timeline 和 el-timeline-item 组件,代码如下: ```html <template> <el-timeline> <el-timeline-item v-for="(item,index) in list" :key="index" :timestamp="item.time">{{ item.content }}</el-timeline-item> </el-timeline> </template> ``` 其中,list 是传入组件的数据,包含每个时间点的内容和时间信息。 接着,我们来实现自动滚动功能。可以通过监听 el-timeline 的 scroll 事件,将 el-timeline 的 scrollTop 设置为最大值,让其自动滚动到底部。代码如下: ```html <template> <el-timeline ref="timeline" @scroll="handleScroll"> <el-timeline-item v-for="(item,index) in list" :key="index" :timestamp="item.time">{{ item.content }}</el-timeline-item> </el-timeline> </template> <script> export default { methods: { handleScroll() { const timelineEl = this.$refs.timeline.$el timelineEl.scrollTop = timelineEl.scrollHeight } } } </script> ``` 然后,我们来实现无限滚动和动态加载功能。可以使用 v-infinite-scroll 插件,通过监听 el-timeline 的滚动事件,在滚动到底部时触发加载更多数据的方法。代码如下: ```html <template> <el-timeline ref="timeline" @scroll="handleScroll" v-infinite-scroll="loadMore" infinite-scroll-disabled="loading" infinite-scroll-distance="10"> <el-timeline-item v-for="(item,index) in list" :key="index" :timestamp="item.time">{{ item.content }}</el-timeline-item> <div v-if="loading">正在加载...</div> </el-timeline> </template> <script> import { InfiniteScroll } from 'element-ui' export default { directives: { InfiniteScroll }, data() { return { list: [], // 数据列表 loading: false // 是否正在加载 } }, methods: { // 加载数据 loadMore() { if (this.loading) { return } this.loading = true // 模拟异步加载数据 setTimeout(() => { const newData = [{ time: '2021-08-01', content: '新的内容' }] this.list = this.list.concat(newData) this.loading = false }, 1000) }, // 滚动事件 handleScroll() { const timelineEl = this.$refs.timeline.$el if (timelineEl.scrollTop === 0) { this.loadMore() } } } } </script> ``` 在上面的代码中,我们使用了 element-ui 中的 InfiniteScroll 指令来实现无限滚动功能。同时,我们在 el-timeline 中添加了 infinite-scroll-disabled 和 infinite-scroll-distance 属性,分别用于控制是否禁用无限滚动和触发加载更多数据的距离。 现在我们已经完成了 element-ui 的时间线组件 + 自动滚动 + v-infinite-scroll 无限滚动 + 动态加载功能的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值