「HTML5」FileAPI 文件操做實戰

本文來自 《FileAPI 文件操做實戰》

其餘全部系列都放在了Github歡迎交流和Starjavascript

介紹

HTML5 爲咱們提供了 File API 相關規範。主要涉及 File 接口FileReader 對象html

本文整理了兼容性檢測、文件選擇、屬性讀取、文件讀取、進度監控、大文件分片上傳以及拖拽上傳等開發中常見的前端文件操做。前端

準備工做

首先,咱們的 File 來自於<input>標籤中選中的文件列表。因此,準備以下的 HTML 代碼:java

<input type="file" id="files" multiple />
<div id="list"></div>
<div id="images"></div>
<!-- File API相關操做寫在了script.js中 -->
<script src="./script.js"></script>

檢測兼容性

File 對象是特殊類型的 Blob。在 script 入口處,應該檢測當前瀏覽器是否支持 File API:git

if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
  throw new Error("當前瀏覽器對FileAPI的支持不完善");
}

監聽文件選擇

對於 type 爲 file 類型的<input>標籤,在選擇文件的時候,會觸發change事件。用戶選中的文件信息也會傳入回調函數的第一個參數中。github

function handleFileSelect(event) {
  const { files } = event.target;
  if (!files.length) {
    console.log("沒有選擇文件");
    return;
  }

  console.log("選中的文件信息是:", files);
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

文件屬性-File

event.target.files 是一個FileList對象,它是一個由File對象組成的列表。後端

每一個 File 對象,保存着選中的對應文件的屬性。經常使用的用:api

  • name:文件名
  • type:文件類型
  • size:文件大小

下面,經過 type 屬性,過濾掉非圖片類型的文件,只展現圖片類型文件的信息:瀏覽器

function handleFileSelect(event) {
  const { files } = event.target;
  if (!files.length) {
    console.log("沒有選擇文件");
    return;
  }

  const innerHTML = [];
  const reImage = /image.*/;

  for (let file of files) {
    if (!reImage.test(file.type)) {
      continue;
    }

    innerHTML.push(
      `
      <li>
        <strong>${file.name}</strong>
        (${file.type || "n/a"}) -
        ${file.size} bytes
      </li>
      `
    );
  }

  document.querySelector("#list").innerHTML = `<ul>${innerHTML.join("")}</ul>`;
}

讀取文件-FileReader

仍是以圖片讀取爲例,讀取而且顯示全部的圖片類型文件。app

文件讀取須要使用FileReader對象,它經常使用 3 個回調方法:

  • onload: 文件讀取完成
  • onloadstart:文件上傳開始
  • onprogress : 文件上傳中觸發

Image相似,在讀取文件以前,須要先綁定事件處理。它讀取操做有:readAsArrayBuffer、readAsDataURL、readAsBinaryString、readAsText。傳入的參數就是File對象。

那麼這幾個方法有什麼區別呢?不一樣的讀取方式,回調事件onload接受到的event.target.result不相同。好比,readAsDataURL讀取的話,result 是一個圖片的 url。

下面就是讀取圖片文件,而後展現的一個例子:

function handleFileSelect(event) {
  let { files } = event.target;
  if (!files.length) {
    return;
  }

  let vm = document.createDocumentFragment(),
    re = /image.*/,
    loaded = 0, // 完成加載的圖片數量
    total = 0; // 總共圖片數量

  // 統計image文件數量
  for (let file of files) {
    re.test(file.type) && total++;
  }

  // onloadstart回調
  const handleLoadStart = (ev, file) =>
    console.log(`>>> Start load ${file.name}`);
  // onload回調
  const handleOnload = (ev, file) => {
    console.log(`<<< End load ${file.name}`);

    const img = document.createElement("img");
    img.height = 250;
    img.width = 250;
    img.src = ev.target.result;
    vm.appendChild(img);

    // 完成加載後,將其放入dom元素中
    if (++loaded === total) {
      document.querySelector("#images").appendChild(vm);
    }
  };

  for (let file of files) {
    if (!re.test(file.type)) {
      continue;
    }

    const reader = new FileReader();
    reader.onloadstart = ev => handleLoadStart(ev, file);
    reader.onload = ev => handleOnload(ev, file);
    // 讀取文件對象
    reader.readAsDataURL(file);
  }
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

監控讀取進度

在監控讀取進度的時候,主要是處理 FileReader 對象上的 onprogress 事件。

下面的例子,請打開一個較大的文件來查看效果(不然一下就讀取完了):

function handleFileSelect(event) {
  let { files } = event.target;
  if (!files.length) {
    return;
  }

  const handleLoadStart = (ev, file) =>
    console.log(`>>> Start load ${file.name}`);
  const handleProgress = (ev, file) => {
    if (!ev.lengthComputable) {
      return;
    }
    // 計算進度,而且以百分比形式展現
    const percent = Math.round((ev.loaded / ev.total) * 100);
    console.log(`<<< Loding ${file.name}, progress is ${percent}%`);
  };

  for (let file of files) {
    const reader = new FileReader();
    reader.onloadstart = ev => handleLoadStart(ev, file);
    reader.onprogress = ev => handleProgress(ev, file);
    reader.readAsArrayBuffer(file);
  }
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

大文件分片讀取

在對於超大文件,通常採用分片上傳的思路解決。文章開頭有講到,File 是 Blob 的一個特例。而 Blob 上有一個slice 方法,經過它,前端就能夠實現分片讀取大文件的操做。

爲了方便說明,請先準備好一個 txt 文件,文件內容就是:hello world

示例代碼以下,代碼中只讀取前 5 個字節,因爲每一個英文字母佔 1 個字節,因此打印結果應該是「hello」。

function handleFileSelect(event) {
  let { files } = event.target;
  if (!files.length) {
    return;
  }
  // 爲了方便說明,這裏僅僅讀取第一個文件
  const file = files[0];
  // 讀取前5個字節的內容
  const blob = file.slice(0, 5);

  const reader = new FileReader();
  // 控制檯輸出結果應該是:hello
  reader.onload = ev => console.log(ev.target.result);
  reader.readAsText(blob);
}

document
  .querySelector("#files")
  .addEventListener("change", handleFileSelect, false);

拖拽上傳

和前面所述的 File API 相關是徹底同樣的。惟一須要特殊處理的是文件對象的獲取入口改變了。對於<input>標籤,監聽 onchange 事件,FileList 存放在 event.target.files 中;對於拖拽操做,FileList 存放在拖拽事件的回調函數參數裏,經過 event.dataTransfer.files 訪問便可。

須要修改一下 html 代碼:

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <style>
    #container {
      width: 300px;
      height: 300px;
      border: 3px dotted red;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script src="./script.js"></script>
</body>
</html>

腳本文件的代碼以下:

function handleDropover(event) {
  event.stopPropagation();
  event.preventDefault();
}

function handleDrop(event) {
  event.stopPropagation();
  event.preventDefault();
  /***** 訪問拖拽文件 *****/
  const files = event.dataTransfer.files;
  console.log(files);
  /**********/
}

const target = document.querySelector("#container");
target.addEventListener("dragover", handleDropover);
target.addEventListener("drop", handleDrop);

後端相關

後端相關超出了本文的討論範圍,能夠參考這篇文章

相關文章
相關標籤/搜索