高级前端进阶(六)

博客 动态
0 139
羽尘
羽尘 2022-09-07 23:04:12
悬赏:0 积分 收藏

高级前端进阶(六)

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

一、图片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,大家应该都很熟悉,用的非常多,可真正会用的人并不是太多的。

最后,祝大家中秋快乐!
posted @ 2022-09-07 22:08 一曲风流唯少年 阅读(30) 评论(0) 编辑 收藏 举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员