简介
前文步骤组件状态治理次要从生命周期的角度介绍了步骤组件实例化时组件之间的状态治理,本文将介绍步骤组件具体实现,急躁读完,置信会对您有所帮忙。
更多组件分析详见 👉 📚 Element 2 源码分析组件总览。
步骤组件 step.vue
步骤条次要性能实现都在该组件中。
HTML
模板渲染成一个类名 el-step
的 div 元素,元素蕴含两局部内容
- 用于图标、轴线的渲染。
- 用于题目、形容的渲染。
// 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-center
、direction
、space
都将生效。
接下将一一剖析为什么设置会生效。
属性 direction
用于设置显示方向,生成类名 is-vertical
或 is-horizontal
。依据计算属性 isSimple
判断设置简洁格调时,只会生成类名 is-simple
。
!isSimple && `is-${$parent.direction}`, // 生成类名 is-vertical/is-horizontal
isSimple && 'is-simple', // 生成类名 is-simple
// computed 是否简洁格调
isSimple() {return this.$parent.simple;},
顶层组件中也会依据属性 simple
、direction
生成根元素类名 el-steps--simple
、el-steps--vertical
或el-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)的尺寸。相当于设置了步骤元素的 width
或 height
。
当一个元素同时被设置了
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: none
或 flex: 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 设置失效。
当组件状态值为 success
或 error
时,应用零碎提供图标。
<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
关注专栏
如果本文对您有所帮忙请关注➕、点赞👍、珍藏⭐!您的认可就是对我的最大反对!