[React] 如何实现一个 Telegram 风格的 Loading 组件?
效果预览
See the Pen Telegram loading by Erioifpud (@erioifpud) on CodePen.
解析
我们可以把问题分解成两个小问题,分别是:
- 如何画一个环形进度条。
- 如何让进度条转起来。
HTML 结构
<div class="loading" style={loadingStyle}>
<div class="progress" style={textStyle}>{value}</div>
<svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 100 100">
<circle fill="transparent" cx="50" cy="50" r="40" stroke-width={thickness} stroke-dasharray="251.3272" stroke-dashoffset="0" class="underlay"></circle>
<circle fill="transparent" cx="50" cy="50" r="40" stroke-width={thickness} stroke-dasharray="251.3272" stroke-dashoffset={dashOffset} stroke-linecap="round" style={{ stroke: color }} class="overlay"></circle>
</svg>
</div>
先看一下 HTML 的结构,一个 loading 容器,里面有一个 svg
元素,svg
包括两个圆环。
画一个环形进度条
svg - viewBox
这个属性你可以理解成给 svg
内部添加百分比单位的,正确地设置 viewBox
,能使图形按 svg
容器的比例自动缩放,例子可参考https://blog.doiduoyi.com/articles/1609316672602.html。
<svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 100 100"></svg>
viewBox
将 svg
容器内部的宽高均分成了 100 个单位,现在 svg
中的图案都能使用这个新的单位,来保证自己能随着容器尺寸按比例缩放。
circle - fill
这个属性负责控制圆的填充色,我们需要画圆环,所以填充色应该是透明的,以下为 fill="red"
的效果。
circle - cx、cy、r
这三个属性分别是圆心的 x 坐标、圆心的 y 坐标与半径,上面我们将 svg
划分成了 100 个单位,所以 cx="50" cy="50"
会让圆心在 svg
里居中,这就和 CSS 中的百分比很像,半径占了整个 svg
尺寸的 40%。
circle - stroke-width、stroke-dasharray、stroke-dashoffset
stroke 这几个属性都是与外轮廓相关的,stroke-width
表示外轮廓的厚度/宽度。
stroke-dasharray
和 stroke-dashoffset
都是与虚线相关的定义,前者表示实线与空隙的长度,后者表示虚线的偏移距离,使用格式如下:
stroke-dasharray="实线的长度 空隙的长度"
(在参数数量是奇数的情况下,会自动将参数重复一次,比如 1 2 3 会变成 1 2 3 1 2 3)
(根据以上参数可以画出一段单位线段:"- --- -- ",将多个单位线段连接后就组成了一条虚线)
stroke-dashoffset="偏移距离"
接下来看一个例子:
<svg style="width:100%;height:100%">
<line stroke-dasharray="15" stroke-dashoffset="15" stroke="#000" stroke-width="4" x1="0" y1="10" x2="300" y2="10"></line>
<line stroke-dasharray="15" stroke="#000" stroke-width="4" x1="0" y1="30" x2="300" y2="30"></line>
<line stroke-dasharray="15 10" stroke="#000" stroke-width="4" x1="0" y1="50" x2="300" y2="50"></line>
</svg>
可以把第二条线当作是基准,他只有一个参数,所以会被解释器认为实线、空隙都是 15 个单位(一样长)。
比较第二条和第三条线,后者调整了空隙长度为 10,所以空隙部分会比较短。
第一条线在第二条的基础上,向左偏移了 15 个单位,正好把第一段实线给隐藏了。
如何控制进度
<circle fill="transparent" cx="50" cy="50" r="40" stroke-width={thickness} stroke-dasharray="251.3272" stroke-dashoffset={dashOffset} stroke-linecap="round" style={{ stroke: color }} class="overlay"></circle>
现在看回代码中的 circle
,我们使用外轮廓 stroke
来表示进度,那么外轮廓的虚线样式可以分成以下几种情况:
- 进度 100% 时,实线长度为圆的周长。
- 进度在
(0, 100%)
之间时,实线和空隙各占一定比例。 - 进度 0% 时,虚线长度为圆的周长。
动态调整实线和空隙各占的比例会比较麻烦,因为进度 30% 时实线长度要要设置为 30%,而空隙长度要设置成 70%,涉及到两个变量的计算,要保证他们的同时变化。
所以我们可以换个思路,将实线和空隙的长度都设置成圆的周长(他们长度一致),让 stroke-dashoffset
成为变量:
- 100% 时不偏移。
- 50% 时偏移半个周长。
- 0% 时偏移一个周长。
超出的部分会被自动隐藏,这就是代码中的实现,其中的 251.3272
是通过周长公式2\pi r计算出的。
进度与 stroke 偏移的映射
根据上一个章节,我们可以知道 stroke
偏移的取值范围是 [0, 周长]
,其中进度 0 时偏移一个周长,而进度 100% 时偏移 0,所以我们需要做一个函数,将进度转换为偏移值。
// 原数据 原数据的最小值 原数据的最大值 映射数据的最小值 映射数据的最大值
function map (val, originMin, originMax, targetMin, targetMax) {
return (val - originMin) * (targetMax - targetMin) / (originMax - originMin) + targetMin
}
const dashOffset = map(value, 0, 100, 251.3272, 0)
这就是个简单的比例计算,到这里我们已经了解做环形进度条需要的步骤了,通过组合数据与模板,就能完成组件。
让进度条转起来
Telegram 的 Loading 与其他的环形进度条相比较,他多了一个旋转的动画,这个动画我们可以使用 CSS 实现,首先需要准备一个 keyframes
:
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
这里要注意,to
那不能写成 360deg
,因为起始和结束状态一致(位置重合),浏览器会直接跳过中间的状态,和没有动画一样。
接着将动画设置在 svg
元素上:
svg {
animation-name: rotate;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
animation-timing-function
表示动画补帧时使用的过渡方式,默认是 ease
,如下图:
也就是在一个周期开始时,动画速度会逐渐加快,快结束时速度会减慢。而我们需要的是从起始到结束都按匀速变化,所以需要将这个属性设置为 linear
。
标题:[React] 如何实现一个 Telegram 风格的 Loading 组件?
作者:Erioifpud
地址:https://blog.doiduoyi.com/articles/1609729221646.html