高级前端进阶(六)

  • 高级前端进阶(六)已关闭评论
  • 136 次浏览
  • A+
所属分类:Web前端
摘要

注意点:图片不能跨域!!!我们知道,XHR操作是异步的,只有在onload方法里面才能获取到Blob,相应的业务代码也要写到里面。怎么能够做到调用这个方法,直接得到Blob结果呢?
Promise便解决了诸如此类的痛点。


最近有个需求,就是上传图片的时候,图片过大,需要压缩一下图片再上传。
需求虽然很容易理解,但要做到,不是那么容易的。
这里涉及到的知识有点多,不多说,本篇博客有点重要呀! 高级前端进阶(六)

一、图片URL转Blob(图片大小不变)

注意点:图片不能跨域!!!

方式一:通过XHR请求获取

function urlToBlobByXHR(url) {     const xhr = new XMLHttpRequest();     xhr.open("get", url);     xhr.responseType = "blob"; // 设置响应请求格式     xhr.onload = (e) => {         if (e.target.status == 200) {             console.log(e.target.response); // e.target.response返回的就是Blob。             return e.target.response;// 这样是不行的         }         else {             console.log("异常");         }     };     xhr.send(); } urlToBlobByXHR("图片URL"); // 调用 

我们知道,XHR操作是异步的,只有在onload方法里面才能获取到Blob,相应的业务代码也要写到里面。怎么能够做到调用这个方法,直接得到Blob结果呢?
Promise便解决了诸如此类的痛点。

function urlToBlobByXHR(url) {     return new Promise((resolve, reject) => {         const xhr = new XMLHttpRequest();         xhr.open("get", url);         xhr.responseType = "blob";         xhr.onload = (e) => {             if (e.target.status == 200) {                 resolve(e.target.response); // resolve             }             else {                 reject("异常"); // reject             }         };         xhr.send();     }) } async f() {     try {     console.log(await urlToBlobByXHR(this.imgUrl)); // 直接返回Blob   } catch (e) {     console.log(e);   } } f(); // 调用 

方式二:通过canvas转化(图片大小会变大很多)

基本原理:就是新建一个canvas元素,然后在里面将图片画上去,接着利用canvas转为Blob。

function canvasToBlob(imgUrl) {     return new Promise((resolve, reject) => {         const imgObj = new Image();         imgObj.src = imgUrl;         imgObj.onload = () => {             const canvasObj = document.createElement("canvas");             const ctx = canvasObj.getContext("2d");             canvasObj.width = imgObj.naturalWidth;             canvasObj.height = imgObj.naturalHeight;             ctx.drawImage(imgObj, 0, 0, canvasObj.width, canvasObj.height);             canvasObj.toBlob((blob) => {                 resolve(blob);             });         }     }) }  const blobCanvas = await canvasToBlob(imgUrl); // 调用,直接获取到blob 

不过呢,利用canvas转化,图片会变大很多,在canvas上面画图片,期间图片分辨率会改变,加上可能还有图片解析的原因,会导致图片变大很多。
而且canvas是可以截图的,不过这一点是人为可以控制的。

二、图片压缩

原理:我们知道在canvas里面画图,canvas相当于图片的容器,既然是容器,那便可以控制容器的宽高,相应的改变图片的宽高,通过这一点,不就可以缩小图片了吗?
不过要注意的是,缩小图片要等比例的缩小,虽然提供的接口里面支持更改图片清晰度,但个人并不建议这么做,至于原因自己想吧。高级前端进阶(六)

版本一:

// imageUrl:图片URL,图片不能跨域 // maxSize:图片最大多少M // scale:图片放大比例 function compressImg1(imageUrl, maxSize = 1, scale = 0.8, imgWidth, imgHeight) {     let maxSizeTemp = maxSize * 1024 * 1024;     return new Promise((resolve, reject) => {         const imageObj = new Image();         imageObj.src = imageUrl;         imageObj.onload = () => {             const canvasObj = document.createElement("canvas");             const ctx = canvasObj.getContext("2d");             if (imgWidth && imgHeight) { // 等比例缩小                 canvasObj.width = scale * imgWidth;                 canvasObj.height = scale * imgHeight;             }             else {                 canvasObj.width = imageObj.naturalWidth;                 canvasObj.height = imageObj.naturalHeight;             }             ctx.drawImage(imageObj, 0, 0, canvasObj.width, canvasObj.height);             canvasObj.toBlob((blob) => {                 resolve({ blob, canvasObj });             });         }     }).then(({ blob, canvasObj }) => {         if (blob.size / maxSizeTemp < maxSize) {             let file = new File([blob], `test${imageUrl.substring(imageUrl.lastIndexOf("."))}`);             return Promise.resolve({ blob, file });         }         else {             return compressImg1(imageUrl, maxSize, scale, canvasObj.width, canvasObj.height); // 递归调用         }     }) } const { blob } = await compressImg1("图片地址"); // 调用 

需求是实现了,但用到了递归,性能完全由缩小比例跟图片大小决定。
图片过大的话或者缩小比例大了点,会导致不断递归,性能低下,这是肯定的。
以上还有两个耗时的操作:
1、不断请求图片
2、不断操作DOM

版本二:

有个潜规则,能不用递归就不用递归。
试想,怎样一步到位可以把图片缩小到需要的大小呢?再深入直接一点,如何得到有效的scale,等比例缩小后就能使图片缩小到想要的程度呢?
然后再把以上两个耗时操作再优化一下,只需加载一次图片。便得到了版本二。

function compressImg2(imageUrl, maxSize = 1, scale = 1) {     let maxSizeTemp = maxSize * 1024 * 1024;     return new Promise((resolve, reject) => {         const imageObj = new Image(); // 只需加载一次图片         imageObj.src = imageUrl;         imageObj.onload = () => {             const canvasObj = document.createElement("canvas"); // 只需创建一次画布             const ctx = canvasObj.getContext("2d");             canvasObj.width = imageObj.naturalWidth;             canvasObj.height = imageObj.naturalHeight;             ctx.drawImage(imageObj, 0, 0, canvasObj.width, canvasObj.height);             canvasObj.toBlob((blob1) => {                 resolve({ imageObj, blob1, canvasObj, ctx });             });         }     }).then(({ imageObj, blob1, canvasObj, ctx }) => {         if (blob1.size / maxSizeTemp < maxSize) {             let file = new File([blob1], `test${imageUrl.substring(imageUrl.lastIndexOf("."))}`);             return Promise.resolve({ blob: blob1, file });         }         else {             const ratio = Math.round(blob1.size / maxSizeTemp); // 比例             canvasObj.width = (imageObj.naturalWidth / ratio) * scale; // 比例调整             canvasObj.height = (imageObj.naturalHeight / ratio) * scale;             ctx.drawImage(imageObj, 0, 0, canvasObj.width, canvasObj.height);             return new Promise((resolve) => {                 canvasObj.toBlob((blob2) => {                     let file = new File([blob2], `test${imageUrl.substring(imageUrl.lastIndexOf("."))}`);                     resolve({ blob: blob2, file });                 });             })         }     }) } 

版本三(Promise转为async await)

我们知道Promise跟asnc await是等价的。

async function compressImg(imageUrl, maxSize = 1, scale = 1) {     let maxSizeTemp = maxSize * 1024 * 1024;     const { imageObj, blob1, canvasObj, ctx } = await new Promise((resolve, reject) => {         const imageObj = new Image();         imageObj.src = imageUrl;         imageObj.onload = () => {             const canvasObj = document.createElement("canvas");             const ctx = canvasObj.getContext("2d");             canvasObj.width = imageObj.naturalWidth;             canvasObj.height = imageObj.naturalHeight;             // console.log(canvasObj);             ctx.drawImage(imageObj, 0, 0, canvasObj.width, canvasObj.height);             canvasObj.toBlob((blob1) => {                 // console.log('blob1', blob1);                 resolve({ imageObj, blob1, canvasObj, ctx });             });         };     });     if (blob1.size / maxSizeTemp < maxSize) {         let file = new File([blob1], `test${imageUrl.substring(imageUrl.lastIndexOf("."))}`);         return Promise.resolve({ blob: blob1, file });     }     else {         // const ratio = Math.round(Math.sqrt(blob1.size / maxSizeTemp));         const ratio = Math.round(blob1.size / maxSizeTemp);         // console.log('ratio', ratio);         canvasObj.width = (imageObj.naturalWidth / ratio) * scale;         canvasObj.height = (imageObj.naturalHeight / ratio) * scale;         // console.log(canvasObj);         ctx.drawImage(imageObj, 0, 0, canvasObj.width, canvasObj.height);         const { blob: blob2, file } = await new Promise((resolve) => {             canvasObj.toBlob((blob2) => {                 // console.log('blob2', blob2);                 let file = new File([blob2], `test${imageUrl.substring(imageUrl.lastIndexOf("."))}`);                 resolve({ blob: blob2, file });             });         })         return { blob: blob2, file };     } } 

三、详细讲解下Promise

简单的一个例子

let p = new Promise((resolve) => {   setTimeout(() => {     resolve(123456); // 5秒后输出123456   }, 5000); }); p.then((s) => {   console.log(s); // 通过then的参数就可以获取到结果 });  let s = await p; // async await转换,简化then写法 console.log(s); 

其实呢,Promise本质上就是回调函数的使用,而Promise主要是为了解决回调地狱(回调函数嵌套)而出现的,async await写法主要是为了简化方便。

咱来模拟一下最简单的Promise,手写一个简单一点的。

// 首先定义一下Promise状态 const status = {   pending: "pending",   fulfilled: "fulfilled",   rejected: "rejected", }; 

不支持异步(先来个简单的)

function MyPromise(executor) {   const self = this;// this指向   self.promiseStatus = status.pending;   self.promiseValue = undefined;   self.reason = undefined;   function resolve(value) {     if (self.promiseStatus == status.pending) {       self.promiseStatus = status.fulfilled;       self.promiseValue = value;     }   }   function reject(reason) {     if (self.promiseStatus == status.pending) {       self.promiseStatus = status.rejected;       self.reason = reason;     }   }   try {     executor(resolve, reject); // 在这里比较难以理解,函数resolve作为函数executor的参数,new MyPromise调用的时候,传的也是个函数。   } catch (e) {     reject(e);   } } MyPromise.prototype.then = function (onResolve, onReject) { // 利用原型添加方法   const self = this;   if (self.promiseStatus == status.fulfilled) {     onResolve(self.promiseValue);   }   if (self.promiseStatus == status.rejected) {     onReject(self.reason);   } }; // 调用 const myPromise = new MyPromise((resolve, reject) => { // MyPromise的参数也是个函数   resolve(123456); // 暂时不支持异步 }); myPromise.then((data) => {   console.log("data", data); // 输出123456 }); 

支持异步的

function MyPromise(executor) {   const self = this;   self.promiseStatus = status.pending;   self.promiseValue = undefined;   self.reason = undefined;   self.onResolve = [];   self.onReject = [];   function resolve(value) {     if (self.promiseStatus == status.pending) {       self.promiseStatus = status.fulfilled;       self.promiseValue = value;       self.onResolve.forEach((fn) => fn(value)); //支持异步     }   }   function reject(reason) {     if (self.promiseStatus == status.pending) {       self.promiseStatus = status.rejected;       self.reason = reason;       self.onReject.forEach((fn) => fn(reason)); // //支持异步     }   }   try {     executor(resolve, reject);   } catch (e) {     reject(e);   } } MyPromise.prototype.then = function (onResolve, onReject) {   const self = this;   if (self.promiseStatus == status.fulfilled) {     onResolve(self.promiseValue);   }   if (self.promiseStatus == status.rejected) {     onReject(self.reason);   }   if (self.promiseStatus == status.pending) {     self.onResolve.push(onResolve);     self.onReject.push(onReject);   } }; // 调用 const myPromise = new MyPromise((resolve, reject) => {   setTimeout(() => {     resolve(123456); // 异步   }, 3000); }); myPromise.then((data) => {   console.log("data", data); // 输出123456 }); 

个人觉得,能明白大致原理,会用就行了,至于能不能手写一个Promise并不是很重要的,不断重复造轮子没啥意思,
但是呢,理解其大概思路以及实现所用到的思想还是很重要的,对成长的帮助很大。

总结

图片压缩还有待优化
Promise,大家应该都很熟悉,用的非常多,可真正会用的人并不是太多的。高级前端进阶(六)

高级前端进阶(六)

最后,祝大家中秋快乐!