手写一个图片压缩上传

315 阅读4分钟

前端图片压缩的意义

现在一些手机设备拍摄的图片往往达到几百k至几十M。但是真实项目需求中并可能不需要用到太精细的图片。例如用户头像上传的图片一般限制在2M或5M以内,如果出现用户手机拍摄的照片超过此限制而无法上传,使得用户体验极差。因此在前端进行图片压缩到相应限制内,提高用户的使用体验。

同时图片压缩后上传,由于上传图片尺寸比较小,因此上传速度会比较快,交互会更加流畅,同时大大降低了网络异常导致上传失败风险。

获取要压缩的图片

在图片上传之前,我们首先要获取需要压缩的图片。通过 HTML 5 file API 的FileReader方法,代码如下:

    // 读取图片为base64格式的方法
    function getImageToBase64 (file,callback) {
        // file可以通过 input file 获取
        let reader = new FileReader();
        reader.addEventListener('load',function(e){
            const base64Image = e.target.result;
            // 在回调函数中处理base64
            callback && callback(base64Image);
            reader = null;
        })
        // readAsDataURL方法将文件读取为base64格式
        reader.readAsDataURL(file);
    }

canvas API 压缩图片

思路:我们使用canvas的drawImage()将图片绘制到canvas中,并使用toDataURL()方法将图片压缩

drawImage

根据MDN上的描述: Canvas 2D API 中的 CanvasRenderingContext2D.drawImage()  方法提供了多种在画布(Canvas)上绘制图像的方式。

    drawImage(image, dx, dy)
    drawImage(image, dx, dy, dWidth, dHeight)
    drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

image.png

根据图片示意,参数image代表当前获取的图片。dx,dy,dWidth,dHeight代表图片在画布中的位置和宽高。sx, sy, sWidth, sHeight,代表图片裁剪大小,如果没有设置这四个值图片将会拉伸或缩放在画布上划定的区域中。

canvas提供了两个转换为图片的方法

canvas.toDataURL()

方法,此方法将canvas转换成base64格式图片

canvas.toDataURL(mimeType, qualityArgument)

其中,mimeType表示canvas导出来的base64图片的类型,默认是png格式,也即是默认值是’image/png’,我们也可以指定为jpg格式’image/jpeg’或者webp等格式。file对象中的file.type就是文件的mimeType类型,在转换时候正好可以直接拿来用(如果有file对象)。

qualityArgument表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92,是一个比较合理的图片质量输出参数,通常情况下,我们无需再设定。

canvas.toBlob()

canvas.toBlob(callback, mimeType, qualityArgument) 可以把canvas转换成Blob文件,和toDataURL()方法相比,toBlob()方法是异步的,因此多了个callback参数。

注意:image的src中可以接收图片的base64,我们将前面的base64赋予image对象的src属性来获取image参数

代码如下:

const image = new Image();
// base64Image之前读取的base64编码格式的图片
image.src = base64Image
// 通过监听image的load事件防止图片获取不到
image.addEventListener('load',function(event){
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d');
    // 获取原图的宽高并设置canvas的宽高
    let width = image.naturalWidth
    let height = image.naturalHeight
    canvas.width = width;
    canvas.height = height;
    // 将图片添加到画布中
    ctx.drawImage(image,0,0,width,height);
    const compressImage = canvas.toDataURL('image/jpeg',0.8)
})

此时成功获取到了压缩后的图片的base64,如果需要上传文件格式到后端,可以使用toBlob()方法实现。

限制图片宽高

某些场景下,图片宽度高度同样有限制,假如图片宽度不得超过1024,高度不得超过1024。此时,我们有一张宽度为2048 高度为1024 的图片,图片宽度超出限制,我们将图片宽度缩小到最大限度的1024正好缩小一倍,高度需要同比缩小一倍。

    let width = image.naturalWidth,height = image.naturalHeight
    const maxW = 1024
    const maxH = 1024
    // 判断宽度高度是否超过限制并等比缩放
    if (width > maxW) {
        height = height / ( width / maxW ) 
        width = maxW
    } 
    if (height>maxH) {
        width = width / ( height / maxH ) 
        height = maxH
    }

待解决的问题

了解了toDataURL方法中的 qualityArgument 参数是用来控制图片压缩的程度,参数范围是0-1数值越小压缩成都越大。

那么图片压缩的具体大小是否是可控呢?如果压缩一次图片后仍然超出大小限制该怎么办?toDataURL的压缩原理是什么是否可以不借用canvas实现图片压缩还有待研究。

参考文献

实现图片前端JS压缩并上传

# 图片压缩知识