HTML5图片拖拽预览原理及实现

一、前言

这两天恰好有一位同事问我怎样做一个图片预览功能。作为现代人的我们首先想到的当然是HTML5啦,其实HTML5做图片预览已经是一个老生常谈的问题了。我在这里就简单说说其中相关的一些东西,当然会附上我们的源码。在 HTML5 之前我们做图片预览主流做法有两种,第一种是通过 Flash 插件来做预览,第二种是 Ajax 实现的假预览,也就是说选择图片文件后,图片其实已经异步上传到服务器,服务器处理后返回图片路径,前端得到响应结果做出处理从而使图片显示在界面上。而有了 HTML5 之后就可以强烈鄙视上面两种做法了。

二、FileReader

要做图片预览功能,就不得不介绍一下 FileReader,顾名思义,它是用来读取文件的。当然新东西总会有一些顽固派排斥的,我们先来看看其兼容性如何(这不是本文讨论的重点)。

PC端兼容列表

PC端兼容列表

移动端兼容列表

移动端兼容列表

兼容性的话大家根据自己的需求参考一下上面的对照表,我们接着来看看 FileReader 的几个常用属性和常用方法

属性

  1. FileReader.onload
    读取完成
  2. FileReader.result
    读取结果
  3. FileReader.error
    读取错误
  4. FileReader.readyState
    当前文档的状态

方法

  1. FileReader.abort()
    中断读取-无参数
  2. FileReader.readAsArrayBuffer(file)
    将文件读取为ArrayBuffer 对象 参数:文件
  3. FileReader.readAsBinaryString(file)
    将文件读取为二进制码 - 参数:文件
  4. FileReader.readAsDataURL(file)
    将文件读取为DataURL 参数:文件
  5. FileReader.readAsText(file)
    将文件读取为文本 参数:文件

废话不多说,我们通过代码来更直观点认识上面的属性和方法。回归到需求,做一个图片预览功能。首先理一理我们需要有的东西,第一要素当然是文件(文件选择器),第二当然是预览(容器)。

html 代码 (样式我顺手加上了)

<!DOCTYPE html>
<html>
<head>
    <title>Cboyce-HTML5图片预览</title>
    <style type="text/css">
        /*主容器*/
        .container{
            width: 90%;
            margin-top: 20px;
        }
        /*图片预览容器*/
        .container .img-prev-container{
            width: 200px;
            height: 100px;
            margin:10px auto;
            border:1px solid #ccc;
        }
        /*预览图片样式*/
        .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(){
    //触发 change 事件
    GetDomById('fileSelecter').onchange = function(event){
        //获取文件对象
        var file = event.target.files[0];

        //创建reader对象
        var reader = new FileReader();
        
        //读取完成后触发
        reader.onload = function(ev){
            //获取图片的url
            var _img_src = ev.target.result;
            console.log(_img_src)
            //添加预览图片到容器框
            var img  = document.createElement('img');
            img.setAttribute('src',_img_src);
            GetDomById('img-perv-div').appendChild(img);
        }
        //获取到数据的url 图片将转成 base64 格式
        reader.readAsDataURL(file);
    }
}
//简化 document.getElementById() 函数
function GetDomById(id){
    return document.getElementById(id);
}

细节注意:这里的图片格式默认转为 base64

base64格式

补充说明:

event.target 属性,其特点在我们的代码中其实不忙看出来 "捕获当前事件作用的对象",通俗点来讲就是,谁触发了该事件,我就能通过该事件的 target 拿到谁。

其实上述代码还有一个小 bug 换图变成多图。 请看下图

换图变成多图

修复:改造 onload

reader.onload = function(ev){
    //获取图片的url
    var _img_src = ev.target.result;
    //预览图的容器
    var _img_container = GetDomById('img-perv-div')
    //添加预览图片到容器框
    var _imgs = _img_container.getElementsByTagName('img');
    //容器中没有则创建,有则修改 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

三、实现拖拽预览

上面我们已经把基础功能给完成了,接下来我们给该程序加个拓展--拖拽图片到预览框自动加载。
要完成该功能还是得靠 HTML5 的 Drag 和 drop。如果你还搞不清楚我们要做什么,那我们先来看下最终效果。

拖拽预览

在代码开始之前我们先来了解两个实现该功能最为关键的事件。

  1. dragover
    拖拽一个对象到目标对象上面触发该事件
  2. drop 拖放事件结束时触发。通俗来讲就是当我们拖拽一个对象到目标对象上后放开(松开鼠标左键)该对象的时候触发

接下来我们来看下代码,这里也对之前的代码做出了一些改造

window.onload = function(){

    //预览图的容器
    var _img_container = getDomById('img-perv-div')
    //创建reader对象
    var reader = new FileReader();

    //触发 change 事件
    getDomById('fileSelecter').onchange = function(event){
        //获取文件对象
        var file = event.target.files[0];

        //读取完成后触发
        reader.onload = function(ev){
            //获取图片的url
            var _img_src = ev.target.result;
            //添加预览图片到容器框
            showPrevImg(_img_container,_img_src);
        }
        //获取到数据的url 图片将转成 base64 格式
        reader.readAsDataURL(file);
    }

    //添加拖放支持
    _img_container.addEventListener('dragover',function(ev){
        ev.preventDefault();//阻止默认事件。比如说Chrome是直接将图片用浏览器打开
    },false)

    _img_container.addEventListener('drop',function(ev){
        ev.preventDefault();
        reader.onload = function(ev){
            //获取图片的url
            var _img_src = ev.target.result;
            
            //图片预览处理
            showPrevImg(_img_container,_img_src);
            
        }
        reader.readAsDataURL(ev.dataTransfer.files[0])

    },false)
}
//简化 document.getElementById() 函数
function getDomById(id){
    return document.getElementById(id);
}
//图片预览处理函数
function showPrevImg(_img_container,_img_src){
    //添加预览图片到容器框
    var _imgs = _img_container.getElementsByTagName('img');
    //容器中没有则创建,有则修改 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)

这段代码重点在于 ev.preventDefault(); 阻止默认行为,如果我们不阻止其默认行为将会产生下面的后果

不阻止默认行为

接下来要做的就是拖放结束展示图片预览效果

_img_container.addEventListener('drop',function(ev){
    ev.preventDefault();
    reader.onload = function(ev){
        //获取图片的url
        var _img_src = ev.target.result;
        
        //添加预览图片到容器框
        showPrevImg(_img_container,_img_src);
        
    }
    reader.readAsDataURL(ev.dataTransfer.files[0])

},false)

这里用到 event.dataTransfer 我们补充一下,我们先来看下他的定义

dataTransfer 拖曳数据传递对象,其提供了对于预定义的剪贴板格式的访问,以便在拖曳操作中使用

通俗来讲就是,我们在拖曳操作中可以使用它来操作我们拖曳的对象。比如拖图片,通过它能拿到我们所拖曳的图片对象

最后,强迫症犯了,稍微写了点样式美化了一下完整代码如下

<!DOCTYPE html>
<html>
<head>
    <title>Cboyce-HTML5图片预览</title>
    <style type="text/css">
        body{
            font-family: '微软雅黑';
        }
        /*主容器*/
        .container{
            width: 90%;
            margin-top: 20px;
        }
        /*每一个图片预览项容器*/
        .img-prev-item{
            width: 200px;
            height: 200px;
            display: inline-block;
            border:1px solid #ccc;
            text-align: center;
            border-radius: 3px;
        }
        /*图片预览容器*/
        .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
        }
        /*预览图片样式*/
        .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(){

            //预览图的容器
            var _img_container = getDomById('img-perv-div')
            //创建reader对象
            var reader = new FileReader();

            //触发 change 事件
            getDomById('fileSelecter').onchange = function(event){
                //获取文件对象
                var file = event.target.files[0];

                //读取完成后触发
                reader.onload = function(ev){
                    //获取图片的url
                    var _img_src = ev.target.result;
                    //添加预览图片到容器框
                    showPrevImg(_img_container,_img_src);
                }
                //获取到数据的url 图片将转成 base64 格式
                reader.readAsDataURL(file);
            }

            //添加拖放支持
            _img_container.addEventListener('dragover',function(ev){
                //ev.stopPropagation();
                ev.preventDefault();//阻止默认事件。比如说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;
                    
                    //添加预览图片到容器框
                    showPrevImg(_img_container,_img_src);
                    
                }
                reader.readAsDataURL(ev.dataTransfer.files[0])

            },false)
        }
        //简化 document.getElementById() 函数
        function getDomById(id){
            return document.getElementById(id);
        }
        function showPrevImg(_img_container,_img_src){
            _img_container.innerHTML="";
            //添加预览图片到容器框
            var _imgs = _img_container.getElementsByTagName('img');
            //容器中没有则创建,有则修改 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);
            }
        }
        //接下来要做的就是拖放结束展示图片预览效果
    </script>
</body>
</html>

运行效果如下

简单美化

四、结语

基本上实现以及代码的原理也就解释到这了。其实前端做的图片预览功能大多数需求是用来上传到服务器的。不得不提到的是这里的拖拽预览虽然看起来体验不错,但是要将该文件上传就得做一些特殊处理。这个我就留到后面的博客再讲了,有问题的朋友可以直接留言。

限于笔者技术,文章观点难免有不当之处,希望发现问题的朋友帮忙指正,笔者将会及时更新。也请转载的朋友注明文章出处并附上原文链接,以便读者能及时获取到文章更新后的内容,以免误导读者。笔者力求避免写些晦涩难懂的文章(虽然也有人说这样显得高逼格,专业),尽量使用简单的用词和例子来帮助理解。如果表达上有好的建议的话也希望朋友们在评论处指出。
本文为作者原创,转载请注明出处! 东野文然

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容