一、圖片預(yù)覽
圖片預(yù)覽有兩種方式:
- 用
FileReader
把圖片轉(zhuǎn)化為base64格式的數(shù)據(jù)嵌入到HTML中姑丑。 - 用
URL.createObjectURL()
構(gòu)造圖片的URL并賦值給<img>
標(biāo)簽。
1.
FileReader
允許瀏覽器異步讀取用戶電腦上的文件呀忧,需要用File或者Blob指定要讀取的文件师痕。
FileReader
有一個(gè)方法叫.readAsDataURL()
,這個(gè)方法用來(lái)讀取指定Blob或者File的內(nèi)容而账,讀取完成之后readyState
會(huì)變成DONE
胰坟,并且觸發(fā)loadend
事件。
讀取完成之后泞辐,result屬性包含了一個(gè)url笔横,這個(gè)url是一個(gè)Data URL
,Data URL
以base64編碼的形式存儲(chǔ)文件的數(shù)據(jù)咐吼。
已知圖片的src屬性即可以接受圖片的url吹缔,也可以接受一個(gè)Data URL,直接把編碼后的圖片信息內(nèi)嵌到HTML中锯茄。
那么我們可以把這個(gè)Data URL
賦值給img.src
厢塘,這樣就可以在頁(yè)面上顯示圖片了。
代碼如下:
<!-- upload.html -->
<div>
<button type="button" id="button">點(diǎn)擊添加圖片</button>
<input type="file" id="chooseFile"> <!-- hidden屬性對(duì)其不起作用 -->
<img src="" id="preview">
</div>
/* index.css */
/* 隱藏input框肌幽,因?yàn)樘罅送砟耄钊似v,這樣我們就可以定義自己美美的輸入框了 */
input {
display: none;
}
// index.js
var log = console.log.bind(console)
$('#button').on('click', function () { // 點(diǎn)擊按鈕觸發(fā)input框的click事件
$('#chooseFile').click()
})
$('#chooseFile').on('change', function () {
var file = this.files[0]
// log(file)
var reader = new FileReader()
reader.onload = function (e) {
// log(e.target)
$('#preview').attr('src', e.target.result) // 把圖片的base64編碼形式賦值給img.src
}
reader.readAsDataURL(file)
})
關(guān)于Data URLs
Data URLs由4個(gè)部分組成:
- 前綴:
data:
-
mediatype;
:一個(gè)MINE類(lèi)型的聲明喂急,表示這串?dāng)?shù)據(jù)的編碼類(lèi)型格嘁,比如:image/png.如果缺省,默認(rèn)為text/plain;charset=US-ASCII - 可選的
base64,
:如果該數(shù)據(jù)是非文本類(lèi)型的廊移,那么要加上這個(gè)部分 - 數(shù)據(jù)本身
舉個(gè)例子糕簿,這是一張圖片的Data URL:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAASuCAYAAADI0RjrAAAMJGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdUk8kWnr8kISGhBSIgJfQ......
Data URLs的好處是:
- 我們可以把體積比較小的圖片嵌入到HTML文件中,減少HTTP請(qǐng)求次數(shù)狡孔。
- 如果圖片是在服務(wù)端用程序動(dòng)態(tài)生成的懂诗,每個(gè)用戶顯示的都不同時(shí),直接把圖片的Data URL嵌入到網(wǎng)頁(yè)中會(huì)很方便步氏。
- 如果訪問(wèn)外部資源很麻煩或者受限時(shí)响禽。
Data URLs的缺點(diǎn)是:
- base64的體積會(huì)比原數(shù)據(jù)體積大約 1/3
- Data URL形式的圖片不會(huì)被瀏覽器緩存,這意味著每次訪問(wèn)頁(yè)面都要加載一次圖片荚醒。這種情況可以通過(guò)CSS文件把Data URL設(shè)成背景圖片的url來(lái)避免芋类。
比如:
div { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAASuCAYAAADI0RjrAAAMJGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdUk8kWnr8kISGhBSIgJfQ......")
關(guān)于base64:
Base64編碼原理與應(yīng)用
阮一峰的網(wǎng)絡(luò)日志——Base64筆記
2.
URL
接口是一個(gè)包含若干靜態(tài)方法的對(duì)象。
URL用于解析界阁,構(gòu)建侯繁,規(guī)范化和編碼URLs。
URL接口可以在Web Worker中使用泡躯。
當(dāng)使用一個(gè)沒(méi)有實(shí)現(xiàn)該構(gòu)造器的用戶代理時(shí)贮竟,可以通過(guò) Window.URL
屬性來(lái)訪問(wèn)該對(duì)象(基于 Webkit 和 Blink 內(nèi)核的瀏覽器均可用 Window.webkitURL
代替)丽焊。
URL = URL || window.URL || window.webkitURL
URL有一個(gè).createObjectURL()
方法,該方法接收一個(gè)本地的blob作為參數(shù)咕别,創(chuàng)建一個(gè)指向該blob的url技健,在幾乎任何可以使用url的地方都可以使用.createObjectURL()
方法創(chuàng)建的url。
這意味著惰拱,我們可以使用它來(lái)實(shí)現(xiàn)圖片的預(yù)覽:
// index.js
$('#chooseFile').on('change', function () {
var file = this.files[0]
var url = URL.createObjectURL(file) // 創(chuàng)建一個(gè)指向選擇的圖片的url
// log(url)
$('#preview').attr('src', url) // 將url賦值給img.src
})
.createObjectURL()
創(chuàng)建的路徑的格式是這樣的:blob:null/元素本身組成的路徑
雌贱。
比如:blob:null/8629b731-05a1-4b74-8a5a-b87878eba849
null表示域名為空。
URL.createObjectURL()
的性能要比使用FileReader高很多偿短,如果只是單純想要預(yù)覽圖片欣孤,那么使用URL.createObjectURL()
比較好。
二昔逗、圖片壓縮
canvas 有一個(gè) toDataURL(type, quantity)
方法降传,就是把canvas的數(shù)據(jù)輸出為一個(gè)Data URL,該方法可以設(shè)置圖片質(zhì)量勾怒。利用這一特性婆排,就可以用來(lái)壓縮圖片。
function compress(img) { // img為圖片的Data URL
var canvas = document.createElement('canvas') // 創(chuàng)建一個(gè)canvas
var context = canvas.getContext('2d')
var width = img.width
var height = img.height
canvas.width = width // 把canvas的寬高設(shè)置為圖片的寬高
canvas.height = height
// 開(kāi)始畫(huà)圖
context.fillStyle = '#fff'
context.fillRect(0, 0, canvas.width, canvas.height)
context.drawImage(img, 0, 0, canvas.width, canvas.height)
var base64Data = canvas.toDataURL('image/jpeg', 0.4) // 壓縮圖片控硼,質(zhì)量參數(shù)太多反而會(huì)加大圖片體積泽论!
canvas = context = null
// log(base64Data.length)
img = null
return base64Data // 返回壓縮后的base64字符串流
}
需要注意的是:
-
canvas.toDataURL
的質(zhì)量參數(shù)只對(duì)image/jpeg
或image/webp
有效艾少。 - 如果圖像本身是
image/png
卡乾,則 type 參數(shù)不能為非image/png
的其他類(lèi)型。 - 質(zhì)量參數(shù)默認(rèn)是0.92缚够,質(zhì)量參數(shù)太大反而會(huì)增加圖片的體積幔妨。
三、 圖片上傳
假設(shè)我們沒(méi)有壓縮圖片谍椅,那么直接使用FormData()
上傳圖片就可以了误堡。
現(xiàn)在假設(shè)我們已經(jīng)壓縮了圖片,那么要怎么傳圖片呢雏吭?
可以直接把base64的數(shù)據(jù)傳到后端再解碼成圖片锁施。也可以在前端先解碼再傳到后端,因?yàn)閎ase64數(shù)據(jù)的體積要比原數(shù)據(jù)的體積大 1/3 杖们。
思路如下:
- 用
.atob()
方法對(duì)用base-64編碼過(guò)的字符串進(jìn)行解碼悉抵,我們現(xiàn)在輸出會(huì)得到一串二進(jìn)制字符串。 - 用
.charCodeAt()
方法得到每一個(gè)字符的Unicode編碼摘完。 - 用
new Uint8Array();
把我們得到的Unicode編碼轉(zhuǎn)化為類(lèi)型數(shù)組姥饰,類(lèi)型數(shù)組擁有二進(jìn)制支持,可以用來(lái)處理二進(jìn)制文件孝治。普通數(shù)組是無(wú)法生成二進(jìn)制文件的列粪。 - 用
Blob()
構(gòu)造函數(shù)構(gòu)造一個(gè)文件审磁。
代碼如下:
// 注意b64Data必須把前面的類(lèi)似于“data:image/png;base64,"的字串截掉
// contentType指圖片類(lèi)型,比如:image/png岂座, image/jpeg 等等
function b64ToBlob(b64Data, contentType) {
contentType = contentType || ''
var byteCharacters = atob(b64Data) // 解碼base64數(shù)據(jù)為二進(jìn)制字符串
var buffer = [] // 注意态蒂,Blob第一個(gè)參數(shù)必須是一個(gè)數(shù)組
// 類(lèi)型數(shù)組用來(lái)處理二進(jìn)制文件
var aBuffer = new ArrayBuffer(byteCharacters.length)
var uBuffer = new Uint8Array(aBuffer)
for (var i = 0; i < byteCharacters.length; i++) {
uBuffer[i] = byteCharacters.charCodeAt(i) // 得到Unicode編碼,存進(jìn)類(lèi)型數(shù)組
}
buffer.push(uBuffer)
var blob = new Blob(buffer, { // 生成一個(gè)二進(jìn)制文件
type: contentType
})
// log(blob)
return blob
}
現(xiàn)在已經(jīng)完成了圖片的預(yù)覽费什,壓縮吃媒,以及壓縮后的轉(zhuǎn)碼。
之后就是ajax上傳的問(wèn)題了吕喘,先構(gòu)建一個(gè)FormData赘那,把圖片append進(jìn)去,傳到后臺(tái)氯质。最后的完整實(shí)現(xiàn):
<!-- index.html -->
<body>
<div id="imagesDiv">
<input type="file" id="chooseFile">
</div>
<script src="../static/js/jquery-3.2.1.min.js"></script>
</body>
// index.js
var log = console.log.bind(console)
// 上傳圖片
$('#chooseFile').on('change', function () {
var file = this.files[0]
if (!file.type.match('image.*')) { // 不是圖片則返回
alert('只能上傳圖片');
return false;
}
var reader = new FileReader()
reader.onload = function () {
var img = new Image()
var originData = this.result
img.onload = function () {
var compressUrl = compress(img) // 壓縮圖片
var blob = b64ToBlob(compressUrl.split(',')[1], 'image/jpeg') // 壓縮成jpeg格式
var form_data = new FormData()
form_data.append('file', blob)
$.ajax({
type: 'POST',
url: '你的url',
data: form_data,
processData: false, // 必須
contentType: false, // 必須
success: function (data, status, xhr) {
console.log(data)
},
error: function (error) {
console.log(error)
}
})
}
img.src = originData // 把圖片數(shù)據(jù)賦值給img
}
reader.readAsDataURL(file) // 讀取圖片信息
})
// 壓縮圖片
function compress(img) {
var canvas = document.createElement('canvas')
var context = canvas.getContext('2d')
var width = img.width
var height = img.height
// log(img.width, img.height)
// log(width, height)
canvas.width = width
canvas.height = height
context.fillStyle = '#fff'
context.fillRect(0, 0, canvas.width, canvas.height)
context.drawImage(img, 0, 0, canvas.width, canvas.height)
var base64Data = canvas.toDataURL('image/jpeg', 0.4)
canvas = context = null
// log(base64Data.length)
img = null
return base64Data
}
// base64轉(zhuǎn)化為二進(jìn)制文件
function b64ToBlob(b64Data, contentType) {
contentType = contentType || ''
var byteCharacters = atob(b64Data) // 解碼base64數(shù)據(jù)為二進(jìn)制字符串
var buffer = [] // 注意募舟,Blob第一個(gè)參數(shù)必須是一個(gè)數(shù)組
// 類(lèi)型數(shù)組用來(lái)處理二進(jìn)制文件
var aBuffer = new ArrayBuffer(byteCharacters.length)
var uBuffer = new Uint8Array(aBuffer)
for (var i = 0; i < byteCharacters.length; i++) {
uBuffer[i] = byteCharacters.charCodeAt(i) // 得到Unicode編碼,存進(jìn)類(lèi)型數(shù)組
}
buffer.push(uBuffer)
// 普通數(shù)組是無(wú)法生成二進(jìn)制文件的
var blob = new Blob(buffer, { // 生成一個(gè)二進(jìn)制文件
type: contentType
})
// log(blob)
return blob
}