iT邦幫忙

2024 iThome 鐵人賽

DAY 9
3
JavaScript

可愛又迷人的 Web API系列 第 9

Day9. File API 介紹與實際應用

  • 分享至 

  • xImage
  •  

大家應該都碰過處理文件的需求,不管是上傳圖片、讀取電腦的文件或是從網站下載文件... 等,多少都已經和 File API 打過交道了。今天想跟大家分享的是 File API 的核心概念,還有 FileReader、Blob 物件的功用,以及一些實際範例。

File API 的構造

File API 可以讓網頁讀寫我們本地端的文件,也能對文件進行操作,且無須伺服器端的介入。Web API 包含以下幾個部分:

  1. File 和 FileList
  2. FileReader
  3. Blob
  4. URL.createObjectURL()

File 和 FileList

我們可以透過 <input type="file"> 處理上傳的文件,並取得 File 的物件屬性。以下是 File 的主要屬性:

  • name:文件名稱
  • size:文件大小 (單位: Byte)
  • type:文件類型
  • lastModified:最後修改時間

上傳文件的按鈕

<input type="file" id="fileInput" />

上傳文件後,使用 event.target.files 得到文件的相關資訊

const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  console.log(file);
});

這就是我們取得的內容

https://mukiwu.github.io/web-api-demo/img/9-1.png

FileReader

FileReader 顧名思義,就是用來讀文件的,他可以將文件轉為 Text、ArrayBuffer,或是 Data Url,以下是主要方法:

  • readAsText():將文件讀取為 Text
  • readAsArrayBuffer():將文件讀取為 ArrayBuffer
  • readAsDataURL():將文件讀取為 Data URL
  • abort():中止讀取操作

我們先建立一個 FileReader

const reader = new FileReader()

再用這個 reader 來讀取文件,並轉成想要的格式。

const fileInput = document.getElementById('fileInput');
const reader = new FileReader();

fileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  // 用你想要的讀取方法,來讀取 file
  reader.readAsText(file);
});

reader.onload = (event) => {
  console.log('文件', event.target.result);
};

reader.onerror = (error) => {
  console.error('讀取文件失敗', error);
};

readAsText()

readAsText() 適合讀取與處理文件,例如 CSV、JSON、XML 和 TXT...等等

⬇️ reader.readAsText(file)

https://mukiwu.github.io/web-api-demo/img/9-2.png

可以再將讀取來的資料進一步解析

reader.onload = (event) => {
  console.log('文件', event.target.result);

  const text = event.target.result;
  console.log('文件內容', text);
  // 解析 JSON 文件
  try {
    const data = JSON.parse(text);
    console.log('解析後的數據', data);
  } catch (error) {
    console.error('無法解析 JSON 文件', error);
  }
}

https://mukiwu.github.io/web-api-demo/img/9-5.png

readAsArrayBuffer()

ArrayBuffer 是二進制的數據結構,他有點像是「容器」,用來儲存二進制原始數據,如果有需要,我們可以用一些方法(如 DataView) 來操作這些數據。

⬇️ reader.readAsArrayBuffer(file)

https://mukiwu.github.io/web-api-demo/img/9-3.png

使用 DataView 處理 ArrayBuffer() 的數據

假設我想要偵測使用者上傳的圖片格式為何?這時候就能利用 DavaView 來操作 ArrayBuffer() 取得資料。

先寫一個檢查圖片格式的函式

function checkImageFormat(view) {
  // JPEG 文件的前兩個 byte 應該是 0xFFD8
  const jpegMarker = view.getUint16(0);
  if (jpegMarker === 0xFFD8) {
    return 'JPEG';
  }

  // GIF 文件的前兩個字節應該是 0x4749
  const gifMarker = view.getUint16(0);
  if (gifMarker === 0x4749) {
    return 'GIF';
  }

  // PNG 文件的前八個字節應該是 0x89504E470D0A1A0A
  const pngMarker = view.getUint32(0) === 0x89504E47 && view.getUint32(4) === 0x0D0A1A0A;
  if (pngMarker) {
    return 'PNG';
  }
  return '未知格式';
}

reader.onload() 呼叫函式,並顯示回傳結果

reader.onload = (event) => {
  const arrayBuffer = event.target.result;
  // 建立 DataView 實例
  const view = new DataView(arrayBuffer)
  // 使用 checkImageFormat() 取得圖片格式
  const format = checkImageFormat(view);
  console.log('圖片格式:', format);
}

上傳圖片後,就可以在 console 取得圖片的格式了

readAsDataUrl()

readAsDataURL() 在處理上傳圖片時很常用到。它可以將圖片轉換成 base64 格式,並直接在網頁上顯示圖片,而不需要讀取圖片的 URL。這樣不需等待圖片上傳或從伺服器讀取,顯示速度更快且更安全。

⬇️ reader.readAsDataUrl(file)

https://mukiwu.github.io/web-api-demo/img/9-4.png

Blob

接著是 Web API 的第三個部分:Blob。Blob (Binary Large Object) 代表一個不可變的、原始資料的類似檔案的物件。可以用來存放圖片、影片等多種格式的資料,並且可以用多種方法進行操作。我們通常會用 Blob 處理二進制數據以及建立新的文件。

Blob 有兩個常用的方法:

  • slice():建立一個新的 Blob 物件
  • stream():返回一個 ReadableStream,用於讀取 Blob 內容

以下是使用這兩個方法的程式碼範例

const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', async function (event) {
  const file = event.target.files[0];
  const reader = new FileReader();
  reader.readAsDataURL(file);

  // 使用 Blob 來處理文件
  const blob = new Blob([file], { type: file.type });
  console.log('blob', blob)

  // 使用 slice() 方法
  const sliceBlob = blob.slice(0, blob.size / 2);
  console.log('sliceBlob', sliceBlob);

  // 使用 stream() 方法
  const stream = blob.stream();
  const streamReader = stream.getReader();
  const { value } = await streamReader.read();
  console.log('Stream chunk:', value);
});

範例程式碼

線上範例網址:https://mukiwu.github.io/web-api-demo/file-blob.html

URL.createObjectURL()

我們可以用 URL.createObjectURL() 方法建立一個特定物件的 URL,用於在網站顯示本機圖片,或提供文件下載。

前面段落分享了使用 Web API 建立 File 或 Blob Object,而 URL.createObjectURL() 的用途,就是能幫這些 File / Blog Object 產生一個臨時的 URL,用於 HTML DOM,例如 <img><a>,以便在網頁上顯示本地圖片或提供文件下載。這種方式避免了將文件直接上傳到伺服器,可以加快響應速度,也能減輕伺服器負擔。

圖片預覽範例

最常見的一個實作,就是在使用者上傳圖片時,顯示圖片的預覽畫面。

首先準備一個 <input type="file /> 以及一個 <img />的預覽框

<input type="file" id="fileInput" />
<img id="preview" src="" alt="Image preview" style="display:none;" />

上傳圖片後,用 createObjectURL() 產生一個臨時的 url,並且用 <img src="" /> 引入這個 url

const fileInput = document.getElementById('fileInput');
const img = document.getElementById('preview');

fileInput.addEventListener('change', async function (event) {
  const file = event.target.files[0];
  const imgURL = URL.createObjectURL(file);
  // 將 createObjectURL 產生的 URL 放入 <img />
  img.src = imgURL;
  img.style.display = 'block';
});

如此就能在上傳後,直接顯示預覽的圖片,而且不需要經過後端伺服器唷

https://mukiwu.github.io/web-api-demo/img/9-6.png

範例程式碼

線上範例網址:https://mukiwu.github.io/web-api-demo/file-objurl.html

File API 的實際應用範例

相信大家對 File API 的構造與方法有了一定的了解,接下來分享幾個我們比較常用的範例,整合了前面段落提到的各種方法,相信大家接下來再看這些程式碼,會更了解他們的用途。

範例一:將大檔拆分成一個個小區塊,並逐塊上傳

常見的做法是使用「chunk」概念,將大檔案拆分成一個個小區塊,然後分批上傳到伺服器。這種方法可以加快響應時間,並降低上傳失敗的風險。

我們可以使用 Blob.slice() 幫我們分割檔案,並逐塊上傳

// 設定 chunk 的大小是 1MB,也就是每 1MB 會拆成一個小塊
const CHUNK_SIZE = 1024 * 1024;
const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', async function (event) {
  const file = event.target.files[0];
  uploadFile(file)
});


// 重點函式
function uploadFile(file) {
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  let currentChunk = 0;

  function uploadNextChunk() {
    const start = currentChunk * CHUNK_SIZE;
    const end = Math.min(file.size, start + CHUNK_SIZE);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('file', chunk, file.name);
    formData.append('chunk', currentChunk);
    formData.append('totalChunks', totalChunks);

    // 模擬上傳
    setTimeout(() => {
      console.log(`上傳 chunk ${currentChunk + 1}/${totalChunks}:`, chunk);

      currentChunk++;
      if (currentChunk < totalChunks) {
        uploadNextChunk();
      } else {
        console.log('文件上傳完成');
      }
    }, 500); // 模擬網絡延遲
  }

  uploadNextChunk();
}

可以透過 console.log() 看到上傳的檔案大小與總數量

https://mukiwu.github.io/web-api-demo/img/9-7.png

範例二:從網站產生下載文件

也能不經由伺服器,直接透過網站讓使用者下載文件,使用 Blob() 以及 createObjectURL()

<button onclick="generateAndDownloadFile()">下載檔案</button>
function generateAndDownloadFile() {
  const content = '這是你的文件內容:HELLO, world.';
  const blob = new Blob([content], { type: 'text/plain' });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = 'download.txt';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);

  URL.revokeObjectURL(url);
}

範例程式碼

我將範例一和範例二合併在同一份檔案中。

這是線上範例網址:https://mukiwu.github.io/web-api-demo/file-objurl.html

範例三:拖放與上傳檔案

還記得我們在前面的文章有分享過 Drag and Drop API 嗎?我們還能結合這個 API 做到拖放上傳檔案的功能唷!

<style>
  img {
    max-width: 100%;
  }

  .drag-over-yellow {
    background-color: yellow;
  }

  .drop-zone {
    height: 300px;
    width: 300px;
    border: 1px solid black;
    margin: 10px;
    padding: 10px;
  }
</style>

<div id="drop-zone" class="drop-zone">放置區域</div>

監聽 drop 事件,將處理檔案的邏輯寫在裡面

const dropZone = document.getElementById('drop-zone');

dropZone.addEventListener('dragover', (event) => {
  event.preventDefault();
});

dropZone.addEventListener('dragenter', (event) => {
  // 可以多加一層判斷,如果是檔案,拖曳區域才會變色
  if (event.dataTransfer.items && event.dataTransfer.items[0].kind === 'file') {
    event.target.classList.add('drag-over-yellow');
  }
});

dropZone.addEventListener('dragleave', (event) => {
  event.target.classList.remove('drag-over-yellow');
});

dropZone.addEventListener('drop', (event) => {
  event.preventDefault();
  event.target.classList.remove('drag-over-yellow');
  // 取得 file 後,再用 createObjectURL() 顯示預覽圖片
  const file = event.dataTransfer.files[0];
  if (file) {
    const imgURL = URL.createObjectURL(file);
    const img = document.createElement('img');
    img.src = imgURL;
    dropZone.innerHTML = '';
    dropZone.appendChild(img);
  }
});

https://mukiwu.github.io/web-api-demo/img/9-8.gif

範例程式碼

線上範例網址:https://mukiwu.github.io/web-api-demo/file-drag.html

小結

原本以為 File API 相對簡單,篇幅應該很短,沒想到寫完居然爆字數 XDD。

也希望透過這篇的介紹,能讓不熟悉的朋友,對 File API 有進一步的了解,有任何問題都歡迎留言討論唷。


上一篇
Day8. Web Notifications API 結合 Google Cloud 的應用
下一篇
Day10. 用 Battery Status API 打造節能模式的網站
系列文
可愛又迷人的 Web API20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Sunny.Cat
iT邦新手 4 級 ‧ 2024-09-17 20:00:29

好耶😆這篇好實用👍👍

我要留言

立即登入留言