图片预览

需求

今天来看一个挺常见的需求,上传图片时页面展示选择的图片,具体实现效果可以点击下面的「上传图片」按钮试试👇~(只能选择 .png 或 .jpeg 格式图片)

按钮设计源自 CodePen

实现

HTML

首先是要使用的标签,上传文件需要用到 <input> 标签,并且指定属性 type 的值为 file

既然是预览图片,那么还需要指定选择文件的类型,使用 accept 属性可以规定期望的文件类型,为啥是「期望」呢?因为这只表示触发控件后默认允许选中的文件类型,比如在 MacOS 上,刁钻的用户仍可以在唤出的选择文件窗口中通过选项➡️格式:所有文件来选择其它类型的文件上传👿:

choose-file.png◎ 仍可选择非指定类型的文件

这就要求在 JavaScript 中还要判断用户选择的文件类型是否合法,后面会说到,先指定期望的类型为 PNG 或 JPG 格式的图片。并且,使用 <input> 标签时常用的做法是与 <label> 标签合用并且设置 <input> 为不可见,这样做的好处有两点:

  1. <input> 的样式不易调整,而 <label> 的样式可以为所欲为。
  2. <label> 增加了 <input>的命中区域。

所以现在的 HTML 是这样的:

1
2
<input type="file" accept="image/png, image/jpg" id="inputFile">
<label for="inputFile">上传图片</label>

CSS

样式上要做的一点是隐藏 <input>,剩下的就完全自由化了:

1
2
3
input {
  display: none;
}

另外,CodePen 上的各种按钮样式可真是美哭我了,比那些在线设计按钮样式的网站好太多了🤫,强烈安利。

JavaScript

到了功能实现的环节了,首先需要拿到选择图片的相关信息,在用户选择了文件时可以触发 <input>change 事件,change 事件的参数内包含一个属性 files,其对应的值 FileList 是一个类数组,由一个个 File 对象组成,对象内存储着用户选择的文件的信息,比如名字、大小、类型等。

因为 <input> 有一个 multiple 属性,设置后可以选择多个文件,所以 FileList 的类型为类数组。

能获取到文件的类型就可以做我们上面说的校验文件合法性了:

1
2
3
4
5
6
7
8
document.getElementById('inputFile').addEventListener('change', handleChange, false);

function handleChange(e){
  const fileList = e.target.files; // 文件对象类数组
  if(fileList[0].type !== 'image/png' && fileList[0].type !== 'image/jpeg'){ // 校验文件类型
      return console.log('文件类型错误');
  }
}

接下来需要在页面中显示图片,需要一个展示图片的容器,当然也可以使用 JavaScript 直接插入到页面,但前者更容易设置图片的样式。显示图片有两种方式——FileReader对象 URL

1. FileReader

FileReader 对象用来异步读取文件的内容,可读取的目标正是获取到的 File 对象。

1
2
3
4
5
const reader = new FileReader(); // 实例化对象
reader.readAsDataURL(fileList[0]); // 开始异步读取文件
reader.onload = function (e) { // 读取完文件后触发的函数
  document.getElementById('previewImg').src = e.target.result; // 将图片的 base64 编码赋值给容器的 src 属性
}

readAsDataURL() 方法可读取传入的 File 对象,读取完成后会触发 onload 事件,事件的参数中就包含图片的 base64 编码,然后将它设置为页面中用于展示图片的 <img> 标签的 src 属性,图片成功显示~

2. 对象 URL

对象 URL 指的是 window.URL.createObjectURL()window.URL.revokeObjectURL() 方法,window.URL.createObjectURL() 方法同样可传入一个 File 对象,但是返回的并不是图片的 base64 编码,而是一个特殊的字符串[1],长这个亚子:

blob:null/1a92aff8-90a4-4421-966c-4b151078da0f

不管它叫啥,它只能由浏览器内部生成,将 <img> 标签的 src 属性设置成它也可以显示图片:

1
document.getElementById('previewImg').src = (URL || webkitURL).createObjectURL(fileList[0]);

(URL || webkitURL).createObjectURL() 为兼容写法。

结语

效果就不用再展示了,完整方法和上面炫酷按钮的案例源码都在我的 GitHub 中。那么到底要选择使用哪个方式呢?

首先,这两个 API 的兼容性都是差不多的,IE 10+ 和所有主流浏览器都支持。从性能上来说,FileReader.readAsDataURL() 是异步执行,而 createObjectURL() 是同步执行,而 Blob URLs 占用的内存比 base64 更小,所以总体来讲 createObjectURL() 占优,但其关闭标签页时才会释放内存,使用的时候应考虑是否要用 window.URL.revokeObjectURL() 方法手动释放内存。

References & Resources

  1. 在 web 应用程序中使用文件 | MDN
  2. Preview an image before it is uploaded | Stack Overflow
  3. What is a blob URL and why it is used? | Stack Overflow

  1. W3C 称它为 Blob URLs,MDN 称它为 Object-URLs

updatedupdated2020-06-282020-06-28
fix: 错误说法修正
加载评论
点击刷新