JS中上传进度提示

在上传图片的过程中,如果耗时较长,如果能够给用户一个进度的提示,会让用户体验更加良好。做了几个比较常见的上传进度提示的demo:

原理

这几个demo是在Vue框架中完成的,使用了axios发送http请求,原理和简单,都是获取上传进度值,控制视图状态,其他就是表现形式的不同。

关键点就是onprogress这个事件,它是XMLHttpRequest对象的一个回调函数,在上传或者下载过程中会周期性执行。接受一个参数eventevent有两个参数:

  • loaded:已经传输的数量
  • total:要传输的总数量

示例如下:

var xmlhttp = new XMLHttpRequest(),
  method = 'GET',
  url = 'https://developer.mozilla.org/';

xmlhttp.open(method, url, true);
xmlhttp.onprogress = function (event) {
  //do something 
  const progressRatio = event.loaded / event.total
};
xmlhttp.send();

在axios中的配置选项中也提供了对应的接口:

{
 // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },
}

在对应的函数中我们不断修改progress值就可以实现上传进度在视图的体现。

使用

上面前两种方案使用了HTML5的标签和标签,

标签标示任务的进度(进程)。max规定要完成多少工作,value表示已经完成的进度:

<progress :value=progress max="100" class="meter">progress>

标签定义已知范围或分数值内的标量测量,例如磁盘用量、查询结果的相关性,等等。实际上在W3C标准上有明确的标识:

标签不应用于指示进度(在进度条中)。如果标记进度条,请使用标签。

-_-||

利用CSS自己写

原理都是相同的,第三种用了一个半透明的遮罩层,随着上传进图不断完成,使用transform将遮罩层移走

圆环

圆环这个稍微麻烦一点,需要对border的知识进行一点回顾。

JS中上传进度提示_第1张图片

border实际上是一个的梯形,将上、右border上色,左、下border透明,再增加border-radius就出现了一个倾斜的半圆环

JS中上传进度提示_第2张图片

然后旋转-135°,让半圆环竖直放置,然后增加动画效果:

.test {
  margin-left: 50px;
  margin-top: 50px;
  width: 200px;
  height: 200px;
  border: 40px solid transparent;
  border-right-color: gray;
  border-top-color: forestgreen;
  border-radius: 50%;
  transform: rotate(-135deg);
  animation: test-run 4s linear infinite;
}

@keyframes test-run {
  0% {
    transform: rotate(-135deg);
  }

  50%, 100% {
    transform: rotate(45deg);
  }
}

这样半圆环就旋转起来了:

JS中上传进度提示_第3张图片

接下来要做的就是用一个和半圆环宽度相同,高度相同的

,将透明的另半部分半圆环切掉,再做出一个对称的左半个半圆环,就Ok了

可以添加一个背景圆环

<div class="outer">
  <div class="loader">
    <div class="loader-bg">div>
    <div class="loader-container">
      <div class="loader-circle loader-left-circle loader-left-circle-animation">div>
    div>
    <div class="loader-container">
      <div class="loader-circle loader-right-circle loader-right-circle-animation">div>
    div>
  div>
div>
.loader {
  box-sizing: border-box;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 150px;
  height: 150px;
}

.loader-container {
  box-sizing: inherit;
  position: relative;
  width: 50%;
  height: 100%;
  overflow: hidden;
  float: left;
}

.loader-bg {
  box-sizing: inherit;
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  border: 10px darkgray solid;
  border-radius: 50%;
}

.loader-circle {
  box-sizing: inherit;
  position: absolute;
  top: 0;
  width: 200%;
  height: 100%;
  border: 10px transparent solid;
  border-radius: 50%;
}

.loader-left-circle {
  left: 0;
  border-bottom-color: royalblue;
  border-left-color: royalblue;
}

.loader-left-circle-animation {
  animation: circle-left-run 4s linear infinite;
}

@keyframes circle-left-run {
  0%, 50% {
    transform: rotate(-135deg);
  }

  100% {
    transform: rotate(45deg);
  }
}

.loader-right-circle {
  right: 0;
  border-top-color: royalblue;
  border-right-color: royalblue;
}

.loader-right-circle-animation {
  animation: circle-right-run 4s linear infinite;
}

@keyframes circle-right-run {
  0% {
    transform: rotate(-135deg);
  }

  50%, 100% {
    transform: rotate(45deg);
  }
}

这样一个不断旋转的进度条就出现了:

JS中上传进度提示_第4张图片

在Vue中,我们将动画去掉,旋转角度用变量控制:

<template>
  <div class="grid-item4 grid-item">
    <img class="img" :src="previewImg.src">
    <div class="filter" v-if="uploading">
      <div class="loader">
        <div class="loader-bg">
          <p class="progress-text progress-text-center">{{progressText}}p>
        div>
        <div class="loader-container">
          <div class="loader-circle loader-left-circle" :style="{'transform': `rotate(${leftAngel}deg)`}">div>
        div>
        <div class="loader-container">
          <div class="loader-circle loader-right-circle" :style="{'transform': `rotate(${rightAngel}deg)`}">div>
        div>
      div>
    div>
  div>
template>

<script>
  // TODO: 1上传token
  // TODO: 2总结

  export default {
    data() {
      return {
        progressRatio: 0,
        previewImg: {},
        max: 100,
        uploadFinish: false
      }
    },
    computed: {
      progress() {
        return Math.round(this.max * this.progressRatio)
      },
      progressText() {
        return this.progress ? this.progress + '%' : '0'
      },
      rightAngel() {
        const endAngel = 45;
        return this.progress > 50 ? endAngel : -135 + 360 * this.progress * 0.01
      },
      leftAngel() {
        const startDeg = -135;
        return this.progress < 50 ? startDeg : startDeg + (this.progress * 0.01 - 0.5) * 360
      }
    },
    methods: {
      // 预览图片
      onPreview(e) {
        // 省略
      },

      // 重置状态
      resetStatus() {
        this.uploadFinish = false;
        this.progressRatio = 0;
      },

      // 上传图片
      async upload() {
        const token = await this.getUploadToken();
        const data =  {
          token,
          file: this.previewImg.file,
        };
        // 上传进度
        let onUploadProgress = (progressEvent) => {
          this.progressRatio = progressEvent.loaded / progressEvent.total
        };
        const config = {
          onUploadProgress,
          headers: {
            'Content-Type': 'multipart/form-data'
          },
          withCredentials: false,
        };
        try {
          // 上传到七牛
          const res = await axios.post('your path', this.getFromData(data), config);
          this.previewImg.src = `${domain}/${res.data.key}`;
        } catch (e) {
          this.progressRatio = 0;
          console.log('上传失败', e)
        }
      },

      // 获取上传图片凭证
      async getUploadToken() {
        // 省略
      },

      getFromData(data) {
      // 省略
      }
    },
  }
script>

<style scoped lang="less">
  .upload-input {
    margin: 10px 0;
  }
  .img {
    width: 100%;
  }
  .meter {
    width: 150px;
  }
  .filter {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    transition: all 3s linear;
  }
  .progress-text {
    position: absolute;
    left: 50%;
    top: 52%;
    transform: translateX(-50%);
    width: 50px;
    height: 20px;
    color: lavenderblush;
    font-size: 16px;
    line-height: 1;
  }
  .progress-text-center {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
  .loader {
    box-sizing: border-box;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 150px;
    height: 150px;
  }
  .loader-container {
    box-sizing: inherit;
    position: relative;
    width: 50%;
    height: 100%;
    overflow: hidden;
    float: left;
  }
  .loader-bg {
    box-sizing: inherit;
    position: absolute;
    left:0;
    top:0;
    width: 100%;
    height: 100%;
    border: 10px darkgray solid;
    border-radius: 50%;
  }
  .loader-circle {
    box-sizing: inherit;
    position: absolute;
    top: 0;
    width: 200%;
    height: 100%;
    border: 10px transparent solid;
    border-radius: 50%;
  }
  .loader-left-circle {
    left: 0;
    border-bottom-color: royalblue;
    border-left-color: royalblue;
  }
  .loader-right-circle {
    right: 0;
    border-top-color: royalblue;
    border-right-color: royalblue;
  }
style>

这样上面第四种圆环进度提示就完成了

完整demo地址在这里。

也可以使用CSS的clip属性完成,具体可以参考这篇文章。

优化

还有进一步优化的空间,在网速比较慢的时候,现在上传进度的显示是卡顿的,不知道有没有办法通过预估出上传时间或者其他途径,让上传过程的显示更加顺滑

有时间再研究一下。

参考

  • MDN-onprogress
  • Axios中文说明
  • HTML标签
  • HTML标签
  • 使用CSS3实现圆形进度条

你可能感兴趣的:(JavaScript)