Vue图片上传

前言:很多项目中都需要用到图片上传功能,而其多处使用的要求,为了避免重复造轮子,让我决定花费一些时间去深入了解,最终封装了一个vue的图片上传组件。现将总结再次,希望有帮助。

Layout

<div class="upload-wraper">
  <input type="file" id="upload_ele" multiple="false" accept="image/*" @change="uploadFile()" />
</div>

type=file将类型设置为选择文件

multiple是否允许文件的多选

accept=”image/*” 将文件的类型限制为image类型,*包括所有格式的图片

change事件当type设置为file后,出发的事件为change,也可通过submit实现

这里布局的话,因为是vue组件所以简单点,不需要多个input构成form表单,然后通过submit提交,一个input通过change事件来实现上传

Js

Basic information for uploading files

let oFile = document.getElementById('upload-ele').files[0];

files是input设置为file后的一个js内置对象。files对象死一个read-only属性,不可被修改!

isClosed:false是否已经结束,可以理解为标签是否闭合

lastModified:1539602132000最后更改的时间timeStamp

lastModifiedDate:Mon Oct 15 2018 19:15:32 GMT+0800 (CST) {}最后更改时间

name:”D9791645A5DF19D17FD7392A080E7A28.jpg”图片的名称

path:”/Users/mac/Documents/D9791645A5DF19D17FD7392A080E7A28.jpg”图片所在的路径为本地路径

Size:38938图片的大小信息 单位为kb

type:’image/jpeg’图片的类型

webkitRelativePath:””文件相关的路径

File Size Processing

大小判断

(oFile.size / 1024) > 1024

1M = 1024KB

Smaller

let form = new FormData();
form.append('file',oFile);
let xhr = new XMLHttpRequest();
xhr.open('post',url,true);
xhr.timeout = 30 * 1000;
xhr.upload.onprogress = this.progress;
xhr.onload = this.uploadComplete;
xhr.onerror = this.uploadFailed;
xhr.upload.onloadstart = () => {
  let date = new Date().getTime();
  let initSize = 0;
}
xhr.send(form);

XMLHttpRequest()是js内置对象,可以使用该属性实现请求头的处理操作。

xhr.open(); 请求方法: post,url: 服务器接受的地址,true/false 是否异步

xhr.timeout; 设置超时时间,据情况而定

xhr.ontimeout; 超时处理,一般为取消请求

xhr.upload.onprogress; 进程处理 ,上传文件的进度处理

xhr.onload; 请求成功处理

xhr.onerror; 请求失败处理

Xhr.upload.onloadstart; 请求开始处理的操作,一般创建时间戳,初始化大小。

xhr.send(); 请求配置完毕,发送请求

这里小于1M的文件是可以直接通过放到formData中,通过配置xhr将图片对象上传到oss,在请求成功时拿到图片的网络路径供后,提供给后端。

Larger

why:为什么要对大于1M的图片进行处理呢?因为文件在大于1M的一般是上传失败的。常见的ftp文件上传时,默认是2M,大于时,会上传失败,所以这里的图片上传进行了大于1M压缩的处理。

how:流程与小于1M时十分相似,不过,这里添加了图片的压缩处理。

more:一般在较大文件处理时,后端也是可以进行处理的,单独提供较大图片的接口。这时,我们就不需要进行压缩了,只需要在判断大于1024KB时接口更换为处理较大文件的接口即可。

如何压缩?

  1. 通过FileReader对象将文件对象读取成base64格式。
  2. 通过Image对象结合canvas画布将base格式的图片将一定的比例缩小后重新保存成为base64格式。
  3. 将base64格式转换成Blob流后,图片就可以说是压缩完成了。
  4. 剩下的步骤重复formData,XMLHttpRequest即可完成较大图片的上传。这里需要注意的是,在formData中防止文件时,因为此时是Blob流,所以防止文件时,需要自定义文件格式,这里的处理是 Blob文件 + 时间戳与.jpg组成。

组件源码

<template>
<!-- 图片上传组件 -->
  <div class="upload-wraper">
    <input type="file" id="upload-ele" multiple="false"  accept="image/*" @change="uploadFile(url,quality,hasApi,BigUrl)">
    <toast v-model="total.isShow" type="text">{{total.text}}</toast>
  </div>
</template>
<script>
import { Indicator } from 'mint-ui';
import { Toast } from 'vux';
export default {
  name: 'uploadImage',
  components: {
    Indicator,
    Toast,
  },
  props: {
    'url': String, // 小于1M的api
    'quality': Number, // 图片质量
    'BigUrl': {
      type: String,
      default: '',
    }, // 大于1M图片的api
    'hasApi': {
      type: Boolean,
      default: false
    } // 是否对大于1M的图片单独分配接口
  },
  data() {
    return {
      total: {isShow:false,text:""}
    }
  },
  methods: {
    uploadFile(url,quality,hasApi,BigUrl) {
      Indicator.open(`上传中`);
      // files是input设置为file后的一个内置对象。files对象是一个只读属性,不可被修改。
      var oFile = document.getElementById('upload-ele').files[0];
      console.log('File Object',oFile);
      var form = new FormData();
      // 大小判断 如果大于1M就新型压缩处理
      // console.log('File Size Unit:KB',(oFile.size / 1024))
      if((oFile.size / 1024) > 1024) {
        if(hasApi) {  
          form.append('file',oFile);
          let xhr = new XMLHttpRequest(); //XMLHttpRequest Object
          xhr.open('post',BigUrl,true); // Method: post,url: server receive address,true/false isAsync
          xhr.timeout = 30 * 1000;  //Timeout one minute;
          xhr.ontimeout = this.uploadTimeout; // Upload Timeout Function
          xhr.upload.onprogress = this.progress; //Progress Function
          xhr.onload = this.uploadComplete; //Upload Success Function
          xhr.onerror = this.uploadFailed; //Upload Failed Funciton
          xhr.upload.onloadstart = () => {
            let date = new Date().getTime(); // TimeStamp Prevents Caching
            let initSize = 0; // Init File Size Zero
          } // Upload Start
          xhr.send(form);
        } else {
          this.imgCompress(oFile,{quality: quality},
          (base64Codes) => {
            var bl = this.convertBase64UrlToBlob(base64Codes);
            form.append("file", bl, "file_" + Date.parse(new Date()) + ".jpg"); // 文件对象
            console.log(form);
            let xhr = new XMLHttpRequest(); // XMLHttpRequest 对象
            xhr.open("post", url, true); //post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
            xhr.upload.onprogress = this.progress; //Progress Function
            xhr.ontimeout = this.uploadTimeout; // Upload Timeout Function
            xhr.onload = this.uploadComplete; //Upload Success Function
            xhr.onerror = this.uploadFailed; //Upload Failed Funciton
            xhr.upload.onloadstart = function() {
              let ot = new Date().getTime(); // TimeStamp Prevents Caching
              let oloaded = 0; // Init File Size Zero
            };// Upload Start
            xhr.send(form); 
          })
        }
      } else {
        // 小与1M
        form.append('file',oFile);
        let xhr = new XMLHttpRequest(); //XMLHttpRequest Object
        xhr.open('post',url,true); // Method: post,url: server receive address,true/false isAsync
        xhr.timeout = 30 * 1000;  //Timeout one minute;
        xhr.ontimeout = this.uploadTimeout; // Upload Timeout Function
        xhr.upload.onprogress = this.progress; //Progress Function
        xhr.onload = this.uploadComplete; //Upload Success Function
        xhr.onerror = this.uploadFailed; //Upload Failed Funciton
        xhr.upload.onloadstart = () => {
          let date = new Date().getTime(); // TimeStamp Prevents Caching
          let initSize = 0; // Init File Size Zero
        } // Upload Start
        xhr.send(form); 
      }
    },
    /**
     * @description Request Success
     */
    uploadComplete(evt) {
      let res = JSON.parse(evt.target.responseText);
      if(evt.target.readyState == 4 && evt.target.status == 200) {
        this.$emit('upload',res.result.url);
      } else {
        this.uploadFailed();
      }
    },
    /**
     * @description Request Failed
     */
    uploadFailed(evt) {
      Indicator.close();
      this.total = {
        isShow:true,
        text:"上传失败"
      }
    },
    /**
     * @description Timeout Function
     */
    uploadTimeout(evt) {
      this.cancleUploadFile(evt)
      Indicator.close();
      this.total = {
        isShow:true,
        text:"请求超时"
      }
    },
    /**e
     * @description Upload Cancel
     */
    cancleUploadFile(evt) {
      evt.abort();
    },
    /**
     * @description Requst Loading....
     */
    progress(progressEvent) {
      if(!progressEvent.lengthComputable) {
        this.total = {
          isShow:true,
          text:"进度读取失败"
        }
        return false;
      }
      let precent = Math.floor(100 * progressEvent.loaded / progressEvent.total); //Upload Progress
      if(precent < 100) {
        Indicator.open(`上传中${precent}%`);
      } else {
        Indicator.close();
        this.total = {  
          isShow:true,
          text:"上传成功"
        }
      }
    },
    /**
      * @description 图片压缩
      * @param {Object} file 压缩的文件
      * @param {Number} width 压缩后端宽度,宽度越小,字节越小
      */
    imgCompress(file,width,callBack) {
      var ready = new FileReader();
      ready.readAsDataURL(file);
      ready.onload = () => {
        this.canvasDataURL(ready.result,width,callBack);
      }    
    },
    /**
     * 将以base64的图片url数据转换为Blob
     * @param urlData
     * 用url方式表示的base64图片数据
     */
    convertBase64UrlToBlob(urlData) {
      var arr = urlData.split(","),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    },  
    /**
     * @description 大于1M的图片进行重新绘制压缩
     */
    canvasDataURL(path, obj, callback) {
      var img = new Image();
      img.src = path;
      img.onload = () => {
        // var that = this;
        // 默认按比例压缩
        var w = this.width,
          h = this.height,
          scale = w / h;
        w = obj.width || w;
        h = obj.height || w / scale;
        var quality = 0.7; // 默认图片质量为0.7
        //生成canvas
        var canvas = document.createElement("canvas");
        var ctx = canvas.getContext("2d");
        // 创建属性节点
        var anw = document.createAttribute("width");
        anw.nodeValue = w;
        var anh = document.createAttribute("height");
        anh.nodeValue = h;
        canvas.setAttributeNode(anw);
        canvas.setAttributeNode(anh);
        ctx.drawImage(img, 0, 0, w, h);
        // 图像质量
        if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
          quality = obj.quality;
        }
        // quality值越小,所绘制出的图像越模糊
        var base64 = canvas.toDataURL("image/jpeg", quality);
        // 回调函数返回base64的值
        callback(base64);
      };
    },
  }
}
</script>
<style lang="less" scoped>
  .upload-wraper {
    width: 100%;
    height: 100%;
  }
</style>

这里是公众号项目,所以,这里引入的第三方插件为mint-ui和vux。具体情况视情况而定。

该组件例外封装了,后端是够对较大图片的处理,如果后端已经进行处理,则直接调用。如果没有处理,则进行压缩处理,

组件的封装,可灵活修改,还有很多地方仍待修改

插槽,图片上传后,回显可在组件内部实现。借由slot更加完美。

该组件限制了图片文件的上传,其他文件则不行。

补充:引入组件后,小伙伴会发现如果上传连续上传同一张图片,并不会上传成功。是因为files对象中的value在上传完一张图片后并不会进行清楚。所以需要手动清空value

document.querySelector('#upload_ele').files.target.value = '';
// 可能由于获取元素的问题这里可能或获取不到元素,大家按照情况进行获取即可。

引入如下:

<upload-image class="upload" :quality='.7' :url="$base.uploadUrl" :hasApi="false" @upload='uploadImage'></upload-image>

参考文献

  1. js内置file(文件)对象。
  2. FormData对象的
  3. XMLHttpRequest对象
  4. Image对象
  5. Canvas画布。内容较多,建议通过学习视频了解。在本文中主要用于大型图片的压缩处理
  6. base64和Blob的转换,百度很多已经封装好的。可直接使用。eg:https://www.cnblogs.com/jiujiaoyangkang/p/9396043.html

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!