Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
Y
yifu-study-front-share
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
CI / CD
CI / CD
Pipelines
Schedules
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
yifu-study
front
yifu-study-front-share
Commits
3db27e8d
Commit
3db27e8d
authored
Aug 31, 2022
by
wangxuxiao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
添加文章
parent
547861a0
Pipeline
#16616
failed with stage
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
279 additions
and
0 deletions
+279
-0
前端图片压缩.md
source/_posts/前端图片压缩.md
+279
-0
No files found.
source/_posts/前端图片压缩.md
0 → 100644
View file @
3db27e8d
---
title
:
前端图片压缩
date
:
2022/8/31 22:00:00
tags
:
-
前端
-
JavaScript
-
压缩
author
:
王许晓
---
> 使用VUE或者React框架加载HTML大图时极容易导致前端页面卡死报out of memory错误,报出这个错误还好,如果没有报错浏览器可能就无法操作了。
<!-- more -->
# 前端压缩
为保前端页面正常渲染html大图,需要在html加载前压缩图片。前端解决这个问题有两种方案,一个是上传前压缩,一个是上传后压缩。上传前可以保证当前图片无论在哪里显示大小已经被压缩过,无需再去管理。上传后压缩可以保证服务器端拥有高清原图,显示前需要做一次压缩。
无论那种压缩,压缩方式基本一致
1.
获取图片尺寸
2.
计算压缩比率
3.
通过canvas.toDataUrl 参数转化压缩图片
4.
将base64字符转转成文件或者直接显示
## 上传前压缩
获取到本地文件后,通过FileReader将本地文件写入到内存中
```
// 获取文件图片的宽高
function getImageWHFromFile(file, fn, err) {
try {
// 读取上传之后的文件对象
const imageReader = new FileReader()
// 将图片的内存url地址添加到FileReader中
imageReader.readAsDataURL(file)
// 当图片完全加载到FileReader对象中后,触发下面的事件
imageReader.addEventListener('loadend', function(e) {
// 获取上传到本机内存中的图片的url地址
const imageSrc = e.target.result
// 调用计算图片大小的方法
calculateImageSize(imageSrc).then(function({ image, width, height }) {
// 得到图片的宽和高
fn(image, width, height)
}).catch(e => {
err()
})
})
// 可以做一些其他状态监听
} catch (e) {
err(file)
}
}
```
计算内存中图片的宽高
```
// 根据图片地址获取图片的宽和高
function calculateImageSize(src) {
return new Promise(function(resolve, reject) {
const image = new Image()
image.addEventListener('load', function(e) {
resolve({
image: image,
width: e.target.width,
height: e.target.height,
})
})
image.addEventListener('error', function(e) {
reject(Error('获取尺寸失败'))
})
// 将图片的url地址添加到图片地址中
image.setAttribute('crossOrigin', 'Anonymous')
image.src = src
})
}
```
得到图片宽高后就可以计算压缩比率,通过图片真水的宽高和期望的宽高计算压缩率,基本上可以得到合适的图片尺寸
```
// 计算压缩比率
function getRatio(oriW, oriH, width, height) {
const proW = (oriW / width).toFixed(5)
const proH = (oriH / height).toFixed(5)
return proW < proH ? proW : proH
}
```
得到压缩比率就可以执行压缩,压缩使用canvas实现
```
// 缩放img对象中的图片
function compressImageToBase64(image, quality) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const imageWidth = image.width * quality
const imageHeight = image.height * quality
canvas.width = imageWidth
canvas.height = imageHeight
context.drawImage(image, 0, 0, imageWidth, imageHeight)
return canvas.toDataURL('image/jpeg', quality)
}
```
压缩完得到图片的base64(DataURL)数据,可以用这个数据直接输出到img标签显示,或者继续加工生成图片文件
将base64转成blob对象
```
// DataURL转Blob对象
function dataURLToBlob(dataurl) {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
```
将Blob转成File,调用接口用FormData的形式上传文件
```
// 对图片进行压缩并返回文件格式
function compressToFile(image, quality) {
const blob = dataURLToBlob(compressImageToBase64(image, quality))
return new File([blob], new Date().getTime() + '.jpg')
}
```
到此,就得到了一个压缩后的文件对象
## 上传后压缩
上传后压缩的意思是,服务器返回一个图片url后,如果这个url指向的图片尺寸很大,比如:10M,这时候在浏览器中显示这个图片会导致页面卡死。我的解决方案是压缩后再显示,压缩方式大概与上传前压缩类似,从calculateImageSize方法开始计算图片尺寸,然后进行压缩,得到base64数据后回调显示这个图片。这里存在一个问题:压缩开始后chrome的控制台无法打开。原因则是image加载图片后会在控制台的Network中输出base64格式的日志,这个日志占用了太大的内存。
# 服务器压缩
服务器压缩是最好的且能满足产品对大图要求的解决方案。前端上传原图,服务器压缩后生成两个地址,一个缩略图一个原图,缩略图供前端显示,原图供下载。
> 附全部代码供参考
```
/**
* @param {待压缩图片文件} file
* @param {异步回调} fn
* @param {压缩期望尺寸} size [width ,height]
*/
const fileCompressToFile = function(file, fn, size) {
const _size = getComperssSize(size)
if (_size === null) {
fn(file)
return
}
// 开启后执行压缩
getImageWHFromFile(file, (image, width, height) => {
const pro = getRatio(_size[0], _size[1], width, height)
if (pro >= 1) {
fn(file)
return
}
// 开始压缩
fn(compressToFile(image, pro))
}, () => {
fn(file)
})
}
// 通过url压缩图片,压缩成功返回base64
const urlCompressToBase64 = function(url, fn, size) {
const _size = getComperssSize(size)
if (_size === null) {
fn(url)
return
}
calculateImageSize(url).then(function({ image, width, height }) {
const pro = getRatio(_size[0], _size[1], width, height)
if (pro >= 1) {
fn(url)
return
}
// 开始压缩
fn(compressImageToBase64(image, pro))
}).catch(e => {
console.error(e)
fn(url)
})
}
// 格式化压缩尺寸
function getComperssSize(size) {
if (!size) {
return null
}
const defaultSize = 500
if (Array.isArray(size)) {
return [size[0] || defaultSize, size[1] || defaultSize]
}
const pf = parseFloat(size)
return [pf || defaultSize, pf || defaultSize]
}
// 计算压缩比率
function getRatio(oriW, oriH, width, height) {
const proW = (oriW / width).toFixed(5)
const proH = (oriH / height).toFixed(5)
return proW < proH ? proW : proH
}
// 缩放img对象中的图片
function compressImageToBase64(image, quality) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const imageWidth = image.width * quality
const imageHeight = image.height * quality
canvas.width = imageWidth
canvas.height = imageHeight
context.drawImage(image, 0, 0, imageWidth, imageHeight)
return canvas.toDataURL('image/jpeg', quality)
}
// 对图片进行压缩并返回文件格式
function compressToFile(image, quality) {
const blob = dataURLToBlob(compressImageToBase64(image, quality))
return new File([blob], new Date().getTime() + '.jpg')
}
// DataURL转Blob对象
function dataURLToBlob(dataurl) {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
// 获取文件图片的宽高
function getImageWHFromFile(file, fn, err) {
try {
// 读取上传之后的文件对象
const imageReader = new FileReader()
// 将图片的内存url地址添加到FileReader中
imageReader.readAsDataURL(file)
// 当图片完全加载到FileReader对象中后,触发下面的事件
imageReader.addEventListener('loadend', function(e) {
// 获取上传到本机内存中的图片的url地址
const imageSrc = e.target.result
// 调用计算图片大小的方法
calculateImageSize(imageSrc).then(function({ image, width, height }) {
// 得到图片的宽和高
fn(image, width, height)
}).catch(e => {
err()
})
})
// 可以做一些其他状态监听
} catch (e) {
err(file)
}
}
// 根据图片地址获取图片的宽和高
function calculateImageSize(src) {
return new Promise(function(resolve, reject) {
const image = new Image()
image.addEventListener('load', function(e) {
resolve({
image: image,
width: e.target.width,
height: e.target.height,
})
})
image.addEventListener('error', function(e) {
reject(Error('获取尺寸失败'))
})
// 将图片的url地址添加到图片地址中
image.setAttribute('crossOrigin', 'Anonymous')
image.src = src
})
}
export {
fileCompressToFile,
urlCompressToBase64,
}
```
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment