0x00 简介
组件 Progress 用于展示操作的当前进度,告知用户当前状态和预期。本文将深入分析组件源码,剖析其实现原理,耐心读完,相信会对您有所帮助。
组件源码文件 packages/progress/src/progress.vue。 🔗gitee repo 🔗组件文档 Progress
更多组件分析详见 👉 📚 Element UI 源码剖析组件总览 。
本专栏的 gitbook 版本地址已经发布 📚《learning element-ui》 ,内容同步更新中!
0x01 源码实现
Props
组件prop声明如下:
<script>
export default {
name: 'ElProgress',
props: {
type: {
type: String,
default: 'line',
validator: val => ['line', 'circle', 'dashboard'].indexOf(val) > -1
},
percentage: {
type: Number,
default: 0,
required: true,
validator: val => val >= 0 && val <= 100
},
status: {
type: String,
validator: val => ['success', 'exception', 'warning'].indexOf(val) > -1
},
strokeWidth: {
type: Number,
default: 6
},
strokeLinecap: {
type: String,
default: 'round'
},
textInside: {
type: Boolean,
default: false
},
width: {
type: Number,
default: 126
},
showText: {
type: Boolean,
default: true
},
color: {
type: [String, Array, Function],
default: ''
},
format: Function
},
};
</script>
各属性功能说明。
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---|---|---|---|---|
| percentage | 百分比(必填) | number | 0-100 | 0 |
| type | 进度条类型 | string | line/circle/dashboard | line |
| stroke-width | 进度条的高度,单位 px | number | — | 6 |
| text-inside | 进度条显示文字内置在进度条内(只在 type=line 时可用) | boolean | — | false |
| status | 进度条当前状态 | string | success/exception/warning | — |
| color | 进度条背景色(会覆盖 status 状态颜色) | string/function/array | — | '' |
| width | 环形进度条画布宽度(只在 type 为 circle 或 dashboard 时可用) | number | 126 | |
| show-text | 是否显示进度条文字内容 | boolean | — | true |
| stroke-linecap | circle/dashboard 类型路径两端的形状 | string | butt/round/square | round |
| format | 指定进度条文字内容 | function(percentage) | — | — |
type 、percentage、 status 供了自定义验证函数,验证属性值是否符合可选值或者可选范围。 当验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
Template
<template>
<div
class="el-progress"
:class="[
'el-progress--' + type,
status ? 'is-' + status : '',
{
'el-progress--without-text': !showText,
'el-progress--text-inside': textInside,
}
]"
role="progressbar"
:aria-valuenow="percentage"
aria-valuemin="0"
aria-valuemax="100"
>
<div class="el-progress-bar" v-if="type === 'line'">
// ...
</div>
<div class="el-progress-circle" v-else>
// ...
</div>
<div class="el-progress__text" v-if="showText && !textInside">
// ...
</div>
</div>
</template>
组件根节点一个类名el-progress的 <div>元素 ,根据组件设定的属性值动态绑定组件样式:
- 进度条类型
el-progress--[line/circle/dashboard] - 进度条状态
is-[success/exception/warning] - 不显示进度条文字
el-progress--without-text - 显示文字内置在进度条内
el-progress--text-inside
属性 role 、aria-valuemin 、aria-valuenow、aria-valuemax 实现 ARIA无障碍网页应用, 更多内容详见 "ARIA",MDN
根节点下包含3个子节点:
- 类名
el-progress-bar的<div>元素用于渲染线形进度条功能。 - 类名
el-progress-circle的<div>元素用于渲染环形/仪表盘进度条功能。 - 类名
el-progress__text的<div>元素渲染外显文字内容。
到此组件主要实现结构已经介绍完毕,接下来将分析各功能源码实现逻辑。
0x02 线形进度条
线形进度条,提供了进度百分比显示、文字内显/外显、文字内容格式化、自定义颜色等功能。
组件 DOM 结构如下图:
- 1️⃣
el-progress组件根节点 - 2️⃣
el-progress-bar线形进度条节点- 3️⃣
el-progress-bar__outer进度条背景 - 4️⃣
el-progress-bar__inner进度显示 - 5️⃣
el-progress-bar__innerText内显文字
- 3️⃣
- 6️⃣
el-progress__text外显文字
<div class="el-progress">
<div class="el-progress-bar" v-if="type === 'line'">
<div class="el-progress-bar__outer" :style="{height: strokeWidth + 'px'}">
<div class="el-progress-bar__inner" :style="barStyle">
<div class="el-progress-bar__innerText" v-if="showText && textInside">{{content}}</div>
</div>
</div>
</div>
<div class="el-progress-circle" v-else>
// ..
</div>
<div
class="el-progress__text"
v-if="showText && !textInside"
:style="{fontSize: progressTextSize + 'px'}"
>
<template v-if="!status">{{content}}</template>
<i v-else :class="iconClass"></i>
</div>
</div>
1️⃣
根据组件设定的属性值绑定组件样式: 进度条类型、进度条状态 、不显示进度条文字、文字显示位置(内置/外部)。
2️⃣
当传入type值为line或不设置 type时,组件渲染成线形进度条。
3️⃣
属性strokeWidth用于设置进度条背景的高度。
4️⃣
计算属性barStyle用于控制进度条进度和颜色。
barStyle根据 percentage 属性值动态生成样式对象 { width: "80%" , backgroundColor: "" }。
进度显示通过宽度百分比控制,此处的 backgroundColor 为自定义颜色值(会覆盖 status 状态颜色),默认情况为空。
barStyle() {
const style = {};
style.width = this.percentage + '%';
style.backgroundColor = this.getCurrentColor(this.percentage);
return style;
},
自定义颜色
方法 getCurrentColor用于自定义进度条颜色,通过判断属性 color 值的类型实现各自逻辑处理。
color 可以接受颜色字符串,函数和数组。
-
若值为函数,函数第一参数应传入
percentage属性值 (格式function(percentage)),直接调用函数即可this.color(percentage)。 -
若值为字符串,直接返回属性值
this.color,color默认值为"", 不设置该属性,默认情况下执行此逻辑。 -
若值为数组(字符串或对象数组),根据
percentage所属范围,返回对应颜色。
若值为数组,则调用方法 getLevelColor 。
getCurrentColor(percentage) {
if (typeof this.color === 'function') {
return this.color(percentage);
} else if (typeof this.color === 'string') {
return this.color;
} else {
return this.getLevelColor(percentage);
}
},
方法 getColorArray,返回对象数组,定义不同数值范围的背景颜色。若字符串数组,方法根据数组长度,自动切分构建成对象数组 [{ color: "#f56c6c", percentage: 20 }]。 若传入对象数组,对象格式应为 { color: string, percentage: number }。
getColorArray() {
const color = this.color;
const span = 100 / color.length;
return color.map((seriesColor, index) => {
// 字符串数组,构建成对象数组
if (typeof seriesColor === 'string') {
return {
color: seriesColor,
percentage: (index + 1) * span
};
}
return seriesColor;
});
}
// [
// { color: "#f56c6c", percentage: 20 },
// { color: "#e6a23c", percentage: 40 },
// { color: "#5cb87a", percentage: 60 },
// { color: "#1989fa", percentage: 80 },
// { color: "#6f7ad3", percentage: 100 },
// ]
方法 getLevelColor中调用方法getColorArray并排序,比对percentage属于哪一区间,返回对应的背景颜色。
getLevelColor(percentage) {
const colorArray = this.getColorArray().sort((a, b) => a.percentage - b.percentage);
for (let i = 0; i < colorArray.length; i++) {
if (colorArray[i].percentage > percentage) {
return colorArray[i].color;
}
}
return colorArray[colorArray.length - 1].color;
},
因为比较使用了>,例如 percentage 值100时, 就会执行return colorArray[colorArray.length - 1].color; 返回数组最后对象的颜色。
<el-progress :percentage="percentage" :color="customColors"></el-progress>
<script>
export default {
data() {
return {
percentage: 100,
customColors: [
{color: '#f56c6c', percentage: 20},
{color: '#e6a23c', percentage: 40},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 80},
{color: '#6f7ad3', percentage: 100}
]
};
},
}
</script>
5️⃣
内显内容计算属性 content。默认按照 [percentage]%格式显示进度百分比。 若设置了属性format,则使用函数处理返回文字内容。
组件通过属性 showText 、 textInside 控制文字显示状态和位置。
content() {
if (typeof this.format === 'function') {
return this.format(this.percentage) || '';
} else {
return `${this.percentage}%`;
}
}
6️⃣
外显内容跟内显一样,计算属性content。若设置了进度条状态status,则显示图标。
<template v-if="!status">{{content}}</template>
<i v-else :class="iconClass"></i>
计算属性 iconClass 返回不同状态 success/exception/warning的icon, 不同类型下success/exception 对应图标有所区别。
iconClass() {
if (this.status === 'warning') {
return 'el-icon-warning';
}
if (this.type === 'line') {
return this.status === 'success' ? 'el-icon-circle-check' : 'el-icon-circle-close';
} else {
return this.status === 'success' ? 'el-icon-check' : 'el-icon-close';
}
},
使用计算属性progressTextSize控制字体大小。
当类型为line时,基于进度条高度-属性strokeWidth;当类型为circle/dashboard时,基于环形进度条画布宽度-属性width。
progressTextSize() {
return this.type === 'line'
? 12 + this.strokeWidth * 0.4
: this.width * 0.111111 + 2 ;
},
自定义文字格式
// format 示例
<el-progress :percentage="100" :format="format"></el-progress>
<script>
export default {
methods: {
format(percentage) {
return percentage === 100 ? '满' : `${percentage}%`;
}
}
};
</script>
0x03 线形进度条
环形/仪表盘进度条实现主要使用了<svg>元素,会另开篇幅进行详细介绍。
0x04 组件样式
src/progress.scss
组件样式源码 packages\theme-chalk\src\progress.scss 使用 scss 的混合指令 b、 e、 m、 when 嵌套生成组件样式。
// 生成 .el-progress
@include b(progress) {
// ...
// 生成 .el-progress__text
@include e(text) {
// ...
// 生成 .el-progress__text i
i {
// ...
}
}
// 生成 .el-progress--circle, .el-progress--dashboard
@include m((circle,dashboard)) {
// ...
// 生成
// .el-progress--circle .el-progress__text,
// .el-progress--dashboard .el-progress__text
.el-progress__text {
// ...
// 生成
// .el-progress--circle .el-progress__text i,
// .el-progress--dashboard .el-progress__text i
i {
// ...
}
}
}
@include m(without-text) {
// 生成 .el-progress--without-text .el-progress__text
.el-progress__text {
// ...
}
// 生成 .el-progress--without-text .el-progress-bar
.el-progress-bar {
// ...
}
}
@include m(text-inside) {
// 生成 .el-progress--text-inside .el-progress-bar
.el-progress-bar {
// ...
}
}
// 状态样式
@include when(success) {
// 生成 .el-progress.is-success .el-progress-bar__inner
.el-progress-bar__inner {
// ...
}
// 生成 .el-progress.is-success .el-progress-bar__text
.el-progress__text {
// ...
}
}
@include when(warning) {
// ...
}
@include when(exception) {
// ...
}
}
// 生成 .el-progress-bar
@include b(progress-bar) {
// ...
// 生成 .el-progress-bar__outer
@include e(outer) {
// ...
}
// 生成 .el-progress-bar__inner
@include e(inner) {
// ...
// 生成 .el-progress-bar__inner::after
@include utils-vertical-center;
}
// 生成 .el-progress-bar__innerText
@include e(innerText) {
// ...
}
}
// 该关键帧规则 代码中没有生效
@keyframes progress {
0% {
background-position: 0 0;
}
100% {
background-position: 32px 0;
}
}
在类.el-progress-bar__inner 定义了默认进度条颜色 background-color: #409eff;。
若指定了组件状态,会生成样式覆盖掉默认颜色。同时制定了外显文本的字体图标颜色。
// success/exception/warning
.el-progress.is-success .el-progress-bar__inner {
background-color: #67c23a;
}
.el-progress.is-success .el-progress__text {
color: #67c23a;
}
若是使用属性color自定义颜色,会生成内联样式,会覆盖 status 状态颜色。
内显文本居右对齐,属性是在.el-progress-bar__inner的定义的。
.el-progress-bar__inner {
text-align: right;
// ...
}
0x05 关注专栏
此文章已收录到专栏中 👇,可以直接关注。