Posts 前端本地文件操作与上传
Post
Cancel

前端本地文件操作与上传

前端与用户的文件交互需要由用户触发,用户可以通过三种方式触发文件上传。

原文地址:https://juejin.im/post/5a193b4bf265da43052e528a

用户可通过以下三种方式操作触发:

  • 通过 input type=”file” 选择本地文件
  • 通过拖拽的方式把文件拖过来
  • 在编辑框里面复制粘贴

通过 input type=”file” 选择本地文件

FormData

通常还会自定义一个按钮,然后盖在它上面,因为 type=”file”的 input 不好改变样式

1
2
3
<form>
  <input type="file" id="file-input" name="fileContent" />
</form>
1
2
3
4
5
6
$("#file-input").on("change", function () {
  console.log(`file name is ${this.value}`);
  let formData = new FormData(this.form);
  formData.append("fileName", this.value);
  console.log(formData);
});

结果:

  • 文件的路径是一个假的路径,也就是说在浏览器无法获取到文件的真实存放位置
  • FormData 打印出来是一个空的 Objet,但并不是说它的内容是空的,只是它对前端开发人员是透明的,无法查看、修改、删除里面的内容,只能 append 添加字段

FileReader

FormData 无法得到文件的内容,而使用 FileReader 可以读取整个文件的内容。用户选择文件之后,input.files 就可以得到用户选中的文件

1
<input type="file" id="uploadBtn"></input>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const uploadBtn = document.getElementById("uploadBtn");
uploadBtn.onchange = (e) => {
  readFile(e.srcElement.files[0]);
};
function readFile(file) {
  const reader = new FileReader();
  const fileType = file.type;
  // 按 base64 的方式读取
  // reader.readAsDataURL(file);
  // 以原始二进制方式读取,读取结果可直接转成整数数组
  // reader.readAsArrayBuffer(file);
  reader.readAsDataURL(file);
  reader.onload = function (evt) {
    if (/^image\/[jpeg|png|gif]/.test(fileType)) {
      console.log(evt.target.result);
    }
  };
}

通过拖拽的方式把文件拖过来

设置一个可接受区域

1
<div class="img-container">drop your image here</div>

然后监听它的拖拽事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(".img-container")
  .on("dragover", function (event) {
    event.preventDefault();
  })
  .on("drop", function (event) {
    event.preventDefault();
    // 数据在event的dataTransfer对象里
    let file = event.originalEvent.dataTransfer.files[0];

    // 然后就可以使用FileReader进行操作
    FileReader.readAsDataURL(file);

    // 或者是添加到一个FormData
    let formData = new FormData();
    formData.append("fileContent", file);
  });

在编辑框里面复制粘贴

通常是在一个编辑框里操作,如把 div 的 contenteditable 设置为 true

1
<div contenteditable="true">hello, paste your image here</div>

粘贴的数据是在 event.clipboardData.files

1
2
3
$("#editor").on("paste", function (event) {
  let file = event.originalEvent.clipboardData.files[0];
});

但是 Safari 的粘贴不是通过 event 传递的,它是直接在输入框里面添加一张图片:

01.png

它新建了一个 img 标签,并把 img 的 src 指向一个 blob 的本地数据。blob 是一种类文件的存储格式,它可以存储几乎任何格式的内容,如 json:

1
2
let data = { hello: "world" };
let blob = new Blob([JSON.stringify(data)], { type: "application/json" });

为了获取本地的 blob 数据,我们可以用 ajax 发个本地的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$("#editor").on("paste", function (event) {
  // 需要setTimeout 0等图片出来了再处理
  setTimeout(() => {
    let img = $(this).find("img[src^='blob']")[0];
    console.log(img.src);
    // 用一个xhr获取blob数据
    let xhr = new XMLHttpRequest();
    xhr.open("GET", img.src);
    // 改变mime类型
    xhr.responseType = "blob";
    xhr.onload = function () {
      // response就是一个Blob对象
      console.log(this.response);
    };
    xhr.send();
  }, 0);
});

blob 打印出来是这样的

02.png

和 File 一样,可以使用 FileReader 读取它的内容

1
2
3
4
5
6
7
8
9
10
function readBlob(blobImg) {
  let fileReader = new FileReader();
  fileReader.onload = function () {
    console.log(this.result);
  };
  fileReader.onerror = function (err) {
    console.log(err);
  };
  fileReader.readAsDataURL(blobImg);
}

还能使用 window.URL 读取,这是一个新的 API,经常和 Service Worker 配套使用,因为 SW 里面常常要解析 url

1
2
3
4
5
6
7
8
function readBlob(blobImg) {
  let urlCreator = window.URL || window.webkitURL;
  // 得到base64结果
  let imageUrl = urlCreator.createObjectURL(blobImg);
  return imageUrl;
}

readBlob(this.response);

发送数据

我们使用了三种方式获取文件内容,最后得到:

  • FormData 格式
  • FileReader 读取得到的 base64 或者 ArrayBuffer 二进制格式

FormData 格式

如果直接就是一个 FormData 了,那么直接用 ajax 发出去就行了,不用做任何处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
let form = document.querySelector("form"),
  formData = new FormData(form);

formData.append("fileName", "photo.png");

$.ajax({
  url: "/upload",
  type: "POST",
  data: formData,
  // 因为jQuery会自动把内容做一些转义,并且根据data自动设置请求mime类型,这里告诉jQuery直接用xhr.send发出去就行了。
  processData: false, // 不处理数据
  contentType: false, // 不设置内容类型
});

base64

使用比较多的应该是 base64,因为前端经常要处理图片,读取为 base64 之后就可以把它画到一个 canvas 里面,然后就可以做一些处理,如压缩、裁剪、旋转等。最后再用 canvas 导出一个 base64 格式的图片。

可以把 base64 转化成 blob,然后再 append 到一个 formData 里面,下面的函数(来自 b64-to-blob)可以把 base64 转成 blob:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || "";
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, { type: contentType });
  return blob;
}

然后就可以 append 到 formData 里面:

1
2
3
let blob = b64toBlob(b64Data, "image/png"),
  formData = new FormData();
formData.append("fileContent", blob);
This post is licensed under CC BY 4.0 by the author.
Trending Tags
Contents

Trending Tags