HTML5圖片拖拽預(yù)覽原理及實(shí)現(xiàn)

一、前言

這兩天恰好有一位同事問我怎樣做一個圖片預(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端兼容列表

PC端兼容列表

移動端兼容列表

移動端兼容列表

兼容性的話大家根據(jù)自己的需求參考一下上面的對照表,我們接著來看看 FileReader 的幾個常用屬性和常用方法

屬性

  1. FileReader.onload
    讀取完成
  2. FileReader.result
    讀取結(jié)果
  3. FileReader.error
    讀取錯誤
  4. FileReader.readyState
    當(dāng)前文檔的狀態(tài)

方法

  1. FileReader.abort()
    中斷讀取-無參數(shù)
  2. FileReader.readAsArrayBuffer(file)
    將文件讀取為ArrayBuffer 對象 參數(shù):文件
  3. FileReader.readAsBinaryString(file)
    將文件讀取為二進(jìn)制碼 - 參數(shù):文件
  4. FileReader.readAsDataURL(file)
    將文件讀取為DataURL 參數(shù):文件
  5. 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

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

解決bug

三、實(shí)現(xiàn)拖拽預(yù)覽

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

拖拽預(yù)覽

在代碼開始之前我們先來了解兩個實(shí)現(xiàn)該功能最為關(guān)鍵的事件。

  1. dragover
    拖拽一個對象到目標(biāo)對象上面觸發(fā)該事件
  2. 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)生下面的后果

不阻止默認(rè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)載請注明出處! 東野文然

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容