文件上传功能


1.Blob

blob数据类型以随机存取块的形式存储任何种类的二进制数据;二进制数据通常由已保存的电子表格、程序装入模块和数字化声音模式等等组成,也就是说它通常用于存储大文件,通常用来读写文件,比如一个图片文件的内容就可以通过Blob 对象读写

在Web中,Blob类型的对象表示不可变的类似文件对象的原始数据,通俗点说,就是Blob对象是二进制数据,但它是类似文件对象的二进制数据,因此可以像操作File对象一样操作Blob对象,实际上,File继承自Blob(文件对象)。

构造函数

  • array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings 会被编码为 UTF-8。

  • options(可选)

    它可能会指定如下两个属性:

    • type,默认值为 "",它代表了将会被放入到 blob 中的数组内容的 MIME 类型。
    • endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。它是以下两个值中的一个:"native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持 blob 中保存的结束符不变 非标准
let blob = new Blob([typedArray.buffer], {type: 'application/octet-stream'}); // 传入一个合适的 MIME 类型
let blob2 = new Blob(["image/jpeg"],{type:"application/json"})

使用字符串构造一个blob对象

var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug)], {type : 'application/json'});
  • 第一个参数是数组,数组元素可以是字符串或二进制文件,用来表示Blob实例对象的数据内容。
  • 第二个参数是配置对象,但是目前该对象只有一个type属性可提供配置,它的值是一个字符串,表示数据的MIME类型,默认是空字符串,关于MIME类型分类,可以参照以下这张表:

blob2url

此时前端可以将Blob数据转换为url下载地址

export function downBlob(blobData: Blob, fileName: string) {
  // 下载地址
  const downUrl = window.URL.createObjectURL(blobData);
  const a = document.createElement('a');
  a.style.display = 'none';
  a.href = downUrl;
  a.target = '_blank';
  if (fileName) {
    a.download = fileName;
  }
  document.body.appendChild(a);
  a.click();
  a.remove();
  window.URL.revokeObjectURL(downUrl); // 释放掉blob对象
}

url2blob

对于canvas生成的base64 url,也就是Data URL 格式的图片数据,为了进一步减少传输的数据量,我们可以把它转换为 Blob 对象,再传给服务端

function dataUrlToBlob(base64, mimeType) {
  let bytes = window.atob(base64.split(",")[1]);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}

URL.createObjectURL

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。

  • createObjectURL返回一段带hash的url,并且一直存储在内存中,直到document触发了unload事件(例如:document close)或者执行revokeObjectURL来释放。

File

文件(File)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。

通常情况下, File 对象是来自用户在一个 input 元素上选择文件后返回的 FileList 对象,也可以是来自由拖放操作生成的 DataTransfer 对象,或者来自 HTMLCanvasElement 上的 mozGetAsFile() API。

File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。

File()

FormData

FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

它的用法和 map 有点像

构造函数

  • FormData()

    创建一个新的 FormData 对象。

方法

  • FormData.append()

    FormData 中添加新的属性值,FormData 对应的属性值存在也不会覆盖原值,而是新增一个值,如果属性不存在则新增一项属性值。

  • FormData.delete()

    从 FormData 对象里面删除一个键值对。

  • FormData.entries()

    返回一个包含所有键值对的iterator对象。

  • FormData.get()

    返回在 FormData 对象中与给定键关联的第一个值。

  • FormData.getAll()

    返回一个包含 FormData 对象中与给定键关联的所有值的数组。

  • FormData.has()

    返回一个布尔值表明 FormData 对象是否包含某些键。

  • FormData.keys()

    返回一个包含所有键的iterator对象。

  • FormData.set()

    FormData 设置属性值,如果FormData 对应的属性值存在则覆盖原值,否则新增一项属性值。

  • FormData.values()

    返回一个包含所有值的iterator对象。

2.选择图片上传

在元素方面,我们可以选择 input

<input type="file" />
<input type="file" accept="image/*"> 
<input type="file" accept="image/gif,image/jpeg,image/jpg,image/png" multiple />

通过设置其 multiple 属性和 accept 属性可以使得 input 标签支持多选和仅支持选择 gifjpg/jpegpng 类型的文件

然后可以对input读取到的数据,采用

2种方式

  • URL.createObjectURL(file),得到图片的url,由于可以获取到每张图片的 File 对象,则可以使用 URL.createObjectURL() 创建一个对象 URL,可以作为 img 标签的 src 值进行传入,则能实现本地图片的预览功能。

    需要注意的是,当不再需要这些使用 URL.createObjectURL() 创建的 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放

    const localUrl = URL.createObjectURL(flieList[0])
    // localUrl 可作为图片的源
    <img src={localUrl} alt='' />
    
    // 无需使用时释放内存
    window.URL.revokeObjectURL(localUrl);
  • 创建一个FileReader对象,监听它的load事件

    $inputFile.addEventListener('change', function() {
     const file = this.files[0];
     const reader = new FileReader();
     reader.addEventListener('load', function() {
       $previewImage.src = reader.result;
     }, false);
    
     if(file) {
       reader.readAsDataURL(file);
     }
    }

上传

前端要实现图片上传的原理就是通过构建FormData对象,把文件对象append()到该对象,然后挂载在XMLHttpRequest对象上 send() 到服务端。(如果是得到本地url可以通过

fetch(data.url).then((resp) => {
  console.log(resp);
  return resp.blob();
}).then((b) => {
  console.log('---- bb', b);
  const fd = new FormData();
  fd.append('file', b);
  console.log(fd, 'fd');
});

得到blob数据格式)

function uploadFile() {
  //这里是用cropperjs对图片进行裁剪
  cropperInstance.getCroppedCanvas().toBlob(function(blob) {
    const formData = new FormData();
    formData.append('avatar', blob);
    fetch('xxxx', {
      method: 'POST',
      body: formData
    });
  });
}

3.文件上传

文件上传实质上和图片上传差不多,都是使用input获取文件

<div>
  <input
         onChange={(v)=>this.handleChange(v)}
  type="file"
  size={this.state.size}
  name="fileSelect"
  accept="image/*"
  multiple={this.state.multiple} />
  <span ref="dragBox"
        onDragOver={(e)=>this.handleDragHover(e)}
    onDragLeave={(e)=>this.handleDragHover(e)}
    onDrop={(e)=>this.handleDrop(e)}
    className="upload-drag-area">
    或者将图片拖到此处
  </span>
</div>

处理图片上传的函数handleChange

e.preventDefault()
let target = event.target
let files = target.files
let count = this.state.multiple ? files.length : 1
for (let i = 0; i < count; i++) {
    files[i].thumb = URL.createObjectURL(files[i])
}
// 转换为真正的数组
files = Array.prototype.slice.call(files, 0)
// 过滤非图片类型的文件
files = files.filter(function (file) {
    return /image/i.test(file.type)
})

处理拖拽进来的文件:

handleDrop(e) {
    this.setState({progress:[]})
    this.handleDragHover(e)
    // 获取文件列表对象
    let files = e.target.files || e.dataTransfer.files
    let count = this.state.multiple ? files.length : 1
    for (let i = 0; i < count; i++) {
        files[i].thumb = URL.createObjectURL(files[i])
    }
    // 转换为真正的数组 
    files = Array.prototype.slice.call(files, 0)
    // 过滤非图片类型的文件
    files = files.filter(function (file) {
        return /image/i.test(file.type)
    })
    this.setState({files: this.state.files.concat(files)})
}

上传函数

upload(file, idx) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest()
        if (xhr.upload) {
            // 上传中
            xhr.upload.addEventListener("progress", (e) => {
                // 处理上传进度
                this.handleProgress(file, e.loaded, e.total, idx);
            }, false)
            // 文件上传成功或是失败
            xhr.onreadystatechange = (e) => {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                    // 上传成功操作
                    this.handleSuccess(file, xhr.responseText)
                    // 把该文件从上传队列中删除
                    this.handleDeleteFile(file)
                    resolve(xhr.responseText);
                 } else {
                    // 上传出错处理 
                    this.handleFailure(file, xhr.responseText)
                    reject(xhr.responseText);
                 }
            }
        }
        // 开始上传
        xhr.open("POST", this.state.uri, true)
        let form = new FormData()
        form.append("filedata", file)
        xhr.send(form)
    })
}

只是上看了好多篇文章,他们对于上传图片的请求好多都是直接用原生xhr来写,而且原声xhr也有自带的上传终止功能(abort)

修改请求头

在上传的时候如果请求的数据格式有点问题,可以调整一下请求头再发请求,比如:

let config = { 
  headers:{'Content-Type':'multipart/form-data'} 
}; //添加请求头 
this.axios.post('http://upload.qiniu.com/',param,config) 
  .then(response=>{ 
  console.log(response.data); 
})

参考:

前端图片上传那些事儿

React 实现图片上传和展示功能

基于Node的React图片上传组件实现


文章作者: Hello
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hello !
 上一篇
一些好用的网站 一些好用的网站
Example以下部分网站分享均来源于老陈打码的网站推荐: 图标 动画图标库 https://loading.io/ css 动画css代码在线演示(可以直接cv动画代码) https://xsgames.co/animatiss/
2022-08-23
下一篇 
React(下) React(下)
10.HooksReact的Hook 是 16.8版本新增的特性/语法,可以让我们在函数式组件中使用state和其他React特性 函数式组件最主要没有上述功能的原因是没有实例对象,没有this(现在函数式组件是主流了) Hook主要是践行
2022-07-02
  目录