业务背景
一般来说:每个后台管理系统,或多或少的都会存在一些上传的业务,而我们为了能够最大的节省储存空间,一般来说都会对上传资源压缩(仅针对图片),而有时候遇到上传资源(其他的例如:word pdf等等文档类的)太大的话,接口响应时间又会很长,这时候,把他分割成一个个小的文件上传,实时的给到用户反馈,算是一个比较好的交互体验了。
图片压缩
这里仅针对png,因为webp和jpeg本身就已经是被压缩过的,再次压缩,意义也不大,所以不考虑。
实现思路(canvas)
- 创建
FileReader读取File,返回base64。 - 创建
Image,把base64赋值src。 - 注册
Image的onload事件,在回调中,创建canvas把图片,通过drawImage画到画布上。 - 调用
canvas.toBlob压缩得到输出Blod。 - 调用者可以自行处理(可以插入到
FormData)。 代码实现
// 创建文件读取器
const createFileReader = () => new FileReader()
// 创建image
const createImg = (width, height) => new Image(width, height)
// 创建canvas
const createCanvasAndCtx = () => {
const canvas = document.createElement("canvas")
const context = canvas.getContext("2d")
// 高分辨率时设置
const scale = window.devicePixelRatio
context.scale(scale, scale)
return [canvas, context, scale]
}
//压缩图片
const compressImgSize = (file, options = { type: 'image/webp', quality: 0.85 }) => {
const canCompressType = ['image/png']
if (canCompressType.includes(file.type)) {
return new Promise((resolve, reject) => {
const reader = createFileReader()
reader.onload = (event) => {
const img = createImg()
img.onload = () => {
const [canvas, context, scale] = createCanvasAndCtx()
canvas.width = Math.floor(img.width * scale)
canvas.height = Math.floor(img.height * scale)
context.drawImage(img, 0, 0, canvas.width, canvas.height)
canvas.toBlob((e) => { resolve(e) }, options.type, options.quality)
}
img.onerror = (error) => {
reject(error)
}
img.src = event.target.result
}
reader.onerror = (error) => {
reject(error)
}
reader.readAsDataURL(file)
})
} else {
return new Promise((resolve) => resolve(file))
}
}
调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片压缩</title>
</head>
<style>
</style>
<body>
<!-- 压缩图片 -->
<input type="file" id="upload" accept="image/png, image/jpeg,image/webp" />
</body>
<script>
const input = document.querySelector('#upload')
input.addEventListener("change", (e) => {
const [currentImg] = e.target.files
compressImgSize(currentImg).then(res => {
console.log('原始文件大小', currentImg.size)
console.log('压缩后大小', res.size)
})
})
</script>
</html>
通过控制台打印,可以看到,压缩后的文件只有原始文件的1/3,节省了2/3的储存空间,总体来说还是可以的。
切割大文件
首先我们要清楚,我们切割的是什么,是File对象,而File是什么:File对象是一种特定类型的 Blob,这意味着Blod上面的所有的方法,File都可以用。而Blod的实例方法上面又有silce()这个方法。
slice()(返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据)。
通过上述我们的切割大文件思路就很清晰了,就是我们就利用slice()方法可以得到指定范围内的数据,利用这个特性,来切割文件。
代码实现
const cutFile = (file, criterionSize = 1024 * 1024 * 10) => {
const files = []
let end = criterionSize
const condition = Math.ceil(file.size / criterionSize)
for (let i = 0; i < condition; i++) {
const item = file.slice(i * criterionSize, end)
end += item.size
files.push(item)
}
return files
}
调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件切割</title>
</head>
<body>
<!-- 切割文件 -->
<input type="file" id="upload">
</body>
<script>
const input = document.querySelector('#upload')
input.addEventListener("change", (e) => {
// 分片上传
const files = cutFile(e.target.files[0])
console.log(files)
})
</script>
</html>
1m(1024*1024)
通过对比可以发现,原本57m的文件,按10m来切割,被切割成了6个小文件,这时候,再去上传,就可以给到用户反馈,实时查看当前上传进度,而不是一直一个大文件一直在上传(避免焦虑)。