最近有个需求,就是上传图片的时候,图片过大,需要压缩一下图片再上传。
需求虽然很容易理解,但要做到,不是那么容易的。
这里涉及到的知识有点多,不多说,本篇博客有点重要呀!
注意点:图片不能跨域!!!
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转为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跟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 };
}
}
简单的一个例子
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,大家应该都很熟悉,用的非常多,可真正会用的人并不是太多的。

最后,祝大家中秋快乐!
我目前还在上学,正在上一门关于用C++实现数据结构的类(class)。在业余时间,我喜欢使用“高级”语言(主要是Ruby和一些c#)进行编程。既然这些高级语言为你管理内存,你会用数据结构做什么?我可以理解对队列和堆栈的需求,但是您需要在Ruby中使用二叉树吗?还是2-3-4树?为什么?谢谢。 最佳答案 Sosincethesehigherlevellanguagesmanagethememoryforyou,whatwouldyouusedatastructuresfor?使用数据结构的主要原因与垃圾收集无关。但它是以某种方式有效的
在Rails3.x应用程序中,我正在使用net::ssh并向远程pc运行一些命令。我想向用户的浏览器显示实时日志。比如,如果两个命令在net中运行::ssh执行即echo"Hello",echo"Bye"被传递然后"Hello"应该在执行后立即显示在浏览器中。这是代码我在rubyonrails应用程序中使用ssh连接和运行命令Net::SSH.start(@servers['local'],@machine_name,:password=>@machine_pwd,:timeout=>30)do|ssh|ssh.open_channeldo|channel|channel.requ
我正在为Jekyll编写一个转换器插件,需要访问一些页眉(YAML前端)属性。只有内容被传递给主要的转换器方法,似乎无法访问上下文。例子:moduleJekyllclassUpcaseConverter关于如何在转换器插件中访问页眉数据有什么想法吗? 最佳答案 基于Jekyll源代码,无法在转换器中检索YAML前端内容。根据您的情况,我看到了两种可行的解决方案。您的文件扩展名可以具有足够的描述性,以提供您本应包含在前言中的信息。看起来Converter插件的设计就是这么基本的。如果修改Jekyll是一个选项,您可以更改Convert
一、简介之前在Vue项目中使用过element的上传组件,实现了点击上传+拖拽上传的两种上传功能。然后我就在想是否可以通过原生的html+js来实现文件的点击上传和拖拽上传,说干就干。首先是点击获取上传文件自然没的说,只需要借助input标签即可,但原生的点击上传按钮,实在是过于简陋,所以我的想法是通过一个div,模拟成上传按钮,然后监听其点击事件,通过input.click()去模拟点击真正的上传元素。然后是拖拽获取上传文件,这个稍有难度,我的想法是通过HTML5新增的drag拖放API+dataTransfer来实现文件的拖拽获取,但是由于是html5新增的,所以可能在某些低版本IE浏览器
一、介绍一下vercelvercel是一个站点托管平台,提供CDN加速,同类的平台有Netlify和GithubPages,相比之下,vercel国内的访问速度更快,并且提供Production环境和development环境,对于项目开发非常的有用的,并且支持持续集成,一次push或者一次PR会自动化构建发布,发布在development环境,都会生成不一样的链接可供预览。但是vercel只是针对个人用户免费,teams是收费的首先vercel零配置部署,第二访问速度比github-page好很多,并且构建很快,还是免费使用的,对于部署个人前端项目路、接口服务非常方便vercel类似于git
🐱个人主页:不叫猫先生🙋♂️作者简介:前端领域新星创作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀!💫系列专栏:vue3从入门到精通、TypeScript从入门到实践📢资料领取:前端进阶资料以及文中源码可以找我免费领取🔥前端学习交流:博主建立了一个前端交流群,汇集了各路大神,一起交流学习,期待你的加入!(文末有我wx或者私信)目录前言一、vue自定义指令directive讲解二、基于DOM的实现方式1.思路整理2.新建index.vue3.新建`directives`文件4.在`directives`文件下创建`index.ts`文件5.在`main.ts`中全局引
一、乱花迷人眼我就是被迷的那双眼。有时候需求来了,用熟悉的套路进行开发,确实很节省时间也能保证功能的稳定,但是这些开发的惯性无形中阻碍了我对技术的探索。我一直想改造详情页,解放重复功能开发的劳动力,但是详情页一眼望都是内容平铺,好像并没有什么可做的代码设计。后来我拨开繁花,发现详情页的组件化不必想的过于复杂,后台系统风格统一即可。因为大部分的详情页面是内容的展示,偶尔会出现少量的操作功能。将风格统一的部分进行组件化处理,操作功能使用回调函数放回当前页面,避免组件里做过多的业务逻辑。看,这不就成了。项目基于React框架开发的,所以代码写法是JSX语法,组件开发使用的hooks函数式组件,UI框
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我正在创建Rails应用程序,想知道在哪里可以找到好的有关如何使用Rails的教程。我使用了这个我认为非常适合开始学习Rails的博客:http://fairleads.blogspot.com/2007/12/rails-20-and-scaffolding-step-by-step.html我刚刚开始使用Rails,现在想学习更高级的Rails。
目录一、初始化文档数据二、分页查询文档2.1、概述2.2、示例一、初始化文档数据在Postman中,向ES服务器发POST请求:http://localhost:9200/user/_doc/1,请求体内容为:{"name":"zhangsan","age":20,"sex":"男"}在Postman中,向ES服务器发POST请求:http://localhost:9200/user/_doc/2,请求体内容为:{"name":"zhangsan1","age":21,"sex":"男"}在Postman中,向ES服务器发POST请求:http://localhost:9200/user/_d
文章目录1.为什么需要数据库设计2.范式2.1范式简介2.2范式都包括哪些2.3键和相关属性的概念2.4第一范式(1stNF)2.5第二范式(2ndNF)2.6第三范式(3rdNF)2.7小结3.反范式化3.1概述3.2应用举例3.3反范式的新问题3.4反范式的适用场景4.BCNF(巴斯范式)5.第四范式6.第五范式、域键范式7.实战案例7.1迭代1次:考虑1NF7.2迭代2次:考虑2NF7.3迭代3次:考虑3NF7.4反范式化:业务优先的原则8.ER模型8.1ER模型包括哪些要素?8.2关系的类型8.3建模分析8.4ER模型的细化8.5ER模型图转换成数据表9.数据表的设计原则10.数据库对