點選上方“ 前端印象 ”,選擇“ 設為星标 ” 第一時間關注技術幹貨!
最開始的一個小需求
前兩天項目中有個小需求:前端下載下傳背景小哥傳回的二進制流檔案。
起初接到這個需求時,我感覺這很簡單啊(雖然我不會,但可以百度啊,,,,)

然後就寫出了如下的代碼:
這一段代碼,我大概強行解釋一下:
首先判斷
window.navigator.msSaveOrOpenBlob
是為了相容
IE
(誰要相容這 xx
IE
!!)
然後非
IE
的通過
URL.createObjectURL()
将
Blob
(
Blob
是啥?不知道?沒關系,我下面會具體裝逼講解的)建構為一個
object URL
對象、指定檔案名&檔案類型、建立
a
連結模拟點選實作下載下傳,最後通過
URL.revokeObjectURL
釋放建立的對象。
功能雖然實作了,但其實我是似懂非懂的~
緊接着 一個不那麼簡單的需求
沒過幾天,産品又給我提了一個需求:圖檔裁剪上傳及預覽。
雖然聽過類似的需求,但自己手寫還真的沒寫過,然後我就開始了網上沖浪時光(各種搜尋,,,)。但這次,沒有想象中那麼簡單了~~
網上看到的都是諸如
FileReader
、
canvas
、
ArrayBuffer
、
FormData
、
Blob
這些名詞。我徹底懵了,這些平時都隻是聽過啊,用的也不多啊。經過了一番學習,我發現這些都屬于前端二進制的知識範疇,是以在搞業務前,我準備先把涉及到的前端二進制梳理一遍,正所謂:底層基礎決定上層建築嘛 🙈
FileReader
FileReader
HTML5
定義了
FileReader
作為檔案
API
的重要成員用于讀取檔案,根據
W3C
的定義,
FileReader
接口提供了讀取檔案的方法和包含讀取結果的事件模型。
建立執行個體
方法
方法名 | 描述 |
---|---|
| 中止讀取操作 |
| 異步按位元組讀取檔案内容,結果用 對象表示 |
| 異步按位元組讀取檔案内容,結果為檔案的二進制串 |
| 異步讀取檔案内容,結果用 的字元串形式表示 |
| 異步按字元讀取檔案内容,結果用字元串形式表示 |
事件
事件名 | 描述 |
---|---|
| 中斷時觸發 |
| 出錯時觸發 |
| 檔案讀取成功完成時觸發 |
| 讀取完成觸發(無論成功或失敗) |
| 讀取開始時觸發 |
| 讀取中 |
示例
下面我們嘗試把一個檔案的内容通過字元串的方式讀取出來:
ArrayBuffer
/ TypedArray
/ DataView 對象
ArrayBuffer
TypedArray
DataView 對象
ArrayBuffer
ArrayBuffer
先來看下
ArrayBuffer
的功能:
先來介紹
ArrayBuffer
,是因為
FileReader
有個
readAsArrayBuffer()
的方法,如果被讀的檔案是二進制資料,那用這個方法去讀應該是最合适的,讀出來的資料,就是一個
Arraybuffer
對象,來看下定義:
對象用來表示通用的、固定長度的原始二進制資料緩沖區.
ArrayBuffer
不能直接操作,而是要通過類型數組對象或
ArrayBuffer
對象來操作,它們會将緩沖區中的資料表示為特定的格式,并通過這些格式來讀寫緩沖區的内容.
DataView
ArrayBuffer
也是一個構造函數,可以配置設定一段可以存放資料的連續記憶體區域。
由于無法對
Arraybuffer
直接進行操作,是以我們需要借助其他對象來操作. 所有就有了
TypedArray
(類型數組對象)和
DataView
對象。
DataView 對象
DataView 對象
上面代碼生成了一段 8 位元組的記憶體區域,每個位元組的值預設都是 0。
為了讀寫這段内容,需要為它指定視圖。
DataView
視圖的建立,需要提供
ArrayBuffer
對象執行個體作為參數。
DataView
視圖是一個可以從二進制
ArrayBuffer
對象中讀寫多種數值類型的底層接口。
-
從setint8()
起始位置以DataView
為計數的指定偏移量(byte
)處存儲一個byteOffset
數(一個位元組)8-bit
-
從getint8()
起始位置以DataView
為計數的指定偏移量(byte
)處擷取一個byteOffset
數(一個位元組)8-bit
調用
示例
TypedArray
TypedArray
另一種
TypedArray
視圖,與
DataView
視圖的一個差別是,它不是一個構造函數,而是一組構造函數,代表不同的資料格式。
TypedArray
對象描述了一個底層的二進制資料緩存區(
binary data buffer
)的一個類數組視圖(
view
)。
但它本身不可以被執行個體化,甚至無法通路,你可以把它了解為接口,它有很多的實作。
實作方法
類型 | 單個元素值的範圍 | 大小(bytes) | 描述 |
---|---|---|---|
Int8Array | -128 to 127 | 1 | 8 位二進制有符号整數 |
Uint8Array | 0 to 255 | 1 | 8 位無符号整數 |
Int16Array | -32768 to 32767 | 2 | 16 位二進制有符号整數 |
Uint16Array | 0 to 65535 | 2 | 16 位無符号整數 |
示例
Blob
Blob
Blob
是用來支援檔案操作的。簡單的說:在
JS
中,有兩個構造函數
File
和
Blob
, 而
File
繼承了所有
Blob
的屬性。
是以在我們看來,
File
對象可以看作一種特殊的
Blob
對象。
上面說了,
File
對象是一種特殊的
Blob
對象,那麼它自然就可以直接調用
Blob
對象的方法。讓我們看一看
Blob
具體有哪些方法,以及能夠用它們實作哪些功能:
是的,我們這裡更加傾向于實戰中的應用~
關于
Blob
的更具體介紹可以參考Blob[1]
atob
和 btoa
atob
btoa
base64
相信大家都不會陌生吧(不知道的看這裡[2]),最常用的操作可能就是圖檔轉
base64
了吧?
在之前要在字元串跟
base64
之間互轉,我們可能需要去網上拷一個别人的方法,而且大部分情況下,你沒有時間去驗證這個方法是不是真的可靠,有沒有
bug
。
從
IE10+
浏覽器開始,所有浏覽器就原生提供了
Base64
編碼解碼方法。
Base64 解碼
Base64 編碼
Canvas
中的 ImageData
對象
Canvas
ImageData
關于
Canvas
,這裡我就不做過多介紹了,具體可參考canvas 文檔[3]
今天主要說一下
Canvas
中的
ImageData
對象(也是為下面的那個圖檔裁剪的項目做一些基礎知識的鋪墊~)
ImageData
對象中存儲着
canvas
對象真實的像素資料,它包含以下幾個隻讀屬性:
-
:圖檔寬度,機關是像素width
-
:圖檔高度,機關是像素height
-
:data
類型的一維數組,包含着Uint8ClampedArray
格式的整型資料,範圍在 0 至 255 之間(包括 255)。RGBA
建立一個 ImageData
對象
ImageData
使用
createImageData()
方法去建立一個新的,空白的
ImageData
對象。
上面代碼建立了一個新的具體特定尺寸的
ImageData
對象。所有像素被預設為透明黑。
得到場景像素資料
為了獲得一個包含畫布場景像素資料的
ImageData
對象,你可以用
getImageData()
方法:
在場景中寫入像素資料
你可以用
putImageData()
方法去對場景進行像素資料的寫入。
toDataURL
将 canvas
轉為 data URI
格式
toDataURL
canvas
data URI
有如下
元素:
可以用下面的方式擷取一個
data-URL
到這裡,二進制相關的基礎知識我已經鋪墊完了。下面讓我們回到文章開頭提到的那個産品的“沒那麼簡單”的新需求:圖檔裁剪上傳及預覽。
其實,像
圖檔裁剪上傳
這種社群已經有非常成熟的解決方案了,如vue-cropper[4]。這裡,我選擇手寫一個簡易的圖檔裁剪的目的是因為這其中用到了上文提及的大量的二進制知識,可以很好的将理論與實踐結合。
話不多說,開 Giao!!
需求開發 Giao Giao!
先來看下最終的效果:
這裡貼下完成後的代碼位址[5]
另外,我用一張圖梳理了以上提到的前端二進制子產品的關系,這對于下面需求的開發會有很大的幫助:
整個需求分以下四步:
1、擷取檔案并讀取檔案。
2、擷取裁剪坐标。
3、裁剪圖檔。
4、讀取裁剪後的圖檔預覽并上傳。
擷取檔案并讀取檔案
首先來看下上面第一步提到的擷取檔案。對應就是給
input
綁定的
handleChange
事件:
HTML5
支援從
input[type=file]
元素中直接擷取檔案資訊,也可以讀取檔案内容。
這裡就需要用到了
FileReader
,這個類是專門用來讀取本地檔案的。純文字或者二進制都可以讀取,但是本地檔案必須是經過使用者允許才能讀取,也就是說使用者要在
input[type=file]
中選擇了這個檔案,你才能讀取到它。
通過
FileReader
我們可以将圖檔檔案轉化成
DataURL
,就是以
data:image/png;base64
開頭的一種
URL
,然後可以直接放在
image.src
裡,這樣本地圖檔就顯示出來了。
擷取裁剪坐标
這裡主要是
mousedown
、
mousemove
、
mouseup
事件的結合使用。
mousedown
mousedown
滑鼠按下事件。這裡要記錄下滑鼠按下時的開始坐标,即
startX
與
startY
,同時要将标志位
startDrag
設為
true
,辨別滑鼠開始移動。
mousemove
mousemove
滑鼠移動事件。判斷
startDrag
為
true
(即滑鼠開始移動),然後記錄對應移動的距離。
mouseup
mouseup
滑鼠彈起事件。這裡要記錄下最終滑鼠的落點坐标,對應就是
lastX
與
lastY
。
裁剪圖檔
這個時候我們就需要用到
canvas
了,
canvas
和圖檔一樣,是以建立
canvas
時就要确定其高寬。
将圖檔放置入
canvas
時需要調用
drawImage
:
具體
API
使用參考
MDN
上的drawImage[6]
其中這裡面我們還加入了
scale
,這個變量是用來實作圖檔
放大
、
縮小
效果的。
而且會判斷圖檔的寬、高的大小關系,進而實作圖檔在
canvas
中對應的适配。
讀取裁剪後的圖檔并上傳
這時我們要擷取
canvas
中圖檔的資訊,用
toDataURL
就可以轉換成上面用到的
DataURL
。
然後取出其中
base64
資訊,再用
window.atob
轉換成由二進制字元串。但
window.atob
轉換後的結果仍然是字元串,直接給
Blob
還是會出錯。是以又要用
Uint8Array
轉換一下。
這時候裁剪後的檔案就儲存在
blob
裡了,我們可以把它當作是普通檔案一樣,加入到
FormData
裡,并上傳至伺服器了。
👇👇👇 歡迎留言讨論 👇👇👇 推薦閱讀
【JS面試題】用四種方式實作數組扁平化你會嗎
【JS面試題】數組去重(6種方法)震驚面試官
【超高頻面試題】這兩段代碼的傳回結果你知道是什麼嗎?
【css面試題】用css畫0.5px的線條
【css面試題】css實作氣泡框效果
【css炫酷動畫】讓面試官眼前一亮的故障風格文字動畫
END
❤支援三連
1.看到這裡了就點個在看支援下吧,你的「在看」是我創作的動力。
2.關注公衆号
前端印象
,「一起交流進步」!
3.關注公衆号回複【加群】,拉你進技術交流群一起玩轉前端。
點贊、在看、分享 支援作者❤️