一、前言
這兩天恰好有一位同事問我怎樣做一個圖片預(yù)覽功能。作為現(xiàn)代人的我們首先想到的當(dāng)然是HTML5啦,其實(shí)HTML5做圖片預(yù)覽已經(jīng)是一個老生常談的問題了。我在這里就簡單說說其中相關(guān)的一些東西,當(dāng)然會附上我們的源碼。在 HTML5 之前我們做圖片預(yù)覽主流做法有兩種,第一種是通過 Flash 插件來做預(yù)覽,第二種是 Ajax 實(shí)現(xiàn)的假預(yù)覽,也就是說選擇圖片文件后,圖片其實(shí)已經(jīng)異步上傳到服務(wù)器,服務(wù)器處理后返回圖片路徑,前端得到響應(yīng)結(jié)果做出處理從而使圖片顯示在界面上。而有了 HTML5 之后就可以強(qiáng)烈鄙視上面兩種做法了。
二、FileReader
要做圖片預(yù)覽功能,就不得不介紹一下 FileReader,顧名思義,它是用來讀取文件的。當(dāng)然新東西總會有一些頑固派排斥的,我們先來看看其兼容性如何(這不是本文討論的重點(diǎn))。
PC端兼容列表

移動端兼容列表

兼容性的話大家根據(jù)自己的需求參考一下上面的對照表,我們接著來看看 FileReader 的幾個常用屬性和常用方法
屬性
- FileReader.onload
讀取完成 - FileReader.result
讀取結(jié)果 - FileReader.error
讀取錯誤 - FileReader.readyState
當(dāng)前文檔的狀態(tài)
方法
- FileReader.abort()
中斷讀取-無參數(shù) - FileReader.readAsArrayBuffer(file)
將文件讀取為ArrayBuffer 對象 參數(shù):文件 - FileReader.readAsBinaryString(file)
將文件讀取為二進(jìn)制碼 - 參數(shù):文件 - FileReader.readAsDataURL(file)
將文件讀取為DataURL 參數(shù):文件 - FileReader.readAsText(file)
將文件讀取為文本 參數(shù):文件
廢話不多說,我們通過代碼來更直觀點(diǎn)認(rèn)識上面的屬性和方法。回歸到需求,做一個圖片預(yù)覽功能。首先理一理我們需要有的東西,第一要素當(dāng)然是文件(文件選擇器),第二當(dāng)然是預(yù)覽(容器)。
html 代碼 (樣式我順手加上了)
<!DOCTYPE html>
<html>
<head>
<title>Cboyce-HTML5圖片預(yù)覽</title>
<style type="text/css">
/*主容器*/
.container{
width: 90%;
margin-top: 20px;
}
/*圖片預(yù)覽容器*/
.container .img-prev-container{
width: 200px;
height: 100px;
margin:10px auto;
border:1px solid #ccc;
}
/*預(yù)覽圖片樣式*/
.container .img-prev-container img{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="container">
<div class="img-prev-container">
</div>
<input type="file" value="請選擇圖片" id="fileSelecter" />
</div>
</body>
</html>
接下來該 FileReader 出場了
window.onload = function(){
//觸發(fā) change 事件
GetDomById('fileSelecter').onchange = function(event){
//獲取文件對象
var file = event.target.files[0];
//創(chuàng)建reader對象
var reader = new FileReader();
//讀取完成后觸發(fā)
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
console.log(_img_src)
//添加預(yù)覽圖片到容器框
var img = document.createElement('img');
img.setAttribute('src',_img_src);
GetDomById('img-perv-div').appendChild(img);
}
//獲取到數(shù)據(jù)的url 圖片將轉(zhuǎn)成 base64 格式
reader.readAsDataURL(file);
}
}
//簡化 document.getElementById() 函數(shù)
function GetDomById(id){
return document.getElementById(id);
}
細(xì)節(jié)注意:這里的圖片格式默認(rèn)轉(zhuǎn)為 base64

補(bǔ)充說明:
event.target 屬性,其特點(diǎn)在我們的代碼中其實(shí)不忙看出來 "捕獲當(dāng)前事件作用的對象",通俗點(diǎn)來講就是,誰觸發(fā)了該事件,我就能通過該事件的 target 拿到誰。
其實(shí)上述代碼還有一個小 bug 換圖變成多圖。 請看下圖

修復(fù):改造 onload
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
//預(yù)覽圖的容器
var _img_container = GetDomById('img-perv-div')
//添加預(yù)覽圖片到容器框
var _imgs = _img_container.getElementsByTagName('img');
//容器中沒有則創(chuàng)建,有則修改 src 屬性
if(!_imgs.lenght){
_imgs[0] = document.createElement('img');
_imgs[0].setAttribute('src',_img_src);
_img_container.appendChild(_imgs[0]);
}else{
_imgs[0].setAttribute('src',_img_src);
}
}
解決bug

三、實(shí)現(xiàn)拖拽預(yù)覽
上面我們已經(jīng)把基礎(chǔ)功能給完成了,接下來我們給該程序加個拓展--拖拽圖片到預(yù)覽框自動加載。
要完成該功能還是得靠 HTML5 的 Drag 和 drop。如果你還搞不清楚我們要做什么,那我們先來看下最終效果。

在代碼開始之前我們先來了解兩個實(shí)現(xiàn)該功能最為關(guān)鍵的事件。
- dragover
拖拽一個對象到目標(biāo)對象上面觸發(fā)該事件 - drop 拖放事件結(jié)束時觸發(fā)。通俗來講就是當(dāng)我們拖拽一個對象到目標(biāo)對象上后放開(松開鼠標(biāo)左鍵)該對象的時候觸發(fā)
接下來我們來看下代碼,這里也對之前的代碼做出了一些改造
window.onload = function(){
//預(yù)覽圖的容器
var _img_container = getDomById('img-perv-div')
//創(chuàng)建reader對象
var reader = new FileReader();
//觸發(fā) change 事件
getDomById('fileSelecter').onchange = function(event){
//獲取文件對象
var file = event.target.files[0];
//讀取完成后觸發(fā)
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
//添加預(yù)覽圖片到容器框
showPrevImg(_img_container,_img_src);
}
//獲取到數(shù)據(jù)的url 圖片將轉(zhuǎn)成 base64 格式
reader.readAsDataURL(file);
}
//添加拖放支持
_img_container.addEventListener('dragover',function(ev){
ev.preventDefault();//阻止默認(rèn)事件。比如說Chrome是直接將圖片用瀏覽器打開
},false)
_img_container.addEventListener('drop',function(ev){
ev.preventDefault();
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
//圖片預(yù)覽處理
showPrevImg(_img_container,_img_src);
}
reader.readAsDataURL(ev.dataTransfer.files[0])
},false)
}
//簡化 document.getElementById() 函數(shù)
function getDomById(id){
return document.getElementById(id);
}
//圖片預(yù)覽處理函數(shù)
function showPrevImg(_img_container,_img_src){
//添加預(yù)覽圖片到容器框
var _imgs = _img_container.getElementsByTagName('img');
//容器中沒有則創(chuàng)建,有則修改 src 屬性
if(!_imgs.lenght){
_imgs[0] = document.createElement('img');
_imgs[0].setAttribute('src',_img_src);
_img_container.appendChild(_imgs[0]);
}else{
_imgs[0].setAttribute('src',_img_src);
}
}
代碼分析
addEventListener('dragover',function(ev){
ev.preventDefault();
},false)
這段代碼重點(diǎn)在于 ev.preventDefault(); 阻止默認(rèn)行為,如果我們不阻止其默認(rèn)行為將會產(chǎn)生下面的后果

接下來要做的就是拖放結(jié)束展示圖片預(yù)覽效果
_img_container.addEventListener('drop',function(ev){
ev.preventDefault();
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
//添加預(yù)覽圖片到容器框
showPrevImg(_img_container,_img_src);
}
reader.readAsDataURL(ev.dataTransfer.files[0])
},false)
這里用到 event.dataTransfer 我們補(bǔ)充一下,我們先來看下他的定義
dataTransfer 拖曳數(shù)據(jù)傳遞對象,其提供了對于預(yù)定義的剪貼板格式的訪問,以便在拖曳操作中使用
通俗來講就是,我們在拖曳操作中可以使用它來操作我們拖曳的對象。比如拖圖片,通過它能拿到我們所拖曳的圖片對象
最后,強(qiáng)迫癥犯了,稍微寫了點(diǎn)樣式美化了一下完整代碼如下
<!DOCTYPE html>
<html>
<head>
<title>Cboyce-HTML5圖片預(yù)覽</title>
<style type="text/css">
body{
font-family: '微軟雅黑';
}
/*主容器*/
.container{
width: 90%;
margin-top: 20px;
}
/*每一個圖片預(yù)覽項(xiàng)容器*/
.img-prev-item{
width: 200px;
height: 200px;
display: inline-block;
border:1px solid #ccc;
text-align: center;
border-radius: 3px;
}
/*圖片預(yù)覽容器*/
.container .img-prev-container{
width: 200px;
height: 156px;
margin: 0 auto;
border-bottom: 1px solid #ccc;
vertical-align: middle;
display: table-cell;
padding: 2px;
color: #838383;
text-align: center
}
/*預(yù)覽圖片樣式*/
.container .img-prev-container img{
width: 100%;
height: auto;
max-height: 100%;
}
/*label*/
.selfile{
background-color: #0095ff;
color: white;
padding: 6px 58px;
border-radius: 5px;
}
/*工具條 div*/
.tool{
padding-top: 9px;
}
/*隱藏文件選擇器*/
#fileSelecter{
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="img-prev-item">
<div class="img-prev-container" id="img-perv-div">
請選擇圖片或者<br />將圖片拖拽至此
</div>
<div class="tool">
<label for="fileSelecter" class="selfile">請選擇圖片</label>
<input type="file" value="請選擇圖片" id="fileSelecter" />
</div>
</div>
</div>
<script type="text/javascript">
window.onload = function(){
//預(yù)覽圖的容器
var _img_container = getDomById('img-perv-div')
//創(chuàng)建reader對象
var reader = new FileReader();
//觸發(fā) change 事件
getDomById('fileSelecter').onchange = function(event){
//獲取文件對象
var file = event.target.files[0];
//讀取完成后觸發(fā)
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
//添加預(yù)覽圖片到容器框
showPrevImg(_img_container,_img_src);
}
//獲取到數(shù)據(jù)的url 圖片將轉(zhuǎn)成 base64 格式
reader.readAsDataURL(file);
}
//添加拖放支持
_img_container.addEventListener('dragover',function(ev){
//ev.stopPropagation();
ev.preventDefault();//阻止默認(rèn)事件。比如說Chrome是直接將圖片用瀏覽器打開
console.log('dragover')
},false)
// _img_container.addEventListener('dragend',function(ev){
// ev.stopPropagation();
// ev.preventDefault();
// console.log('dragend')
// },false)
_img_container.addEventListener('drop',function(ev){
//ev.stopPropagation();
ev.preventDefault();
console.log('drop')
//console.log(ev.dataTransfer.files[0])
reader.onload = function(ev){
//獲取圖片的url
var _img_src = ev.target.result;
//添加預(yù)覽圖片到容器框
showPrevImg(_img_container,_img_src);
}
reader.readAsDataURL(ev.dataTransfer.files[0])
},false)
}
//簡化 document.getElementById() 函數(shù)
function getDomById(id){
return document.getElementById(id);
}
function showPrevImg(_img_container,_img_src){
_img_container.innerHTML="";
//添加預(yù)覽圖片到容器框
var _imgs = _img_container.getElementsByTagName('img');
//容器中沒有則創(chuàng)建,有則修改 src 屬性
if(!_imgs.lenght){
_imgs[0] = document.createElement('img');
_imgs[0].setAttribute('src',_img_src);
_img_container.appendChild(_imgs[0]);
}else{
_imgs[0].setAttribute('src',_img_src);
}
}
//接下來要做的就是拖放結(jié)束展示圖片預(yù)覽效果
</script>
</body>
</html>
運(yùn)行效果如下

四、結(jié)語
基本上實(shí)現(xiàn)以及代碼的原理也就解釋到這了。其實(shí)前端做的圖片預(yù)覽功能大多數(shù)需求是用來上傳到服務(wù)器的。不得不提到的是這里的拖拽預(yù)覽雖然看起來體驗(yàn)不錯,但是要將該文件上傳就得做一些特殊處理。這個我就留到后面的博客再講了,有問題的朋友可以直接留言。
限于筆者技術(shù),文章觀點(diǎn)難免有不當(dāng)之處,希望發(fā)現(xiàn)問題的朋友幫忙指正,筆者將會及時更新。也請轉(zhuǎn)載的朋友注明文章出處并附上原文鏈接,以便讀者能及時獲取到文章更新后的內(nèi)容,以免誤導(dǎo)讀者。筆者力求避免寫些晦澀難懂的文章(雖然也有人說這樣顯得高逼格,專業(yè)),盡量使用簡單的用詞和例子來幫助理解。如果表達(dá)上有好的建議的話也希望朋友們在評論處指出。
本文為作者原創(chuàng),轉(zhuǎn)載請注明出處! 東野文然