乐趣区

关于前端:Element-2-组件源码剖析之Steps-步骤条下核心逻辑

简介

前文步骤组件状态治理次要从生命周期的角度介绍了步骤组件实例化时组件之间的状态治理,本文将介绍步骤组件具体实现,急躁读完,置信会对您有所帮忙。

更多组件分析详见 👉 📚 Element 2 源码分析组件总览

步骤组件 step.vue

步骤条次要性能实现都在该组件中。

HTML

模板渲染成一个类名 el-step 的 div 元素,元素蕴含两局部内容

  1. 用于图标、轴线的渲染。
  2. 用于题目、形容的渲染。
// packages\steps\src\step.vue
<template>
  <div class="el-step">
    <!-- 图标 & 轴线 -->
    <div class="el-step__head" >
      <div class="el-step__line" >
        // line
      </div> 
      <div class="el-step__icon">
        // icon
      </div>
    </div>
    <!-- 题目 & 形容 -->
    <div class="el-step__main">
      <div class="el-step__title">
        // title
      </div> 
      <div class="el-step__description">
        // description
      </div>
    </div>
  </div>
</template>

下图是不同状态步骤的展现成果:

步骤元素通过内联款式和动静类名渲染不同配置下组件款式。

<div
  class="el-step"
  :style="style"
  :class="[!isSimple && `is-${$parent.direction}`,
    isSimple && 'is-simple',
    isLast && !space && !isCenter && 'is-flex',
    isCenter && !isVertical && !isSimple && 'is-center'
    ]">
  // ...
</div>

根元素自定义类名

文档中提到当设置 simple 可利用简洁格调,该条件下 align-centerdirectionspace 都将生效。

接下将一一剖析为什么设置会生效。

属性 direction 用于设置显示方向,生成类名 is-verticalis-horizontal。依据计算属性 isSimple 判断设置简洁格调时,只会生成类名 is-simple

!isSimple && `is-${$parent.direction}`,  // 生成类名 is-vertical/is-horizontal
isSimple && 'is-simple', // 生成类名 is-simple

// computed 是否简洁格调
isSimple() {return this.$parent.simple;}, 

顶层组件中也会依据属性 simpledirection 生成根元素类名 el-steps--simpleel-steps--verticalel-steps--horizontal

// packages\steps\src\steps.vue
<div
  class="el-steps"
  :class="[
      !simple && 'el-steps--' + direction,
      simple && 'el-steps--simple'
    ]">
    <slot></slot>
</div>

如果未设置间距 space和居中对齐 alignCenter,步骤条末元素会生成类名 is-flex用于自适应宽度。如果设置了简洁格调,计算属性 space 中属性 space 设置有效。

isLast && !space && !isCenter && 'is-flex', // 生成类名 is-flex

// computed
// 是否步骤条末元素
isLast() {
  const parent = this.$parent;
  return parent.steps[parent.steps.length - 1] === this;
},
// 距离设置
space() {const { isSimple, $parent: { space} } = this;
  return isSimple ? '' : space ;
},
// 题目形容是否居中对齐
isCenter() {return this.$parent.alignCenter;},

非简洁模式和竖直方向下,设置居中对齐 alignCenter才会失效,生成类名is-center

isCenter && !isVertical && !isSimple && 'is-center' // 生成类名 is-center


// computed 是否竖直方向
isVertical() {return this.$parent.direction === 'vertical';}, 

根元素内联款式

顶层组件 steps 的根元素采纳 flex 布局。

.el-steps {display: flex;}  
.el-steps--vertical {
  height: 100%; 
  flex-flow: column;
} 

计算属性 style 依据属性 space 设置flex-basis,指定了 flex 元素在主轴方向上的初始大小,也就是内容盒(content-box)的尺寸。相当于设置了步骤元素的 widthheight

当一个元素同时被设置了 flex-basis (除值为 auto 外) 和 width (或者在 flex-direction: column 状况下设置了height) , flex-basis 具备更高的优先级。

传入的属性 space值类型为 number时生成格局 {20}px。不设置依据步骤个数主动计算百分比实现自适应间距。

程度方向时,步骤末元素设置属性 max-width 值, 其余设置属性 margin-right 值(属性 stepOffset 值没有相干计算或赋值,始终为 0)。

//  computed 根元素款式
style: function() {const style = {};
  const parent = this.$parent;
  const len = parent.steps.length;

  const space = (typeof this.space === 'number'
    ? this.space + 'px' // 值为 number, 生成 {20}px
    : this.space
      ? this.space
      : 100 / (len - (this.isCenter ? 0 : 1)) + '%'); // 未指定,则自适应间距

  style.flexBasis = space;
  if (this.isVertical) return style;
  if (this.isLast) {style.maxWidth = 100 / this.stepsCount + '%';} else {style.marginRight = -this.$parent.stepOffset + 'px';}
  return style;
}

属性 space 可设置值能够参考以下内容:

/* 指定 <'width'> */
flex-basis: 10em;
flex-basis: 3px;
flex-basis: auto;

/* 固有的尺寸关键词 */
flex-basis: fill;
flex-basis: max-content;
flex-basis: min-content;
flex-basis: fit-content;

/* 在 flex item 内容上的主动尺寸 */
flex-basis: content;

/* 全局数值 */
flex-basis: inherit;
flex-basis: initial;
flex-basis: unset;

上面通过运行实例比照剖析下,各属性设置的渲染成果。

<!-- space 未设置 -->
<el-steps :active="2" finish-status="success">
  <el-step title="步骤 1" description="这是一段很长很长很长的描述性文字"></el-step>
  <el-step title="步骤 2" description="这是一段很长很长很长的描述性文字"></el-step>
  <el-step title="步骤 3" description="这段就没那么长了"></el-step>
  <el-step title="步骤 4" description="这段就没那么长了"></el-step>
</el-steps>
<!-- space 未设置  align-center 为 true 居中对齐 -->
<el-steps :active="2" finish-status="success" align-center>
  // ...
</el-steps>
<!-- space 100 即 100px -->
<el-steps :active="2" finish-status="success" :space="100">
  // ...
</el-steps>

步骤元素内容个数雷同状况下,不同设置的成果如下:

各步骤元素 DOM 构造如下(此处不思考外部内容款式区别),除了第一个示例,其余每个步骤都是等分。

那问题来了,第一个示例中 space 未设置, 组件会自适应宽度操作,那么末步骤元素产生了什么导致其宽度跟其余步骤元素不统一?

第一个示例 DOM 构造渲染如下,尽管都设置了flex-basis: 33.3333%;, 然而末元素的未失效。

<div class="el-steps el-steps--horizontal">
  <div class="el-step is-horizontal" style="flex-basis: 33.3333%; margin-right: 0px"></div>
  <div class="el-step is-horizontal" style="flex-basis: 33.3333%; margin-right: 0px"></div>
  <div class="el-step is-horizontal" style="flex-basis: 33.3333%; margin-right: 0px"></div> 
  <div class="el-step is-horizontal is-flex" style="flex-basis: 33.3333%; max-width: 25%"></div>
</div>

因为末元素增加了款式类名is-flex,笼罩了flex-basis,等同于 flex: noneflex: 0 0 auto。元素会依据本身宽高来设置尺寸。它是齐全非弹性的:既不会缩短,也不会伸长来适应 flex 容器。

.is-flex {
  flex-basis: auto !important; 
  flex-shrink: 0; 
  flex-grow: 0;
}

上文中提到非简洁模式下,只有未设置间距 space和居中对齐 alignCenter时,步骤条末元素会生成类名 is-flex。这也是第二、三示例末元素跟其余元素宽度统一起因。

isLast && !space && !isCenter && 'is-flex', // 生成类名 is-flex

竖直方向款式逻辑与程度统一(alignCenter设置有效), 此处不再赘述。

图标 & 轴线

依据计算属性 currentStatus 生成以后步骤状态对应的主题款式 is-[wait/process/finish/error/success]

<!-- 图标 & 轴线 -->
<div  class="el-step__head" :class="`is-${currentStatus}`">
  <div  class="el-step__line">
    // 轴线...
  </div> 
  <div class="el-step__icon"> 
    // 图标...
  </div>
</div>

图标

图标元素是类名 el-step__icon 的 div 元素,提供了具名 icon 插槽自定义步骤节点图标。插槽内容默认展现 Icon 图标或示意节点程序圆环数字(从 1 开始)。简洁格调下,只有 Icon 设置失效。

当组件状态值为 successerror 时,应用零碎提供图标。

<div class="el-step__icon" :class="`is-${icon ?'icon':'text'}`">
  <slot v-if="currentStatus !=='success'&& currentStatus !=='error'"name="icon">
    <!-- 节点图标 -->
    <i v-if="icon" class="el-step__icon-inner" :class="[icon]"></i>
    <!-- 节点序号 -->
    <div class="el-step__icon-inner" v-if="!icon && !isSimple">{{index + 1}}</div>
  </slot>
  <i v-else :class="['el-icon-'+ (currentStatus ==='success'?'check':'close')]"
    class="el-step__icon-inner is-status"
  >
  </i>
</div>

数字圆环的款式 is-text 设置的。

:class="`is-${icon ?'icon':'text'}`"

.el-step__icon.is-text {
  border-radius: 50%;
  border: 2px solid;
  border-color: inherit;
}

不同设置组件成果比照如下:

设置居中对齐 alignCenter时,图标的居中成果是通过款式管制的。

.el-step.is-center .el-step__head {text-align: center;}

轴线

轴线元素是类名 el-step__line 的 div 元素,应用了相对布局。

<div class="el-step__line">
  <i class="el-step__line-inner" :style="lineStyle"></i>
</div>

通过偏移量、height、width 等属性设置轴线地位、长度以及粗细。

.el-step__line {
  position: absolute;
  border-color: inherit;
  background-color: #c0c4cc;
}
/* 程度方向 */
.el-step.is-horizontal .el-step__line {
  height: 2px;
  top: 11px;
  left: 0;
  right: 0;
}
/* 程度居中 */
.el-step.is-center .el-step__line {
  left: 50%;
  right: -50%;
}
/* 竖直方向 */
.el-step.is-vertical .el-step__line {
  width: 2px;
  top: 0;
  bottom: 0;
  left: 11px;
}

应用了伪类:last-of-type 设置最初一个步骤元素中轴线不显示。

.el-step:last-of-type .el-step__line {display: none;}

居中对齐下的轴线偏移量有些非凡,渲染成果如下。

理论元素 DOM 构造如下:

轴线进度状态成果通过类名 el-step__line-inner 元素实现,该元素内联款式绑定属性lineStyle

属性 lineStyle 通过办法 calcProgress 依据以后步骤的状态计算而来。办法 updateStatus 作用在生命周期中具体介绍过。

以后步骤元素不是第一个,此时步骤的状态为已实现,就会更新其上一元素的 lineStyle 值,显示进度成果。

updateStatus(val) { 
  // 存在上一步骤节点  计算进度
  if (prevChild) prevChild.calcProgress(this.internalStatus); 
},

calcProgress(status) {
  let step = 100;
  const style = {}; 
  
  style.transitionDelay = 150 * this.index + 'ms'; // 提早响应过渡成果
  if (status === this.$parent.processStatus) {step = this.currentStatus !== 'error' ? 0 : 0;} else if (status === 'wait') {
    step = 0;
    style.transitionDelay = (-150 * this.index) + 'ms'; // 负时会导致过渡立刻开始
  }
  // 简洁格调有效
  style.borderWidth = step && !this.isSimple ? '1px' : 0;
  // 方向不同 赋值不同属性
  this.$parent.direction === 'vertical'
    ? style.height = step + '%'
    : style.width = step + '%';

  this.lineStyle = style;
}

题目 & 形容

题目是类名 el-step__title 的 div 元素, 提供了具名 title 插槽用于自定义题目,后备插槽内容为属性 title值。

形容类名 el-step__description 的 div 元素, 提供了具名 description 插槽用于自定义描述性文字,后备插槽内容为属性 description值。简洁格调下,description 设置生效。

它们依据计算属性 currentStatus 生成以后步骤状态对应的主题款式 is-[wait/process/finish/error/success]

<!-- 题目 & 形容 -->
<div class="el-step__main">
  <div class="el-step__title" ref="title" :class="['is-'+ currentStatus]">
    <slot name="title">{{title}}</slot>
  </div>
  <!-- 简洁格调的箭头 -->
  <div v-if="isSimple" class="el-step__arrow"></div>
  <div  v-else class="el-step__description" :class="['is-'+ currentStatus]">
    <slot name="description">{{description}}</slot>
  </div>
</div>

简洁格调

后面章节中介绍了简洁格调会导致很多设置有效,接下来整体的看下简洁格调的实现成果。

此时 DOM 构造渲染如下。

<div class="el-steps el-steps--simple">
  <!-- 步骤 1 -->
  <div class="el-step is-simple">
    <div class="el-step__head">
      <div class="el-step__line">
        <i class="el-step__line-inner"></i>
      </div>
      <div class="el-step__icon is-icon">
        <i class="el-step__icon-inner el-icon-edit"></i>
      </div>
    </div>
    <div class="el-step__main">
      <div class="el-step__title"> 步骤 1</div>
      <div class="el-step__arrow"></div>
    </div>
  </div>
  <!-- 步骤 2 -->
  <!-- 步骤 3 -->
</div>

此时步骤元素应用 flex 布局,所以图标、题目、箭头元素都在一行内展现。

.el-step.is-simple {
  display: flex;
  align-items: center;
}

.el-step.is-simple .el-step__main {
  display: flex;
  align-items: stretch;
}

轴线元素 DOM 渲染了,然而没有设置宽、高、边框粗细,页面就无奈展现。

.el-step.is-horizontal .el-step__line {height: 2px;}
.el-step.is-vertical .el-step__line {width: 2px;}

calcProgress(status) {
  // ...
  // 简洁格调有效
  style.borderWidth = step && !this.isSimple ? '1px' : 0; 
}

箭头款式应用伪类 :before:after 定义。

.el-step.is-simple .el-step__arrow::after,
.el-step.is-simple .el-step__arrow::before {
  content: "";
  display: inline-block;
  position: absolute;
  height: 15px;
  width: 1px;
  background: #c0c4cc;
}
.el-step.is-simple .el-step__arrow::before {transform: rotate(-45deg) translateY(-4px); 
  transform-origin: 0 0;
}
.el-step.is-simple .el-step__arrow::after {transform: rotate(45deg) translateY(4px); 
  transform-origin: 100% 100%;
}
.el-step.is-simple:last-of-type .el-step__arrow {display: none;}

款式实现

组件款式源码 packages\theme-chalk\src\steps.scss 应用混合指令嵌套生成组件款式。

// packages\theme-chalk\src\steps.scss

// 生成 .el-steps
@include b(steps) {
  // ...
  
  // 生成 .el-steps--simple
  @include m(simple) {// ...}
  // 生成 .el-steps--horizontal
  @include m(horizontal) {// ...}
  // 生成 .el-steps--vertical
  @include m(vertical) {// ...}
}

组件款式源码 packages\theme-chalk\src\step.scss 应用混合指令嵌套生成组件款式。

// packages\theme-chalk\src\step.scss

// 生成 .el-step
@include b(step) {
  // ...
  
  // 生成 .el-step:last-of-type .el-step__line
  @include pseudo(last-of-type) {@include e(line) {// ...} 
    // 生成 .el-step:last-of-type.is-flex 
    @include when(flex) {// ...}
    // 生成 .el-step:last-of-type .el-step__description,.el-step:last-of-type .el-step__main
    @include e((main, description)) {// ...}
  }
  // 生成.el-step__head
  @include e(head) {
    // ...
    
    // 生成 .el-step__head.is-[wait/process/finish/error/success]
    @include when(process) {// ...}
    // wait/finish/error/success ... 
  }
  // 生成 .el-step__icon 
  @include e(icon) {
    // ...
    
    // 生成 .el-step__icon.is-text
    @include when(text) {// ...}
    // 生成 .el-step__icon.is-icon
    @include when(icon) {// ...}
  }
  // 生成 .el-step__icon-inner
  @include e(icon-inner) {
    // ...
    
    // 生成 .el-step__icon-inner[class*="el-icon"]:not(.is-status)
    &[class*=el-icon]:not(.is-status) {// ...}

    // 生成 .el-step__icon-inner.is-status
    @include when(status) {// ...}
  }
  // 生成 .el-step__line
  @include e(line) {// ...}
  // 生成 .el-step__line-inner
  @include e(line-inner) {// ...}
  // 生成 .el-step__main
  @include e(main) {// ...}
  // 生成 .el-step__title
  @include e(title) {
    // ...
    
    // 生成  .el-step__title.is-[wait/process/finish/error/success]
    @include when(process) {// ...} 
    // wait/finish/error/success
  }
  // 生成 .el-step__description
  @include e(description) {
    // ...
    // 生成  .el-step__description.is-[wait/process/finish/error/success]
    @include when(process) {// ...}
    // wait/finish/error/success
 
  }
  // 生成 .el-step.is-horizontal
  @include when(horizontal) {
    // ...
    
    // 生成 .el-step.is-horizontal .el-step__line
    @include e(line) {// ...}
  }
  // 生成.el-step.is-vertical
  @include when(vertical) {
    // ...
    
    // 生成.el-step.is-vertical .el-step__head/main/title/line
    @include e(head) {/*...*/}
    @include e(main) {/*...*/} 
    @include e(title) {/*...*/} 
    @include e(line) {/*...*/}
     
    // 生成.el-step.is-vertical .el-step__icon.is-icon 
    @include e(icon) {@include when(icon) {// ...}
    }
  }
  
  @include when(center) { 
    // 生成.el-step.is-center .el-step__head/description/line
    @include e(head) {/*...*/}
    @include e(description) {/*...*/} 
    @include e(line) {/*...*/}  
  }
  // 生成.el-step.is-simple
  @include when(simple) {
    // ...
    
    // 生成.el-step.is-simple .el-step__head/icon/main/title
    @include e(head) {/*...*/}
    @include e(icon) {/*...*/}
    @include e(main) {/*...*/}
    @include e(title) {/*...*/}
    
    
    @include e(icon-inner) {// 生成 .el-step.is-simple .el-step__icon-inner[class*="el-icon"]:not(.is-status) 
      &[class*=el-icon]:not(.is-status) {// ...}
      // 生成 .el-step.is-simple .el-step__icon-inner.is-status
      &.is-status {// ...}
    }
    
    // 生成 .el-step.is-simple:not(:last-of-type) .el-step__title 
    @include pseudo('not(:last-of-type)') {@include e(title) {// ...}
    }
    // 生成 .el-step.is-simple .el-step__arrow
    @include e(arrow) {
      // ...
      
      // 生成 .el-step.is-simple .el-step__arrow::after,.el-step.is-simple .el-step__arrow::after 
      &::before,
      &::after {// ...}
      // 生成 .el-step.is-simple .el-step__arrow::before  
      &::before {// ...}
      // 生成 .el-step.is-simple .el-step__arrow::after 
      &::after {// ...}
    }
    // 生成 .el-step.is-simple:last-of-type .el-step__arrow 
    @include pseudo(last-of-type) {@include e(arrow) {// ...}
    }
  }
} 

📚参考 & 关联浏览

‘CSS/flex’,MDN\
‘CSS/:last-of-type’,MDN

关注专栏

如果本文对您有所帮忙请关注➕、点赞👍、珍藏⭐!您的认可就是对我的最大反对!

退出移动版