Front Engineering Workshop: 从零开始build一个进度环

Database and Ruby, Python, History


需求是构建一个上传进度的圆圈。

圆饼图

首先构建一个圆饼图。

border-radius: 50% 构建一个圆形,然后conic-gradient 构建一个圆饼出来。

.pie-chart {
  width: 100px;
  height: 100px;
  background: conic-gradient(#4352f3 0deg, #e52c5c 360deg);
  border-radius: 50%;
}

内嵌一个圆形

再在中间嵌一个圆形。

.inner-circle {
  width: calc(160px - 8px);
  height: calc(160px - 8px);
  border-radius: 50%;
  background-color: white;
  display: flex;
  align-items: center;
  justify-content: center;
}
50%

动态更新进度

写一段 Javascript 去更新进度

export const updateProgressRing = (progressRing, percent) => {
  if (progressRing === null) {
    return;
  }

  const roundPercent = Number.parseInt(percent * 100);
  const processValue = progressRing.querySelector(".percentage");

  processValue.textContent = `${roundPercent}%`;

  progressRing.style.background = `conic-gradient(#0d6efd ${
    roundPercent * 3.6
  }deg, white 0deg)`;
};

跟踪进度

这里用了阿里云的 OSS 上传文件。

如果单纯上传一个文件,进度可以通过回调函数参数 progress 跟踪,然后再更新进度就行了。 如果是上传多个文件,那么需要先构建一个数组,记录每个文件的上传进度,然后再算出整体进度。

代码如下。

const uploadFile = async (key, file, idx, progressArray) => {
  const store = new OSS({});
  const options = {
    meta: { type: object_type },
    progress: (p, cpt, res) => {
      if (progressArray && progressArray.length > 0) {
        progressArray[idx] = p;
        totalProcent =
          progressArray.reduce((sum, e) => sum + e, 0) / progressArray.length;
        updateProgressRing(progressRing, totalProcent);
      }
    },
  };

  const result = await store.multipartUpload(object_key, data, options);
};

const uploadFiles = async (files) => {
  const progressArray = Array(files.length).fill(0);
  const promises = Array.from(files).map(async (file, idx) => {
    const file_suffix = file.name.split(".").slice(-1)[0];
    const timestamp = Math.floor(Date.now());
    const file_key = `file_${timestamp}.${file_suffix}`;
    const isUploaded = await uploadFile(file_key, file, progressArray, idx);
    return file_key;
  });

  const file_keys = await Promise.all(promises);
};

SVG

另外一种实现方式是用 SVG。关键点是stroke-dasharray,第一个参数是虚线宽度,第二个参数是虚线间隔。将虚线间隔设置为大于圆圈周长,虚线宽度设置为进度长度,就可以表示进度了。

<svg
  width="440"
  height="440"
  style="
    transform: rotate(-90deg);
"
>
  <circle
    cx="220"
    cy="220"
    r="170"
    stroke-width="8"
    stroke="#D1D3D7"
    fill="none"
  ></circle>
  <circle
    cx="220"
    cy="220"
    r="170"
    stroke-width="8"
    stroke="#00A5E0"
    fill="none"
    stroke-dasharray="534 1069"
  ></circle>
</svg>