[React] 如何实现一个 Telegram 风格的 Loading 组件?

  |   0 评论   |   0 浏览   |   Erioifpud

效果预览

See the Pen Telegram loading by Erioifpud (@erioifpud) on CodePen.

解析

我们可以把问题分解成两个小问题,分别是:

  1. 如何画一个环形进度条。
  2. 如何让进度条转起来。

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>

viewBoxsvg 容器内部的宽高均分成了 100 个单位,现在 svg 中的图案都能使用这个新的单位,来保证自己能随着容器尺寸按比例缩放。

circle - fill

这个属性负责控制圆的填充色,我们需要画圆环,所以填充色应该是透明的,以下为 fill="red" 的效果。

sCxL5t.png

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-dasharraystroke-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>

sCxbVA.png

可以把第二条线当作是基准,他只有一个参数,所以会被解释器认为实线、空隙都是 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 来表示进度,那么外轮廓的虚线样式可以分成以下几种情况:

  1. 进度 100% 时,实线长度为圆的周长
  2. 进度在 (0, 100%) 之间时,实线和空隙各占一定比例。
  3. 进度 0% 时,虚线长度为圆的周长

动态调整实线和空隙各占的比例会比较麻烦,因为进度 30% 时实线长度要要设置为 30%,而空隙长度要设置成 70%,涉及到两个变量的计算,要保证他们的同时变化。

所以我们可以换个思路,将实线和空隙的长度都设置成圆的周长(他们长度一致),让 stroke-dashoffset 成为变量:

sCxXPP.png

  1. 100% 时不偏移。
  2. 50% 时偏移半个周长。
  3. 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,如下图:

sCxqUI.png

也就是在一个周期开始时,动画速度会逐渐加快,快结束时速度会减慢。而我们需要的是从起始到结束都按匀速变化,所以需要将这个属性设置为 linear


标题:[React] 如何实现一个 Telegram 风格的 Loading 组件?
作者:Erioifpud
地址:https://blog.doiduoyi.com/articles/1609729221646.html

评论

发表评论