JavaScript实现压缩图片至指定大小
作者:视觉CG
这篇文章主要为大家详细介绍了如何使用JavaScript实现压缩图片至指定大小,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
完整代码
yaSuoImg.js
/** * 加载图片 * @param {URL} url 图片地址 * @returns {Promise.<Image>} */ const loadImageAsync = (url) => { return new Promise((resolve, reject) => { const image = new Image(); image.src = url; image.onload = function () { resolve(image); }; image.onerror = function (err) { reject(new Error("出错 " + err)); }; }); } /** * 图片压缩 * @param {File} file 拾取的图片文件File * @param {Number} [maxSizeKB=300] 超过maxSizeKB启动压缩(KB) * @param {String} [imgType="webp"] 压缩后的图片格式,推荐webp,既无损压缩 * @param {Number} [opy=.9] 清晰度 0-1 推荐0.9,既不失真,又可压缩图片内存 * @return {Promise.<Base64URLString>} */ export const compress = async (file, maxSizeKB = 300, imgType = 'webp', opy = .9) => { const canvas = document.createElement("canvas"), context = canvas.getContext("2d"), image = await loadImageAsync(URL.createObjectURL(file)), { height, width } = image; if (file.size > maxSizeKB * 1024) { let rate = 0; // 压缩率 const fileSizeKB = file.size / 1024; // 文件大小KB, file.size给的是字节Byte if (fileSizeKB > maxSizeKB) { // 当图片大小超标,才进行压缩 rate = (fileSizeKB - maxSizeKB) / fileSizeKB; // 计算压缩率 } // 纠正因子,不加会导致压缩出的文件太小 const factor = 0, cvWidth = width * (1 - rate + factor), cvHeight = height * (1 - rate + factor); canvas.height = cvHeight; canvas.width = cvWidth; context.clearRect(0, 0, cvWidth, cvHeight); context.drawImage(image, 0, 0, cvWidth, cvHeight); } else { canvas.height = height; canvas.width = width; opy = .7 context.clearRect(0, 0, width, height); context.drawImage(image, 0, 0, width, height); } URL.revokeObjectURL(file)//释放内存 return canvas.toDataURL(`image/${imgType}`, opy); }
具体使用
import { compress } from "/yaSuoImg.js"; const imgBlobSrc = await compress(file);
方法补充
1.JS将base64图片压缩至指定大小
在开发中,通常在上传图片时,由于各种限制,需要将上传的图片压缩到某一大小范围内才能上传。在此提供以下方法实现该需求,复制可用。
压缩图片原理
通过原生的input标签拿到要上传的图片文件
将图片文件file对象转化成图片base64
根据图片base64创建img图片对象
在canvas上绘制该HTMLImageElement
通过不断降低图片质量指数来达到压缩图片大小的目的
封装
将以上代码封装为一个js文件,这里命名为compressImg.js,放置在utils文件夹下
/** * 压缩图片到指定大小 * @param baseImg base64图片 * @param maxSize 单位kb */ export function compressImgBySize (baseImg, maxSize = 200) { return new Promise((resolve) => { // 计算图片大小 const strLength = baseImg.length const fileLength = parseInt(strLength - (strLength / 8) * 2) let size = parseInt((fileLength / 1024).toFixed(2)) // 判断图片是否符合指定大小要求 if (size <= maxSize) { resolve(baseImg) return } // 创建image对象 const img = new Image() if (baseImg.indexOf('data:image/') !== -1) { img.src = baseImg } else { img.src = 'data:image/jpeg;base64,' + baseImg } img.onload = () => { // 把image对象 转换为 canvas对象 const canvas = imgToCanvas(img) let resUrl = '' // 图片质量,范围:0 ~ 1 let quality = 0.9 // 当图片大小大于指定maxSize,图片质量大于0时继续通过降低图片资料来压缩 while (size > maxSize && quality > 0) { // 在canvas上绘制该HTMLImageElement,得到图片base64 resUrl = canvas.toDataURL('image/jpeg', quality).replace(/^data:image\/\w+;base64,/, '') const resLength = resUrl.length // 计算绘制出的base64图片大小 const resFileLength = parseInt(resLength - (resLength / 8) * 2) size = parseInt((resFileLength / 1024).toFixed(2)) // 降低图片质量 quality = (quality - 0.1).toFixed(1) } resolve(resUrl) } img.onerror = () => { resolve(baseImg) } }) } // 把image 转换为 canvas对象 export function imgToCanvas (image) { var canvas = document.createElement('canvas') canvas.width = image.width canvas.height = image.height canvas.getContext('2d').drawImage(image, 0, 0) return canvas }
在需要使用图片压缩的.vue文件中引入并使用,如下
// 注意引入路径是否正确 import { compressImgBySize } from '@/utils/compressImg' // this.applyFile[0].content 为指定的base64格式照片 // 1200 指将图片压缩到1200kb大小以下 compressImgBySize('file文件对象', 1200).then(baseImg => { // 输出图片base64 console.log(baseImg) })
2.js压缩图片到指定大小
代码如下:
import React from 'react'; import PropTypes from 'prop-types'; import styles from './upload.less'; import compress from './compress'; class Upload extends React.Component { constructor(props) { super(props); this.fileInput = React.createRef(); this.state = { fileObjs: [], // item { originFile, compressBase64, compressFile } }; } getFileUrl(file) { let url; const agent = navigator.userAgent; if (agent.indexOf('MSIE') >= 1) { url = file.value; } else if (agent.indexOf('Firefox') > 0 || agent.indexOf('Chrome') > 0) { url = window.URL.createObjectURL(file); } return url; } compressCallBack(file, fileObj, result) { const { fileObjs } = this.state; file.compressing = false; // 压缩完成 fileObj.compressBase64 = result.compressBase64; fileObj.compressFile = result.compressFile; this.setState({ fileObjs: [...fileObjs] }); if (fileObjs.length && fileObjs.every(fileObjItem => fileObjItem.compressBase64)) { console.log('全部压缩完成', fileObjs); } } onInputChange(e) { const { fileObjs } = this.state; Object.keys(e.target.files).forEach((key) => { const file = e.target.files[key]; // 验证图片格式 const type = file.name.split('.')[1]; if (type !== 'png' && type !== 'jpg' && type !== 'jpeg') { console.warn('请上传png,jpg,jpeg格式的图片!'); e.target.value = ''; return; } file.url = this.getFileUrl(file); file.compressing = true; // 压缩状态,开始压缩 const fileObj = { originFile: file, compressBase64: null, compressFile: null }; fileObjs.push(fileObj); // 压缩图片的方法, maxSize单位为kb compress(file, 200).then((res) => { this.compressCallBack(file, fileObj, res); }, (err) => { // 压缩失败,则返回原图片的信息 this.compressCallBack(file, fileObj, err); }); }); this.setState({ fileObjs: [...fileObjs] }); e.target.value = ''; } render() { const { fileObjs } = this.state; return ( <div className={styles.uploadContainer} > <div className={styles.gridItem}> <div className={styles.inputContainer} onClick={() => { this.fileInput.current.click(); }} > <span className={styles.uploadIcon}>+</span> <input className={styles.fileInput} ref={this.fileInput} type="file" name="file" multiple="multiple" accept="image/*" onChange={e => this.onInputChange(e)} /> </div> </div> { fileObjs.map(fileObj => ( <div className={styles.gridItem}> <img src={fileObj.compressBase64 ? fileObj.compressBase64 : fileObj.originFile.url} className={fileObj.originFile.compressing && styles.filter} /> { fileObj.originFile.compressing ? <div className={styles.progressContainer}> <div className={styles.progress}> <div className={styles.progressHighlight} /> </div> </div> : '' } </div> )) } </div>); } } Upload.propTypes = { dispatch: PropTypes.func.isRequired, }; export default Upload;
图片压缩主要代码compress.js
// 将File(Blob)对象转变为一个dataURL字符串, 即base64格式 const fileToDataURL = file => new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = e => resolve(e.target.result); reader.readAsDataURL(file); }); // 将dataURL字符串转变为image对象,即base64转img对象 const dataURLToImage = dataURL => new Promise((resolve) => { const img = new Image(); img.onload = () => resolve(img); img.src = dataURL; }); // 将一个canvas对象转变为一个File(Blob)对象 const canvastoFile = (canvas, type, quality) => new Promise(resolve => canvas.toBlob(blob => resolve(blob), type, quality)); const compress = (originfile, maxSize) => new Promise(async (resolve, reject) => { const originSize = originfile.size / 1024; // 单位为kb console.log('图片指定最大尺寸为', maxSize, '原始尺寸为:', originSize); // 将原图片转换成base64 const base64 = await fileToDataURL(originfile); // 缩放图片需要的canvas const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); // 小于maxSize,则不需要压缩,直接返回 if (originSize < maxSize) { resolve({ compressBase64: base64, compressFile: originfile }); console.log(`图片小于指定大小:${maxSize}KB,不用压缩`); return; } const img = await dataURLToImage(base64); const scale = 1; const originWidth = img.width; const originHeight = img.height; const targetWidth = originWidth * scale; const targetHeight = originHeight * scale; canvas.width = targetWidth; canvas.height = targetHeight; context.clearRect(0, 0, targetWidth, targetHeight); context.drawImage(img, 0, 0, targetWidth, targetHeight); // 将Canvas对象转变为dataURL字符串,即压缩后图片的base64格式 // const compressedBase64 = canvas.toDataURL('image/jpeg', 0.1); // 经过我的对比,通过scale控制图片的拉伸来压缩图片,能够压缩jpg,png等格式的图片 // 通过canvastoFile方法传递quality来压缩图片,只能压缩jpeg类型的图片,png等格式不支持 // scale的压缩效果没有canvastoFile好 // 在压缩到指定大小时,通过scale压缩的图片比通过quality压缩的图片模糊的多 // 压缩的思路,用二分法找最佳的压缩点 // 这里为了规避浮点数计算的弊端,将quality转为整数再计算; // const preQuality = 100; const maxQualitySize = { quality: 100, size: Number.MAX_SAFE_INTEGER }; const minQualitySize = { quality: 0, size: 0 }; let quality = 100; let count = 0; // 压缩次数 let compressFinish = false; // 压缩完成 let invalidDesc = ''; let compressBlob = null; // 二分法最多尝试8次即可覆盖全部可能 while (!compressFinish && count < 12) { compressBlob = await canvastoFile(canvas, 'image/jpeg', quality / 100); const compressSize = compressBlob.size / 1024; count++; if (compressSize === maxSize) { console.log(`压缩完成,总共压缩了${count}次`); compressFinish = true; return; } if (compressSize > maxSize) { maxQualitySize.quality = quality; maxQualitySize.size = compressSize; } if (compressSize < maxSize) { minQualitySize.quality = quality; minQualitySize.size = compressSize; } console.log(`第${count}次压缩,压缩后大小${compressSize},quality参数:${quality}`); quality = Math.ceil((maxQualitySize.quality + minQualitySize.quality) / 2); if (maxQualitySize.quality - minQualitySize.quality < 2) { if (!minQualitySize.size && quality) { quality = minQualitySize.quality; } else if (!minQualitySize.size && !quality) { compressFinish = true; invalidDesc = '压缩失败,无法压缩到指定大小'; console.log(`压缩完成,总共压缩了${count}次`); } else if (minQualitySize.size > maxSize) { compressFinish = true; invalidDesc = '压缩失败,无法压缩到指定大小'; console.log(`压缩完成,总共压缩了${count}次`); } else { console.log(`压缩完成,总共压缩了${count}次`); compressFinish = true; quality = minQualitySize.quality; } } } if (invalidDesc) { // 压缩失败,则返回原始图片的信息 console.log(`压缩失败,无法压缩到指定大小:${maxSize}KB`); reject({ msg: invalidDesc, compressBase64: base64, compressFile: originfile }); return; } compressBlob = await canvastoFile(canvas, 'image/jpeg', quality / 100); const compressSize = compressBlob.size / 1024; console.log(`最后一次压缩(即第${count + 1}次),quality为:${quality},大小:${compressSize}`); const compressedBase64 = await fileToDataURL(compressBlob); const compressedFile = new File([compressBlob], originfile.name, { type: 'image/jpeg' }); resolve({ compressFile: compressedFile, compressBase64: compressedBase64 }); }); export default compress;
到此这篇关于JavaScript实现压缩图片至指定大小的文章就介绍到这了,更多相关JavaScript压缩图片内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!