H5直播系列一 Blob File FileReader URL

參考
[HTML5] Blob對象
理解DOMString、Document、FormData、Blob、File、ArrayBuffer數(shù)據(jù)類型
HTML5 File API — 讓前端操作文件變的可能

一、Blob與ArrayBuffer

jsmpeg系列一 基礎知識 字節(jié)序 ArrayBuffer TypedArray中介紹了處理二進制數(shù)組ArrayBuffer,現(xiàn)在來看一下Blob。Blob是Binary large object的縮寫,它與ArrayBuffer的區(qū)別是除了raw bytes以外它還提供了mime type作為元數(shù)據(jù)。但它依然是無法直接被讀寫的??匆幌聵嬙旌瘮?shù):

var aBlob = new Blob( array[, options])
// array可以為ArrayBuffer、ArrayBufferView、Blob、DOMStrings這些,或者他們的各種組合
// options: 包含type(如:MIME類型)和ending。

ArrayBuffer vs Blob and XHR2中有如下回復:

Blobs (according to spec anyway) have space for a MIME and easier to put into the HTML5 file API than other formats (it's more native to it).
You would use an ArrayBuffer when you need a typed array because you intend to work with the data, and a blob when you just need the data of the file

Binary World:ArrayBuffer、Blob以及他們的應用中有描述:

ArrayBuffer似乎是Blob的底層,Blob內部使用了ArrayBuffer。并且構造好的一個Blob實體就是一個raw data。既然用途差不多,那為什么一個Blob一個ArrayBuffer呢?當然,設計Blob和ArrayBuffer的目的是不同的。因為ArrayBuffer更底層,所以它專注的是細節(jié),比如說按字節(jié)讀寫文件。相反,Blob更像一個整體,它不在意細節(jié):就是那么一個原始的Binary Data,你只要來回傳輸就行了。

二、Blob構造方式

1、創(chuàng)建一個裝填DOMString對象的Blob對象


image.png

2、創(chuàng)建一個裝填ArrayBuffer對象的Blob對象


image.png

3、創(chuàng)建一個裝填ArrayBufferView對象的Blob對象(ArrayBufferView可基于ArrayBuffer創(chuàng)建,返回值是一個類數(shù)組。如下:創(chuàng)建一個8字節(jié)的ArrayBuffer,在其上創(chuàng)建一個每個數(shù)組元素為2字節(jié)的“視圖”)
image.png

4.通過Blob.slice()
此方法返回一個新的Blob對象,包含了原Blob對象中指定范圍內的數(shù)據(jù)

Blob.slice(start:number, end:number, contentType:string)
start:開始索引,默認為0
end:截取結束索引(不包括end)
contentType:新Blob的MIME類型,默認為空字符串
image.png

5.通過canvas.toBlob()

var canvas = document.getElementById("canvas");
canvas.toBlob(function(blob){
    console.log(blob);
});
image.png
三、DOMString

跟著XMLHttpRequest闖南走北很多年,看名字似乎很囂張且高深莫測。實際上,在JavaScript中,DOMString就是String。規(guī)范解釋說DOMString指的是UTF-16字符串,而JavaScript正是使用了這種編碼的字符串,因此,在Ajax中,DOMString就等同于JS中的普通字符串。

大家應該都與XMLHttpRequest中數(shù)據(jù)返回屬性之responseText打過交道吧,按照我的理解,這廝就是與DOMString數(shù)據(jù)類型發(fā)生關系的,表明返回的數(shù)據(jù)是常規(guī)字符串。

四、File

FileList 對象針對表單的 file 控件。當用戶通過 file 控件選取文件后,這個控件的 files 屬性值就是 FileList 對象。它在結構上類似于數(shù)組,包含用戶選取的多個文件。如果 file 控件沒有設置 multiple 屬性,那么用戶只能選擇一個文件,F(xiàn)ileList 對象也就只有一個元素了。

<input type='file' multiple />
<script>
    document.querySelector('input').onchange = function() {
      console.log(this.files);
    };
</script>

比如我選擇了兩個文件,控制臺打?。?/p>

FileList {0: File, 1: File, length: 2}
0: File
1: File
  length:2
__proto__: Object
image.png
  • name:文件名,該屬性只讀。
  • size:文件大小,單位為字節(jié),該屬性只讀。
  • type:文件的 MIME 類型,如果分辨不出類型,則為空字符串,該屬性只讀。
  • lastModified:文件的上次修改時間,格式為時間戳。
  • lastModifiedDate:文件的上次修改時間,格式為 Date 對象實例。
五、FileReader

FileReader 的實例擁有 4 個方法,其中 3 個用以讀取文件,另一個用來中斷讀取。下面的表格列出了這些方法以及他們的參數(shù)和功能,需要注意的是 ,無論讀取成功或失敗,方法并不會返回讀取結果,這一結果存儲在 result屬性中。

方法名 參數(shù) 描述
abort none 中斷讀取
readAsBinaryString file 將文件讀取為二進制碼
readAsDataURL file 將文件讀取為 DataURL
readAsText file, [encoding] 將文件讀取為文本

readAsText:該方法有兩個參數(shù),其中第二個參數(shù)是文本的編碼方式,默認值為 UTF-8。這個方法非常容易理解,將文件以文本方式讀取,讀取的結果即是這個文本文件中的內容。

readAsBinaryString:該方法將文件讀取為二進制字符串,通常我們將它傳送到后端,后端可以通過這段字符串存儲文件。

readAsDataURL:這是例子程序中用到的方法,該方法將文件讀取為一段以 data: 開頭的字符串,這段字符串的實質就是 Data URL,Data URL是一種將小文件直接嵌入文檔的方案。這里的小文件通常是指圖像與 html 等格式的文件。

// 填充選擇的圖片到展示區(qū)
var img = document.createElement("img");
img.classList.add("obj");
img.file = file;
preview.appendChild(img);

// 讀取File對象中的內容
var reader = new FileReader();
reader.onload = (function (aImg) {
    return function (e) {
        aImg.src = e.target.result;
    };
})(img);
reader.readAsDataURL(file);
六、URL
//blob參數(shù)是一個File對象或者Blob對象.
var objecturl =  window.URL.createObjectURL(blob);

上面的代碼會對二進制數(shù)據(jù)生成一個 URL,這個 URL 可以放置于任何通??梢苑胖?URL 的地方,比如 img 標簽的 src 屬性。需要注意的是,即使是同樣的二進制數(shù)據(jù),每調用一次 URL.createObjectURL 方法,就會得到一個不一樣的 URL。

這個 URL 的存在時間,等同于網(wǎng)頁的存在時間,一旦網(wǎng)頁刷新或卸載,這個 URL 就失效。(File 和 Blob 又何嘗不是這樣呢)除此之外,也可以手動調用 URL.revokeObjectURL 方法,使 URL 失效。

window.URL.revokeObjectURL(objectURL);

舉個簡單的例子。

var blob = new Blob(["Hello hanzichi"]);
var a = document.createElement("a");
a.href = window.URL.createObjectURL(blob);
a.download = "a.txt";
a.textContent = "Download";

document.body.appendChild(a);

頁面上生成了一個超鏈接,點擊它就能下載一個名為 a.txt 的文件,里面的內容是 Hello hanzichi。

這里插點題外話,簡單介紹下 H5 新增的 download 屬性。對于一些諸如 exe,rar 等瀏覽器不能直接打開的文件類型,我們一般可以直接用一個 a 標簽,將其指向文件在服務端的地址,點擊即可下載。但是如果是一些瀏覽器能直接打開的文件,比如 txt,js 等,如果這樣設置一個超鏈接,點擊會直接打開文件,一般我們可以配合后端實現(xiàn),比如用 PHP。

我們再回到 URL 上來。對于 File 或者 Blob 對象,我們可以這樣理解,它們的存在,依賴于頁面,而 URL 能給這些 "轉瞬即逝" 的二進制對象一個臨時的指向地址。這個臨時的地址還有什么用呢?也能做圖片預覽。

<input type='file' multiple /><br/>
<img />
<script>
document.querySelector("input").onchange = function() {
  var files = this.files;
  document.querySelector("img").src = window.URL.createObjectURL(files[0]);
}
</script>
七、Blob應用場景

1.分片上傳
File接口基于Blob,繼承了Blob的功能并進行了擴展,故我們可以像使用Blob一樣使用File對象。通過Blob.slice方法,可以將大文件分片,輪循向后臺提交各文件片段,即可實現(xiàn)文件的分片上傳。分片上傳邏輯如下:

  • 獲取要上傳文件的File對象,根據(jù)chunk(每片大小)對文件進行分片
  • 通過post方法輪循上傳每片文件,其中url中拼接querystring用于描述當前上傳的文件信息;post body中存放本次要上傳的二進制數(shù)據(jù)片段
  • 接口每次返回offset,用于執(zhí)行下次上傳
initUpload();

//初始化上傳
function initUpload() {
    var chunk = 100 * 1024;   //每片大小
    var input = document.getElementById("file");    //input file
    input.onchange = function (e) {
        var file = this.files[0];
        var query = {};
        var chunks = [];
        if (!!file) {
            var start = 0;
            //文件分片
            for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
                var end = start + chunk;
                chunks[i] = file.slice(start , end);
                start = end;
            }
            
            // 采用post方法上傳文件
            // url query上拼接以下參數(shù),用于記錄上傳偏移
            // post body中存放本次要上傳的二進制數(shù)據(jù)
            query = {
                fileSize: file.size,
                dataSize: chunk,
                nextOffset: 0
            }

            upload(chunks, query, successPerUpload);
        }
    }
}

// 執(zhí)行上傳
function upload(chunks, query, cb) {
    var queryStr = Object.getOwnPropertyNames(query).map(key => {
        return key + "=" + query[key];
    }).join("&");
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://xxxx/opload?" + queryStr);
    xhr.overrideMimeType("application/octet-stream");
    
    //獲取post body中二進制數(shù)據(jù)
    var index = Math.floor(query.nextOffset / query.dataSize);
    getFileBinary(chunks[index], function (binary) {
        if (xhr.sendAsBinary) {
            xhr.sendAsBinary(binary);
        } else {
            xhr.send(binary);
        }

    });

    xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                var resp = JSON.parse(xhr.responseText);
                // 接口返回nextoffset
                // resp = {
                //     isFinish:false,
                //     offset:100*1024
                // }
                if (typeof cb === "function") {
                    cb.call(this, resp, chunks, query)
                }
            }
        }
    }
}

// 每片上傳成功后執(zhí)行
function successPerUpload(resp, chunks, query) {
    if (resp.isFinish === true) {
        alert("上傳成功");
    } else {
        //未上傳完畢
        query.offset = resp.offset;
        upload(chunks, query, successPerUpload);
    }
}

// 獲取文件二進制數(shù)據(jù)
function getFileBinary(file, cb) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function (e) {
        if (typeof cb === "function") {
            cb.call(this, this.result);
        }
    }
}

以上是文件分片上傳前端的簡單實現(xiàn),當然,此功能還可以更加完善,如后臺需要對合并后的文件大小進行校驗;或者前端加密文件,全部上傳完畢后后端解密校驗等,此處不做贅述。

2.通過url下載文件
window.URL對象可以為Blob對象生成一個網(wǎng)絡地址,結合a標簽的download屬性,可以實現(xiàn)點擊url下載文件
實現(xiàn)如下:

createDownload("download.txt","download file");

function createDownload(fileName, content){
    var blob = new Blob([content]);
    var link = document.createElement("a");
    link.innerHTML = fileName;
    link.download = fileName;
    link.href = URL.createObjectURL(blob);
    document.getElementsByTagName("body")[0].appendChild(link);
}

執(zhí)行后頁面上會生成此Blob對象的地址,點擊后可下載:


image.png

3.通過XHR請求顯示圖片

var xhr = new XMLHttpRequest();    
xhr.open("get", "mm1.jpg", true);
xhr.responseType = "blob";
xhr.onload = function() {
    if (this.status == 200) {
        var blob = this.response;  // this.response也就是請求的返回就是Blob對象
        var img = document.createElement("img");
        img.onload = function(e) {
          window.URL.revokeObjectURL(img.src); // 清除釋放
        };
        img.src = window.URL.createObjectURL(blob);
        eleAppend.appendChild(img);    
    }
}
xhr.send();

4.通過input type=File顯示圖片

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <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>

  <script>
    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);
        }
      }
    }
  </script>
</body>
</html>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容