据说很早以前浏览器是支持直接查看用户本地磁盘的文件(图片)的,但是由于隐私和安全性的问题被移除掉了。如果要实现 WEB 端的图片预览有两种方式可以选择:1、FLASH。2、先上传再查看。这两种方式都没有兼容性问题,但是 FLASH 这款软件存在了非常多的漏洞,而且性能不是太好,甚至 IOS 设备不支持使用 FLASH。第二种方式去除去上传的等待时间不算,如果用户主动放弃了上传的文件,服务器是不能及时删除已经被放弃的文件的,长久以往就造成了一种资源浪费。

HTML5 找到了痛症所在,提供了一种新的方式做到纯客户端的图片预览。在开始之前,要知道如何上传图片。HTML5 提供了一个 <input type="file" accept="images/*"> Camera API,我们能够很轻松的在手机端调起手机后置摄像头拍照。看起来这个标签只是增加了一个 accept 属性,表明了当前这个 input[type="file"] 只接受图片类型的文件。关于 accept 的属性值:

<input accept="file_extension|audio/*|video/*|image/*|media_type">  
属性值 对象
具体的文件扩展名文件以 .gif, .jpg, .png, .doc 结尾
audio/*所有的音频文件
video/*所有的视频文件
image/*所有的图片文件
媒体类型查看所有的媒体类型 [IANA Media Types]

由此可见,我们可以限制当前这个 input 多种文件类型,这其中就包括图片、视频、文本、文档等。在这里我们只考虑图片类型,默认接受所有的图片类型。 在移动端上,用户点击了当前这个标签浏览器会展现一个选择界面。用户可以选择是选择本地磁盘的文件还是调用手机摄像头。当用户勾选了本地磁盘的文件或者拍照完成之后, 当前的文件会被 input[type="file"] 所接受,并且触发 input[type="file"] 的 onchange 事件。

HTML5 实现图片预览

  • 一、使用 FileReader

点击下面的按钮使用 FileReader 预览。

使用 FileReader 对象,可以异步的读取存储在用户计算机上的文件内容。使用 File 对象或者 Blob 对象来处理指定所要处理的文件或数据。其中File对象可以是来自用户在一个 input 元素上选择文件后返回的 FileList 对象,也可以来自拖放操作生成的 DataTransfer 对象,还可以是来自在一个 HTMLCanvasElement 上执行 mozGetAsFile() 方法后的返回结果。

FileReader 可以将文件读取为二进制编码、文本、字符串。在图片预览的时候使用 readAsDataURL 方法。

方法名 描述
readAsBinaryString将文件读取为二进制编码
readAsText将文件读取为文本
readAsDataURL将文件读取为包含一个data: URL格式的字符串
abort中止该读取操作

获取到 input[type="file"] 传递的对象后调用 readAsDataURL 方法,将该文件转化为 Base64 编码的方式,动态创建一个 img 标签路径指向刚才转化的字符串。以一张大约 17m 左右的图片为例,Chrome 需要 2s,IE 和 firefox 则需要大约 2.7s。如果采用上传服务器在预览的方式,以 4m 网为例,大约需要需要 44s 优势就显而易见了。

<input type="file" id="file" onchange="imagePreview( this );" accept="image/*">  
<script>  
function imagePreview( elem ){  
    /* 获取文件类型 */
    const fileType = elem.files[0].type, 
    /* 获取时间戳 */
    timestamp = Date.now(),
    /* 创建文件 */
    image = document.createElement('img'), 
    /* 实例化文件读取 */
    reader = new FileReader();
    /* 错误处理 */
    if( /^image\//.test( fileType ) === false ) return;
    /* 图片展示 */
    reader.onload = function( e ){ 
        /* 获取时间 */
        const time = document.createElement('span');
        /* 添加路径  */
        image.src = e.target.result; 
        /* 插入元素 */  
        elem.parentNode.appendChild( image ); 
        /* 插入时间 */
        time.innerHTML = '时间:' + (Date.now() - timestamp);
        /* 插入时间 */
        elem.parentNode.appendChild( time );
    }
    /* 读取文件 */
    reader.readAsDataURL( elem.files[0] );
}
</script>  
  • 二、使用 Window.URL

window.URL 对象上挂载了两个方法 createObjectURLrevokeObjectURL。第一个方法用于给 File 对象创建一个临时的 URL 字符串对象,并且这个 URL 对象是唯一的,类似于 blob:null/d28d71bb-e22a-48cd-9748-8f2f8ff2fb9d,第二个方法用来释放被创建的 URL 对象减少内存使用。

属性名 属性值 描述
createObjectURLFile 对象给文件对象创建一个 URL
revokeObjectURLURL 对象释放被创建的 URL 对象

点击下面的按钮实现 Window.URL 图片预览。

在 window7 上主流浏览器 17m 图片预览时间表(ms):

浏览器 FileReader URL.createObjectURL
chrome49296380
firefox45271375
IE112545154
safari5.34Safari6.0 才支持Safari6.0 才支持

safari 这货看起来已经不爽 windows 了,windows 平台没有 safari 的 6.0 版本。结合上面的上传时间预览表可以很明显的看出使用 URL.createObjectURL 的方式远远快于使用 FileReader,所以在同等情况下应该优先使用 window.URL 方式。FileReader 为什么这么慢,就是他将文件转成了字符串,以图片为例就会被转成 base64 编码。而 base64 编码的一个缺点就是大,网页上不适合存储大量的文本内容,这会让网页性能降低。

  • 三、图片预览之图片上传

使用 FileReader 与 window.URL 能够做到纯客户端的图片预览,下面这个简单的 DEMO 综合了这两个方法。对于预览前的图片获取,提供了文件名称、文件大小、文件类型、最后修改时间等参数。对于大文件的预览,还可以使用 pending 事件,提供了此次上传所消耗的时间。

HTML5 实现图片预览

点此链接查看HTML5实现图片预览:HTML5 实现图片预览

仅仅做到客户端的图片预览还是不够的,如果需要在 web 中使用用户上传的图片,我们需要将文件传送到服务器上。XMLHttpRequest Level 2提供了一个新的接口 FormData,使用这个接口我们能够实现二进制文件上传,非常好的替代 flash 方案。FormData 以 key value 的形式存储数据,只接受一个参数,这个参数必须是表单元素,当然这个参数可以是不传的。

/* 表单 */
var formdata = new FormData(document.forms[0]);  

如果不使用参数的方式,FormData 提供了 append 方式来给 FormData 对象扩充。

/* 实例化 FormData */
var formdata = new FormData();  
/* 添加文件 */
formdata.append('file', document.getElementsByTagName('input')[0].files[0]);  

FormData 还提供了 has、 get、 set、 entries、 values 等数据操作方式,不过遗憾的是现代浏览器都没有支持,由于 FormData 上传文件是通过二进制方式上传的,所以我们并不能直接看到某个文件的『源码』。在上传大文件时提示上传进度用户体验度非常好,也许以前我们需要分割文件,分段上传来计算当前上传进度,但是 XMLHttpRequest.upload 属性提供了一个 XMLHttpRequestUpload 对象,我们可以使用它来显示当前文件上传的进度。

事件监听 Data type of response property
onloadstart开始传输
onprogress数据正在传输
onabort传输动作被取消
onerror传输失败
onload传输完成
ontimeout传输时间超出预计时间
onloadend传输完成(无论成功或者失败)

通过 upload 事件的监听,我们能够很方便的知道当前上传的进度。监听文件上传进度,我们需要用到 onprogress 事件,onprogress 的返回值有下面这些:

通过 xmlhttprequest.upload 监听文件上传

通过对已经传输的数据量 loaded 与总共需要传输的数据量 total 的比率,就是当前文件上传的进度,实现起来代码也很简单。 如果使用 jQuery 来上传图片一定要使用 type: 'POST', contentType: false, processData: false 来阻止jQuery 对数据编码。

// 实例化 FormData
var formData = new FormData(),  
// 实例化 XMLHttpRequest
http = new XMLHttpRequest(),  
// 获取文件
files = document.getElementsByTagName('input')[0].files[0];  
// 添加文件
formData.append('smfile', files);  
// 注册成功事件
http.onload = function( res ){ console.log( "返回值:" + http.responseText ); };  
// 开始请求
http.open('POST', 'https://sm.ms/api/upload', true);  
// 注册进度事件
http.upload.addEventListener('progress', function(event){  
    if(event.lengthComputable) {
      var percentComplete = event.loaded / event.total;
      console.log( "当前进度:" +  (percentComplete*100).toFixed(2) );
    }
}, false);
// 发送数据
http.send( formData );  

使用 FormData 文件上传效果预览:

点击此按钮实现 HTML5 图片上传进度查询,我的服务器空间有限,所以我调用了 sm.ms 这个图床服务器的 API 来完成这个DEMO。因此不能保证这个功能的稳定性。以前我们需要依赖 flash 或者复杂插件才能实现的图片预览上传,因为 HTML5 的存在,所以我们能够很方便的通过 API 来实现。这让我们把更多的精力放在了项目的其他方面。

完。