前端指定大小缩放比例压缩图片 您所在的位置:网站首页 canvas怎么上传图片 前端指定大小缩放比例压缩图片

前端指定大小缩放比例压缩图片

2023-12-28 06:51| 来源: 网络整理| 查看: 265

核心内容 图片压缩,包括大小和缩放 瓦片绘制,针对 ios 图片的大小超过两百万像素,图片无法绘制到canvas上的问题 canvas的大小限制,如果canvas的大小大于大概五百万像素(即宽高乘积)的时候,不仅图片画不出来,其他什么东西也都是画不出来的。【解决:对图片的宽高进行适当压缩】 canvas的toDataURL和toBlob是只能压缩图片格式为image/jpeg或者image/webp的图片,需要将图片类型设置为image/jepg png类型图片,写入canvas后背景会变成黑色,手动为图片铺白色底 由于无法直接设置图片大小,所以通过二分法尽可能找到最接近设置大小的值

效果预览,如下是图片设置最大10KB,缩放比例0.5

compress(file, 10, 0.5);

测试结果

您可以点击此处进行测试测试链接

准备知识

在移动端压缩图片并且上传主要涉及到 filereader 、canvas 以及 formdata 这三个h5的api。同时还包括一些blob、base64、blobURL等相关转换的知识。

通过本文你将会了解:

blob => dataURL(base64) FileReader.toBlob() blob => blobURL URL.createObjectURL、URL.revokeObjectURL canvas 绘制图片 ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) canvas 瓦片绘制 二分法求最接近值 循环的异步执行 async await (此方法并非压缩方法需要,只在demo中用到) canvas 压缩图片 canvas.toDataURL(type, encoderOptions)、canvas.toBlob(callback, type, encoderOptions)

相关内容下文代码中会体现,如果希望详细了解也可以参考blob、base64、file相互转换;

实现

canvas为本例的核心部分,图片质量,压缩比例均需要通过canvas实现.

canvas 具有图像操作能力,支持将一个已有的图片作为图片源,来操作图像。

设计

方法接收 file 类型文件,压缩大小,压缩比例,返回压缩后的文件

/** * @param: (file: blob, maxSize: number, size: number[0-1]) * @return: {blob, base64} */ function compress(file, maxSize, scale): Promise { ... } 1. 接收file,并转为url(base64/objectURL)传入img, 在回调函数中处理压缩逻辑 // 利用 FileReader 将blob转base64 // function blob2base64(blob) { // return new Promise(function(resolve, reject) { // const reader = new FileReader(); // reader.onload = (e) => { // resolve(e.target.result) // } // reader.readAsDataURL(blob) // }) // } // const dataUrl = blob2base64(file); // 利用URL.createObjectURL 将blob转为objectURL const blobUrl = URL.createObjectURL(file); const tmpImg = new Image(); tmpImg.onLoad = function(e) { const img = e.target; URL.revokeObjectURL(file); // 清理图片缓存 // ... 压缩代码 } tmpImg.src = blobUrl; 2. 绘制canvas /** * @description 将图片绘制到画布上,并根据缩放比例进行缩放,若图片大于2000000像素则进行瓦片处理 * @param {context: canvasContext, img: imgObj, scale: 0-1} */ function drawImage(context, img, scale) { const { width: sourceWidth, height: sourceHeight, } = img; const pSize = sourceWidth * sourceHeight; const targetWidth = sourceWidth * scale, targetHeight = sourceHeight * scale; const defSize = 1000; // 设置瓦片默认宽高 const cols = pSize > 2e6 ? ~~(targetWidth / defSize) + 1 : 1; // 列数 const rows = pSize > 2e6 ? ~~(targetHeight / defSize) + 1 : 1; // 行数 // 瓦片绘图 for (let i = 0; i < rows; i ++) { // 遍历列 for (let j = 0; j < cols; j ++) { // 遍历行 const canvasWidth = j === (cols - 1) ? targetWidth % defSize : defSize; const canvasHeight = i === (rows - 1) ? targetHeight % defSize : defSize; const canvasLeft = j * defSize; const canvasTop = i * defSize; const imgWidth = canvasWidth / scale; const imgHeight = canvasHeight / scale; const imgLeft = canvasLeft / scale; const imgTop = canvasTop / scale; context.drawImage(img, imgLeft, imgTop, imgWidth, imgHeight, canvasLeft, canvasTop, canvasWidth, canvasHeight); } } }

注意:demo中添加了定时器,延迟每次绘制150ms执行,并且defSize设置为50,用来展示瓦片绘制的效果,实际上并不需要

demo 代码

function drawImage(context, img, scale) { const { width: sourceWidth, height: sourceHeight, } = img; const targetWidth = sourceWidth * scale, targetHeight = sourceHeight * scale; // 定义sleep方法用于延迟执行 function sleep(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, time * 120) }) } return new Promise(async (resolve, reject) => { const defSize = 50; const cols = ~~(targetWidth / defSize) + 1; // 列数 const rows = ~~(targetHeight / defSize) + 1; // 行数 // 这里注意,for循环若想要异步执行,则方法需要加上 async 关键字,并且异步方法通过 await 调用 for (let i = 0; i < rows; i ++) { // 遍历列 for (let j = 0; j < cols; j ++) { // 遍历行 const canvasWidth = j === (cols - 1) ? targetWidth % defSize : defSize; const canvasHeight = i === (rows - 1) ? targetHeight % defSize : defSize; const canvasLeft = j * defSize; const canvasTop = i * defSize; const imgWidth = canvasWidth / scale; const imgHeight = canvasHeight / scale; const imgLeft = canvasLeft / scale; const imgTop = canvasTop / scale; await sleep(1); context.drawImage(img, imgLeft, imgTop, imgWidth, imgHeight, canvasLeft, canvasTop, canvasWidth, canvasHeight); } } resolve('success'); }) }

上述代码涉及到循环的异步执行,参考注释,另外,这种方式在 forEach 方法中无效,因为 forEach 本身为同步设计的,不支持异步

3. 压缩方法 /** * @description 该步骤为关键步骤,通过canvas的toDataURL和toBlob对图片进行压缩 * @param {canvas: Canvas, quality: 0-1 图片质量} */ function compressBlob(canvas, quality) { const type = "image/jpeg"; // 只有jpeg格式图片支持图片质量压缩 const dataURL = canvas.toDataURL(type, quality); return new Promise((resolve, reject) => { canvas.toBlob(function(blob) { resolve({ dataURL, blob, }); }, type, quality); }) } 4. 压缩算法,计算最接近maxSize的文件大小

这一步利用二分法获取合适的 quality , 重复调用 compressBlob(), 使得文件大小尽量接近设定值

/** * @param {canvas: Canvas, maxSize: number} */ function compressCaculation(canvas, maxSize) { let count = 1; // 记录循环次数,防止死循环 let quality = 1; // 图片质量 let maxQuality = 1, minQuality = 0; // 定义图片质量范围 return new Promise(function(resolve, reject) { async function fn() { quality = (maxQuality + minQuality) / 2; // 获取压缩后结果 let res = await compressBlob(canvas, quality); const { blob, dataURL } = res; const { size } = blob; console.log(`第${count}次压缩:`, quality, size); // 二分法获取最接近值 function partition() { count ++; if (size > maxSize) { maxQuality = quality; } else { minQuality = quality; } fn(); } if(count < 10 && size !== maxSize) { // 默认循环不大于20次,认为10次内的结果已足够接近 partition(); } else if(size > maxSize) { // 若限制次数之后size > maxSize,则继续执行,至结果小于maxSize partition(); } else { resolve(res); } } fn(); }) } 5. 整合方法

串联整个压缩流程

/** * * @param {file: File|Blob, maxSize: number, scale: 0-1} * @return {name: string, blob: Blob, dataURL: base64} */ function compress(file, maxSize = 13, scale = 0.5) { maxSize *= 1024; // maxSize 单位为kb,这里转为b const { size: sourceSize, name, } = file; const fileType = name.replace(/^.*\.(\w*)$/, '$1').toLowerCase(); if (!['jpg', 'jpeg', 'png', 'webp'].includes(fileType)) return Promise.reject('文件格式不支持!'); const blobUrl = URL.createObjectURL(file); const tmpImg = new Image(); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); return new Promise((resolve, reject) => { tmpImg.onload = async function(e) { const img = e.target; URL.revokeObjectURL(file); // 清理图片缓存 let { width, height } = img; width *= scale; height *= scale; //如果图片大于四百万像素,重写缩放比例比并将大小压至400万以下 if ((width * height / 4e6) > 1) { scale = 4e6 / (width * height); width *= scale; height *= scale; } // 设置画布大小 canvas.width = width; canvas.height = height; context.fillStyle = "#fff"; // 填充底色,处理png背景为黑色的问题 context.fillRect(0, 0, width, height); // canvas 绘图,这一步将图片按比例缩放,并处理瓦片问题 drawImage(context, img, scale); compressCaculation(canvas, maxSize).then(res => { console.log('压缩完成,压缩前:', sourceSize, ' => 压缩后:', res.blob.size); resolve({ name, ...res, }) }) } tmpImg.src = blobUrl; }) } 测试 function upload(e) { [file] = e.files; // 最大值10M, 缩放比例0.5 compress(file, 10, 0.5).then(res => { console.log(res); }); }

效果如下

测试结果

参考链接

MDN WEB Docs Blob-File-Base64转换



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有