业务场景
试想这样的应用场景:
当我们上传本地图片时,需要校验图片的分辨率,只有水平分辨率不大于1920、垂直分辨率不大于1080的图片才允许上传。
后端处理方案
显然,我们是可以将分辨率校验交给后端的,这样的问题在于,后端校验需要前端先发起ajax请求,并且后端校验也需要一定的处理时间,就整体体验而言,用户等待时间较长,不利于用户体验。
因此还是推荐前端校验,直接给出结果。
难点所在
对于type='file'
的input框,在其change事件中,我们可以直接拿到file对象,包含了name、size等属性,但是并不包含分辨率数据。因为这时,图片尚未渲染到dom中,浏览器不知道这是一个图片,拿不到分辨率也可以理解。如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="file" accept="image/jpg,image/jpeg,image/png" id="fileInput" />
<script>
document
.getElementById("fileInput")
.addEventListener("change", handleFileChange, false);
function handleFileChange(e) {
console.log(e.target.files[0]);
}
</script>
</body>
</html>
破局:使用FileReader转换为base64格式
虽然原生的file对象拿不到图片的分辨率信息,但是借助于FileReader则可以读取到文件的内部数据,并转换为base64。
function handleFileChange(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
if (reader.readyState == 2) {
console.log(reader.result);
}
};
}
突破:使用Image对象获取分辨率
现在,已经拿到base64数据,那么只需要创建一个Image实例,并将其src 设置为base64数据地址,再监听onload事件就可以拿到分辨率了,如下:
function handleFileChange(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
if (reader.readyState == 2) {
const img = new Image();
img.src = reader.result;
img.onload = function () {
console.log(this.height, this.width);
};
}
};
}
时序:使用promise解决异步和同步问题
以上,我们虽然能够拿到数据,但是,获取分辨率的过程是异步的
,例如我们监听了reader.onload、img.onload
事件。显然,我们校验突破分辨率时,需要等待这些过程执行完,才能给出结果。而对于图片的校验很可能是同时还需要校验格式、文件大小,这些都被封装在校验函数中,而我们的校验分辨率的函数则应属于整个校验过程的一部分
。
即,整个校验函数中需要等待校验分辨率函数的结果,这是一个同步的过程
。
这并不困难,我们只需要使用es6的promise即可比较容易的达成这一目标,写法多样,下面提供一种示例:
function validRatio(file) {
return new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
if (reader.readyState == 2) {
const img = new Image();
img.src = reader.result;
img.onload = function() {
const bool = this.height > 1080 || this.width > 1920;
if (bool) {
resolve(false);
}
resolve(true);
};
}
};
});
},
async function validateFile(file) {
//...
const isRatioValid = await validRatio(file);
//...
}
如有其他更好的方案,欢迎指出!
全文完。