前端/JS/React/ES6:纯前端实现图片压缩技术
网上图片压缩文章抄来抄去,真没意思,我自己写一个
import React from 'react'; // import logo from './logo.svg'; import './App.css'; function App() { const handleChange = e => { e.persist() // 为了看到原生file的内容,不加这句,原生file内容会被react隐藏 let fileObj = e.target.files[0] // console.log(e) console.log(fileObj) // 设定标准上限为1MB,进行压缩处理 compress(fileObj, 1024, (data) => { console.log(data) }) } // 异步读取图片的promise const loadImageAsync = (url) => { return new Promise(function (resolve, reject) { const image = new Image() image.onload = function () { resolve(image) }; image.onerror = function () { reject(new Error('Could not load image at ' + url)) }; image.src = url }) } // 异步转换成base64编码的promise const fileToImgAsync = (file) => { return new Promise(function (resolve, reject) { const reader = new FileReader() reader.onload = function (e) { resolve(e.target.result); }; reader.onerror = function () { reject(new Error('readAsDataURL:fail')) }; reader.readAsDataURL(file) }); } const downloadFileByBlob = (blobUrl, filename) => { const a = document.createElement('a') a.download = filename a.style.display = 'none' a.href = blobUrl // 触发点击 document.body.appendChild(a) a.click() // 然后移除 document.body.removeChild(a) } // async 搭配 promise 使用 const compress = async (file, maxSizeKB, succFunc) => { if (file.size > 3 * 1024 * 1024) { let rate = 0 // 压缩率 // 文件转图片 const dataUrl = await fileToImgAsync(file) // 图片转画布 const image = await loadImageAsync(dataUrl) // console.log(dataUrl, image) // 文件大小KB, file.size给的是字节Byte const fileSizeKB = file.size / 1024 console.log(fileSizeKB) // 当图片大小超标,才进行压缩 if (fileSizeKB > maxSizeKB) { // 计算压缩率 rate = (fileSizeKB - maxSizeKB) / fileSizeKB console.log('压缩率:', rate) console.log('压缩后文件大小:', fileSizeKB * (1 - rate), 'kb') } // 画布执行压缩 let canvas = document.createElement('canvas') let context = canvas.getContext('2d') const cvWidth = image.width * (1 - rate) const cvHeight = image.height * (1 - rate) console.log(image.width, image.height, cvWidth, cvHeight) canvas.height = cvHeight canvas.width = cvWidth context.clearRect(0, 0, cvWidth, cvHeight) context.drawImage(image, 0, 0, cvWidth, cvWidth) // 导出图片 canvas.toBlob((blob) => { // 方式一:下载到本地 // const blobUrl = window.URL.createObjectURL(blob) // downloadFileByBlob(blobUrl, file.name) // 方式二:生成网页可读取的对象 const newImage = new File([blob], file.name, { type: file.type }); succFunc(newImage) }); } } return ( <div className="App"> <input type="file" id="file" onChange={handleChange} /> </div> ); } export default App;
其实我们可以发现一个问题,导出的图片,在window系统中,大小是 207kb并不是1024kb,很明显size的比例用在width height的比例上有出入,要加入调节因子,因此算法还可以优化,如下
import React from 'react'; // import logo from './logo.svg'; import './App.css'; function App() { const handleChange = e => { e.persist() // 为了看到原生file的内容,不加这句,原生file内容会被react隐藏 let fileObj = e.target.files[0] // console.log(e) console.log(fileObj) // 设定标准上限为1MB,进行压缩处理 compress(fileObj, 1024, (data) => { console.log(data) }) } // 异步读取图片的promise const loadImageAsync = (url) => { return new Promise(function (resolve, reject) { const image = new Image() image.onload = function () { resolve(image) }; image.onerror = function () { reject(new Error('Could not load image at ' + url)) }; image.src = url }) } // 异步转换成base64编码的promise const fileToImgAsync = (file) => { return new Promise(function (resolve, reject) { const reader = new FileReader() reader.onload = function (e) { resolve(e.target.result); }; reader.onerror = function () { reject(new Error('readAsDataURL:fail')) }; reader.readAsDataURL(file) }); } const downloadFileByBlob = (blobUrl, filename) => { const a = document.createElement('a') a.download = filename a.style.display = 'none' a.href = blobUrl // 触发点击 document.body.appendChild(a) a.click() // 然后移除 document.body.removeChild(a) } // async 搭配 promise 使用 const compress = async (file, maxSizeKB, succFunc) => { if (file.size > maxSizeKB * 1024) { let rate = 0 // 压缩率 // 文件转图片 const dataUrl = await fileToImgAsync(file) // 图片转画布 const image = await loadImageAsync(dataUrl) // console.log(dataUrl, image) // 文件大小KB, file.size给的是字节Byte const fileSizeKB = file.size / 1024 console.log(fileSizeKB) // 当图片大小超标,才进行压缩 if (fileSizeKB > maxSizeKB) { // 计算压缩率 rate = (fileSizeKB - maxSizeKB) / fileSizeKB console.log('压缩率:', rate) console.log('压缩后文件大小:', fileSizeKB * (1 - rate), 'kb') } // 纠正因子,不加会导致压缩出的文件太小 const factor = 0.2 // 画布执行压缩 let canvas = document.createElement('canvas') let context = canvas.getContext('2d') const cvWidth = image.width * (1 - rate + factor) const cvHeight = image.height * (1 - rate + factor) console.log(image.width, image.height, cvWidth, cvHeight) canvas.height = cvHeight canvas.width = cvWidth context.clearRect(0, 0, cvWidth, cvHeight) context.drawImage(image, 0, 0, cvWidth, cvWidth) // 导出图片 canvas.toBlob((blob) => { // 方式一:下载到本地 const blobUrl = window.URL.createObjectURL(blob) downloadFileByBlob(blobUrl, file.name) // 方式二:生成网页可读取的对象 // const newImage = new File([blob], file.name, { type: file.type }); // succFunc(newImage) }); } } return ( <div className="App"> <input type="file" id="file" onChange={handleChange} /> </div> ); } export default App;
就这样吧,勉强能用,现在导出的都有800-900kb,还行吧