这篇文章写了什么:
- 上传图片本地预览
- 获取图片宽高
- 获取图片大小
- 获取图片旋转信息
- 旋转图片
- 图片提前占位
- 图片error自动重试
- 图片重试之后显示默认图
- 图片复制
- 图片拖拽
斜体部分下一篇讲
一、上传图片本地预览
想要在上传的时候在本地预览图片,就不得不提到这两个对象 FileList
和 FileReader
1. FileList
前端想要拿到用户文件,可以通过 FileList
对象:
一个 FileList 对象通常来自于一个 HTML 元素的 files 属性,你可以通过这个对象访问到用户所选择的文件。该类型的对象还有可能来自用户的拖放操作,这个我们后面说拖拽的时候会再详细介绍。
通过元素拿到 FileList
对象:
<input id="fileInput" type="file">
var file = document.getElementById('fileInput').files[0];
获取 FileList
之后可以获取到里面的 File
对象,File
继承了 Blob
接口的属性:
File.name
返回当前 File 对象所引用文件的名字。File.size
返回文件的大小。可以使用它限制上传文件的大小。File.type
返回文件的多用途互联网邮件扩展类型(MIME Type)。可以使用它判断上传文件的类型。File.lastModified
返回当前 File 对象所引用文件最后修改时间,自 UNIX 时间起始值(1970年1月1日 00:00:00 UTC)以来的毫秒数。File.lastModifiedDate
返回当前 File 对象所引用文件最后修改时间的 Date 对象。
File
对象继承自 Blob
对象:
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
要从其他非blob对象和数据构造一个 Blob,请使用Blob()
构造函数。要创建一个 blob 数据的子集 blob,请使用slice()
方法。
要获取用户文件系统上的文件对应的 Blob 对象,可以使用input
。
当然,input 标签还支持多选 multiple
属性 以及 accept
:
该属性表明了服务器端可接受的文件类型,该属性的值必须为一个逗号分割的列表,包含了多个唯一的内容类型声明:
以 STOP 字符 (U+002E) 开始的文件扩展名。(例如:".jpg,.png,.doc")
一个有效的 MIME 类型,但没有扩展名
audio/ 表示音频文件
video/ 表示视频文件
image/* 表示图片文件
2. FileReader
通过 input
元素拿到文件对象之后,接下来是将图片进行预览。 想要在本地页面中显示本地图片,需要用到 FileReader
对象:
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。 其中File对象可以是来自用户在一个元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行
mozGetAsFile()
方法后返回结果。
重要提示: FileReader仅用于以安全的方式从用户(远程)系统读取文件内容 它不能用于从文件系统中按路径名简单地读取文件。 要在JavaScript中按路径名读取文件,应使用标准Ajax解决方案进行服务器端文件读取,如果读取跨域,则使用CORS权限。
FileReader
具有以下属性:
FileReader.error
只读一个DOMException,表示在读取文件时发生的错误 。FileReader.readyState
只读表示FileReader状态的数字。取值如下:
EMPTY 0 还没有加载任何数据
LOADING 1 数据正在被加载
DONE 2 已完成全部的读取请求
FileReader.result
只读文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。
FileReader
具有以下事件:
FileReader.onabort
处理abort事件。该事件在读取操作被中断时触发。FileReader.onerror
处理error事件。该事件在读取操作发生错误时触发。FileReader.onload
处理load事件。该事件在读取操作完成时触发。FileReader.onloadstart
处理loadstart事件。该事件在读取操作开始时触发。FileReader.onloadend
处理loadend事件。该事件在读取操作结束时(要么成功,要么失败)触发。FileReader.onprogress
处理progress事件。该事件在读取Blob时触发。
FileReader
具有以下方法:
FileReader.abort()
中止读取操作。在返回时,readyState属性为DONE。FileReader.readAsArrayBuffer()
开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.FileReader.readAsBinaryString()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含所读取文件的原始二进制数据。FileReader.readAsDataURL()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的Base64字符串以表示所读取文件的内容。FileReader.readAsText()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。
获取用户的文件并显示:
<body>
<input type="file" id="file" onchange="showPreview(this)" />
<img src="" id="image" />
<script>
function showPreview(event) {
var file = event.files[0]
var fr = new FileReader()
fr.onloadend = function (e) {
document.getElementById('image').src = e.target.result
}
fr.readAsDataURL(file)
}
</script>
</body>
当然,也可以包成一个Promise。
3. 根据图片本地path进行显示
背景是在Electron中,拿到了图片的本地path,如何进行展示? 在Electron中可以直接使用node的fs模块读取图片内容,然后转成上面提到的File对象,按照上面的方法显示即可:
fs.readFile('path', (err, data) => {
console.log({data})
})
var file = new File([data], 'name.jpg', { type: 'image/jpg' });
4. 获取图片大小(size)
有的场景上传图片的时候需要限制图片的大小,或者是图片大小需要传给后端 前面有提到,通过inpu标签获取的File对象里面会包含有图片的size属性。在Electron中,获取了文件的path,可以使用fs.stats()
获取文件的size
二、获取图片宽高
在上传图片的时候需要传递图片的实际宽高给后端
1. Image
new Image(width, height)
函数将会创建一个新的HTMLImageElement实例。 它的功能等价于 document.createElement('img')
无论构造函数中指定的width, height
是多少,都会加载整个位图. 如果在构造时指定了尺寸信息,那么将会反应在实例的 HTMLImageElement.width 和 HTMLImageElement.height 属性上。图像自身的CSS像素值将会反应在HTMLImageElement.naturalWidth
和HTMLImageElement.naturalHeight
属性。如果没有指定值,那么两个属性的值相同。
2. naturalWidth/naturalHeight
naturalWidth是HTMLImageElement接口的只读属性,返回图像的固有,经过密度校正的宽度(以CSS像素为单位)。这是在没有任何限制其宽度的情况下绘制图像的宽度。如果您既未指定图像的宽度,也未将图像放置在限制或明确指定图像宽度的容器内,则这是图像宽度的CSS像素数。 相应的naturalHeight方法返回图像的自然高度。 兼容性如图:
这个值和width/height不一样,换句话说,用CSS设置的宽高就是width/height。
3. 获取宽高
那最后获取图片宽高的方法(传入一个img元素):
function getImgNaturalDimensions(img, callback) {
if (img.naturalWidth) {
callback({ width: img.naturalWidth, height: img.naturalHeight })
} else { // IE6/7/8
var imgae = new Image()
image.src = img.src
image.onload = () => {
callback({ width: image.width, height: image.height })
}
image.onerror = () => {
// error
}
}
}
三、获取图片旋转信息
这是一个bug引起的思考,我们系统里面用户上传的图片,发现在浏览器里面显示是被旋转过的,在Android和iOS端都是正常的:
原因:在我们加载图片的时候,浏览器有时候加载带有旋转信息的图像文件的时候方向有旋转的偏差。浏览器会根据图片本身的元数据进行渲染。会忽略EXIF里面的旋转信息 Orientation,永远展示的是 Orientation 为1的方向。所以我们需要读取图片的旋转信息,手动转一下图片
1. EXIF
可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的文件格式,可以记录数码照片的属性信息和拍摄数据。
Exif最初由日本电子工业发展协会在1996年制定,版本为1.0。1998年,升级到2.1版,增加了对音频文件的支持。2002年3月,发表了2.2版。
Exif可以附加于JPEG、TIFF、RIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。
Exif信息是可以被任意编辑的,因此只有参考的功能。
Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kB,而内部采用TIFF格式。
Exif 信息是镶嵌在 JPEG/TIFF 图像文件格式内的一组拍摄参数。需要注意的是 EXIF 信息是不支持 png,webp 等图片格式的 Windows 上通过鼠标右键点击图片打开菜单,点击属性并切换到详细信息标签下即可直接查看Exif信息 MAC 上打开图片=>菜单栏工具=>显示检查器
下面的图显示了Exif会包含的部分信息:
其中一项是 Orientation ,用于记录拍摄时相机物理旋转角度,例如把相机倒过来 Orientation 是3,顺时针竖起来 Orientation 是6,逆时针竖起来 Orientation 是8,正常模式 Orientation 是1。根据这个属性我们可以使用Canvas来对图片重绘。
从图中可以看出1、3、6、8是旋转;2、4、5、7是翻转,不太常见。
那这里就有两个问题,第一是怎么获取图片的Exif信息,第二是怎么旋转。具体做的时候,我们可以使用https://github.com/exif-js/exif-js这个库来获取Exif信息
2. 旋转图片
至于旋转图片,我们可以使用canvas。大概的代码如下,里面有详细的注释:
// 导入插件
import EXIF from 'exif-js'
// 使用exif-js获取Orientation(传入img元素)
EXIF.getData(img, function() {
Orientation = EXIF.getTag(this, 'Orientation') // 指定获取Orientation
ctxWidth = this.naturalWidth
ctxHeight = this.naturalHeight
// 创建画布
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 设置画布宽高
canvas.width = ctxWidth
canvas.height = ctxHeight
// 判断是否需要交换宽高,根据前面的图,需要交换宽高的场景只有5、6、7、8
if ([5, 6, 7, 8].includes(Orientation)) {
canvas.width = ctxHeight
canvas.height = ctxWidth
}
// 判断方向,旋转画布
switch (Orientation) {
case 2:
ctx.transform(-1, 0, 0, 1, ctxWidth, 0)
break
case 3:
ctx.transform(-1, 0, 0, -1, ctxWidth, ctxHeight)
break
case 4:
ctx.transform(1, 0, 0, -1, 0, ctxHeight)
break
case 5:
ctx.transform(0, 1, 1, 0, 0, 0)
break
case 6:
ctx.transform(0, 1, -1, 0, ctxHeight, 0)
break
case 7:
ctx.transform(0, -1, -1, 0, ctxHeight, ctxWidth)
break
case 8:
ctx.transform(0, -1, 1, 0, 0, ctxWidth)
break
default:
ctx.transform(1, 0, 0, 1, 0, 0)
}
ctx.drawImage(img, 0, 0, ctxWidth, ctxHeight);
// 输出图片的base64
base64 = canvas.toDataURL('image/jpeg', 0.9) // 0.9是图片质量
})
3. image-orientation
既然图片会有Orientation这个属性,那浏览器原生有没有什么方法可以显示带有此类信息的图片呢? 有一个实验中的CSS属性: image-orientation 用来修正某些图片的预设方向
该属性不是用来对图片进行任意角度旋转的, 它是用来修正那些带有不正确的预设方向的图片的. 因此该属性值会被四舍五入到 90 度的整数倍.
但是兼容性比较差!
参考资料
- EXIF: https://zh.wikipedia.org/wiki/EXIF
- File: https://developer.mozilla.org/zh-CN/docs/Web/API/File
- FileList: https://developer.mozilla.org/zh-CN/docs/Web/API/FileList
- FileReader: https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
- Image: https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement/Image
- naturalWidth: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalWidth
- Orientation说明: https://www.impulseadventure.com/photo/exif-orientation.html
- image-orientation: https://developer.mozilla.org/zh-CN/docs/Web/CSS/image-orientation