在上传图片的过程中,如果耗时较长,如果能够给用户一个进度的提示,会让用户体验更加良好。做了几个比较常见的上传进度提示的demo:
这几个demo是在Vue框架中完成的,使用了axios发送http请求,原理和简单,都是获取上传进度值,控制视图状态,其他就是表现形式的不同。
关键点就是onprogress
这个事件,它是XMLHttpRequest对象的一个回调函数,在上传或者下载过程中会周期性执行。接受一个参数event
,event
有两个参数:
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标准上有明确的标识:
标签不应用于指示进度(在进度条中)。如果标记进度条,请使用
标签。
-_-||
原理都是相同的,第三种用了一个半透明的遮罩层,随着上传进图不断完成,使用transform
将遮罩层移走
圆环这个稍微麻烦一点,需要对border的知识进行一点回顾。
border实际上是一个的梯形,将上、右border上色,左、下border透明,再增加border-radius
就出现了一个倾斜的半圆环
然后旋转-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);
}
}
这样半圆环就旋转起来了:
接下来要做的就是用一个和半圆环宽度相同,高度相同的 可以添加一个背景圆环 这样一个不断旋转的进度条就出现了: 在Vue中,我们将动画去掉,旋转角度用变量控制: 这样上面第四种圆环进度提示就完成了 完整demo地址在这里。 也可以使用CSS的 还有进一步优化的空间,在网速比较慢的时候,现在上传进度的显示是卡顿的,不知道有没有办法通过预估出上传时间或者其他途径,让上传过程的显示更加顺滑 有时间再研究一下。<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);
}
}
<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>
clip
属性完成,具体可以参考这篇文章。优化
参考
标签
标签