图片压缩的js实现

271 阅读2分钟

图片上传是前端中常见的的业务场景,但是一旦图片过大就会导致不好的用户体验。适当的对图片进行压缩处理, 可以显著的提升用户体验。

压缩思路

涉及到 JS 的图片压缩,我的想法是需要用到 Canvas 的绘图能力,通过调整图片的分辨率或者绘图质量来达到图片压缩的效果,实现思路如下

  • 获取上传 Input 中的图片对象 File
  • 将图片转换成 base64 格式
  • base64 编码的图片通过 Canvas 转换压缩,这里会用到的 Canvas 的 drawImage(调节图片的分辨率) 以及 toDataURL(调节图片压缩质量并且输出) 这两个 Api,下面会有详细介绍
  • 转换后的图片生成对应的新图片,然后输出图片对象

优缺点介绍

Canvas 压缩的方式也有着自己的优缺点:

  • 优点:实现简单,参数可以配置化,自定义图片的尺寸,指定区域裁剪等等。
  • 缺点:只有 jpeg 、webp 支持原图尺寸下图片质量的调整来达到压缩图片的效果,其他图片格式,仅能通过调节尺寸来实现

压缩效果

图片

代码实现

<template>
  <div class="container">
    <input type="file" id="input-img" @change="compress" />
    <a :download="fileName" :href="compressImg" >下载</a>
    <div>
        <div>压缩前图片的大小:{{compressImageInfo.beforeKB}}</div>
        <div>压缩后图片的大小:{{compressImageInfo.afterKB}}</div>
        <img  referrerPolicy="no-referrer"   referrerPolicy="no-referrer"   referrerPolicy="no-referrer"   referrerPolicy="no-referrer"  :src="compressImg" />
    </div>
  </div>
</template>
<script>
export default {
    name: 'compress',
    data() {
        return {
            compressImg: null,
            fileName: null,
            compressImageInfo: {},
        };
    },
    methods: {
        compress() {
            // 获取文件对象
            const fileObj = document.querySelector('#input-img').files[0];

            // 获取文件名称,后续下载重命名
            this.fileName = `${new Date().getTime()}-${fileObj.name}`;

            // 压缩图片
            this.compressImage(fileObj, 0.2, 100, 100).then((res) => {
                console.log(res);
                this.compressImageInfo = res;
                this.compressImg = window.URL.createObjectURL(res.file);
            });
        },
        /**
         * 压缩方法
         * @param {string} file 文件
         * @param {Number} quality  0~1之间
         * @param {Number} compressImageWidth  压缩后图片的宽度,默认原图片的宽度
         * @param {Number} compressImageHeight  压缩后图片的高度,默认原图片的高度
         * @param {string} compressImageType 压缩后图片的类型,默认原图片的类型  png、jpeg...
        */
        compressImage(file, quality, compressImageWidth, compressImageHeight, compressImageType) {
            console.log('file', file);

            return new Promise((resolve) => {
                const reader = new FileReader(); // 创建 FileReader

                reader.onload = ({
                    target: {
                        result: src,
                    },
                }) => {
                    const image = new Image(); // 创建 img 元素

                    image.onload = async () => {
                        const canvas = document.createElement('canvas'); // 创建 canvas 元素
                        const imageType = compressImageType ? `image/${compressImageType}` : file.type; // 压缩后图片类型
                        const imageWidth = compressImageWidth || image.width;
                        const imageHeight = compressImageHeight || image.height;

                        canvas.width = imageWidth;
                        canvas.height = imageHeight;
                        canvas.getContext('2d').drawImage(image, 0, 0, imageWidth, imageHeight); // 绘制 canvas
                        // 调用 `canvas` 的 `toDataURL` 方法可以输出 `base64` 格式的图片。
                        const canvasURL = canvas.toDataURL(imageType, quality);
                        // 将 canvas 生成的 base64 数据拆分后,通过 `atob` 方法解码
                        const buffer = atob(canvasURL.split(',')[1]);
                        let length = buffer.length;
                        const bufferArray = new Uint8Array(new ArrayBuffer(length));

                        while (length--) {
                            bufferArray[length] = buffer.charCodeAt(length);
                        }

                        const miniFile = new File([bufferArray], file.name, {
                            type: imageType,
                        });

                        resolve({
                            file: miniFile,
                            origin: file,
                            beforeSrc: src,
                            afterSrc: canvasURL,
                            beforeKB: Number((file.size / 1024).toFixed(2)),
                            afterKB: Number((miniFile.size / 1024).toFixed(2)),
                        });
                    };

                    image.src = src;
                };

                reader.readAsDataURL(file);
            });
        },
    },
};
</script>
Input 上传 File 处理

将 File 对象通过 FileReader 的 readAsDataURL 方法转换为URL格式的字符串(base64 编码)

const fileObj = document.querySelector('#input-img').files[0];
const reader = new FileReader();
// 读取文件
reader.readAsDataURL(fileObj);
Canvas 处理 File 对象

建立一个 Image 对象,一个 canvas 画布,设定自己想要下载的图片尺寸,调用 drawImage 方法在 canvas 中绘制上传的图片

const image = new Image(); // 创建 img 元素
const canvas = document.createElement('canvas'); // 创建 canvas 元素
...
canvas.getContext('2d').drawImage(image, 0, 0, imageWidth, imageHeight); // 绘制 canvas
drawImage
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

img:图片对象,可以是页面上获取的 DOM 对象,也可以是虚拟 DOM 中的图片对象。
dx、dy、dWidth、dHeight:表示在 canvas 画布上规划出一片区域用来放置图片,dx, dy 为绘图位置在 Canvas 元素的 X 轴、Y 轴坐标,dWidth, dHeight 指在 Canvas 元素上绘制图像的宽度和高度(如果不说明, 在绘制时图片的宽度和高度不会缩放)。
sx、sy、swidth、sheight:这 4 个参数是用来裁剪源图片的,表示图片在 canvas 画布上显示的大小和位置。sx, sy 表示在源图片上裁剪位置的 X 轴、Y 轴坐标,然后以 swidth, sheight 尺寸来选择一个区域范围,裁剪出来的图片作为最终在 Canvas 上显示的图片内容( swidth, sheight 不说明的情况下,整个矩形(裁剪)从坐标的 sx 和 sy 开始,到图片的右下角结束)。

Canvas 输出图片

调用 canvas 的 toDataURL 方法可以输出 base64 格式的图片。

canvas.toDataURL(`image/${type}`);
toDataURL
canvas.toDataURL(type, encoderOptions);

type:指定转换为base64编码后图片的格式,如:image/png、image/jpeg、image/webp等等,默认为image/png格式。
encoderOptions:可选参数。用于设置转换为base64编码后图片的质量,取值范围为0-1,超出取值范围用默认值0.92代替。

a 标签的下载

调用 <a> 标签的 download 属性,即可完成图片的下载。

// href 下载必填
<a download="filename" href="href"> 下载 </a>

filename:指定文件名称。
href:文件的下载地址。

需要注意的是,href属性的地址必须是和你前端js非跨域的地址,如果引用的是第三方的网站或者说是前后端分离的项目,调用后台的接口,这时download就会不起作用。