HTML5 神奇的 Object URL:不用後端,前端便能產生獲取指定物件的網址

用過 Twitter、Facebook 的人一定很熟悉一個使用情境

在更換大頭照或封面照片時,會先讓你預覽更換後的樣子,如果覺得不滿意可以取消或再次更換

這功能你會怎麼實作?

通常傳統方式的實作方式是:

  1. 實作一個後端 API(如:POST /avatar),負責上傳照片,並回傳此照片的網址,且此照片不能覆蓋掉原本照片
  2. 前端發出 POST /avatar 的 request,等到 request 成功後,知道此照片網址,放進<img>的 “src” attribute 中,便可讓使用者預覽

看起來這樣就結束了

BUT

這衍生出以下幾個問題:

  • 耗時:每一次的預覽,都要重新上傳照片,當然非常耗時。
  • 處理預覽照片:預覽照片如何清掉?是要不管?還是用 cron job 定期處理?
  • 預覽與完成:預覽所使用的 API 與完成所使用的 API,基本上是一樣的,只是差在是否要覆蓋檔案或者是否要修改資料庫。所以可能會有以下幾種方式實作
  1. Request body 多個 flag,讓後端知道這是預覽還是完成
  2. 不同的 API,share 大部分的邏輯
  3. 確定與取消各發出不同的 request,讓後端去處理

以上這幾種實作方式,其實都需要花時間額外處理

而且不管如何,使用者完成一次更新照片都至少需要發出 2 次 requests:1 次是預覽,1 次是確定更新

有沒有其他實作方式讓使用者可以預覽照片,但只在他確定更新照片時才上傳照片,這樣只需要發出 1 次 request,而且還不用處理之前預覽的照片?

HTML5 的 Object URL 便能達到這件事

Object URL is a URL representing a object. The URL lifetime is tied to the document in the window on which it was created. The new object URL represents the specified File object or Blob object. — MDN
objectURL = URL.createObjectURL(object);
// object: A File, Blob, or MediaStream object to create an object URL for.
// objectURL is like: blob:https://<FQDN>/<UUID>

把 objectURL 放置 <img> “src” attribute 中,便可預覽「上傳」的東西囉

完全不會牽扯到後端,而且速度也非常的快

透過此方式「上傳」的 object,是放在瀏覽器的記憶體裡面

所以當網頁離開,這些「上傳」的 objects 都會被釋放,達到清除預覽照片的效果

以下是完整預覽照片的範例(from MDN):

  • HTML
<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<a href="#" id="fileSelect">Select some files</a>
<div id="fileList">
<p>No files selected!</p>
</div>
  • JavaScript
window.URL = window.URL || window.webkitURL;

var fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
fileList = document.getElementById("fileList");

fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);

function handleFiles(files) {
if (!files.length) {
fileList.innerHTML = "<p>No files selected!</p>";
} else {
fileList.innerHTML = "";
var list = document.createElement("ul");
fileList.appendChild(list);
for (var i = 0; i < files.length; i++) {
var li = document.createElement("li");
list.appendChild(li);

var img = document.createElement("img");
img.src = window.URL.createObjectURL(files[i]);
img.height = 60;
img.onload = function() {
window.URL.revokeObjectURL(this.src);
}
li.appendChild(img);
var info = document.createElement("span");
info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
li.appendChild(info);
}
}
}

另外,Twitter 的「更新大頭照」與「更新封面照片」便是利用 Object URL 達成的,有興趣的人可以去玩玩看

Object URL 除了可以運用在預覽照片之外,也可以用來下載檔案

如果檔案內容是跟使用者的 input 有關,如:畫圖(canvas),或是這檔案其實是透過處理而產出,且這處理希望透過前端處理

便可以使用 Object URL 來實作,而且已經有人寫了一個 Library 來處理這件事:https://github.com/eligrey/FileSaver.js

最後,Object URL 是 HTML5 的新標準,所以服用前,請先詳閱公開說明書

Reference