• 前端图片压缩,不借助任何第三方api


    前端图片压缩

    • 需求:最近做项目,要求900k-5M的图片,需要压缩到900K以下
    • 实现:利用canvas.toDataURL(type, encoderOptions)toBlob(callback, type, encoderOptions)实现

    前置知识点

    • canvas.toDataURL(type, encoderOptions),直接照搬MDN的文档描述。总的来说,想要用这个方法压缩图片,调用toDataURL和toBlob的时候,type只能jpeg或者webp的格式来处理,不然压缩是无效的。最后输出的时候是可以换成任意格式的图片的,比如我就改成了png格式的图片输出。
      1. 如果画布的高度或宽度是0,那么会返回字符串“data:,”。
      2. 如果传入的类型非“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。
      3. Chrome支持“image/webp”类型。
      4. 在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
    • canvas.toBlob(callback, type, encoderOptions),这个方法用起来比上面简单一点,因为上面的方法需要处理base64的URL转换成bufferArray,这个直接使用blob对象去初始化File就可以。
      1. callback,回调函数,可获得一个单独的Blob对象参数。
      2. type,DOMString类型,指定图片格式,默认格式为image/png。
      3. encoderOptions,Number类型,值在0与1之间,当请求图片格式为image/jpeg或者image/webp时用来指定图片展示质量。如果这个参数的值不在指定类型与范围之内,则使用默认值,其余参数将被忽略。
    • atob(base64Str),将base64的字符串解码,与btoa(str)功能相反,因为toDataURL返回的是一个base64编码的地址,需要将其解析并转化成文件。
    • String.prototype.charCodeAt(),返回 0 到 65535 之间的整数,表示给定索引处的 UTF-16 代码单元。

    代码实现

    // 使用toBlob
    // file,需要压缩的file对象
    // quality,压缩的初始质量
    // targetSize,需要压缩的临界值,当压缩一次后比这个临界值要大,就自动递归压缩
    function compress(file, quality = 0.9, targetSize = 900 * 1024) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = (e) => {
                const src = e.target.result;
                const image = new Image();
                image.src = src;
                image.onload = () => {
                    const canvas = document.createElement('canvas');
                    const width = image.width;
                    const height = image.height;
                    canvas.width = width;
                    canvas.height = height;
    
                    const context = canvas.getContext('2d');
                    context.drawImage(image, 0, 0, width, height);
    
                    canvas.toBlob((blob) => { 
                        // 将文件名后缀改成png,并修改文件格式为png格式
                        const miniFile = new File([blob], file.name.replace(/.[a-zA-Z]+$/g, '.png'), { type: 'image/png' })
                        if(miniFile.size > targetSize) {
                            compress(file, quality * 0.9, targetSize).then(mini => resolve(mini));
                        } else {
                            resolve(miniFile);
                        }
                    }, 'image/jpeg', quality);
                }
                image.onerror = (err) => {
                    reject(err);
                }
            }
            reader.onerror = (err) => {
                reject(err);
            }
        })
    }
    // 使用toDateURL
    function compress(file, quality = 0.9, targetSize = 900 * 1024) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = (e) => {
                const src = e.target.result;
                const image = new Image();
                image.src = src;
                image.onload = () => {
                    const canvas = document.createElement('canvas');
                    const width = image.width;
                    const height = image.height;
                    canvas.width = width;
                    canvas.height = height;
    
                    const context = canvas.getContext('2d');
                    context.drawImage(image, 0, 0, width, height);
    
                    const canvasURL = canvas.toDataURL('image/jpeg', quality);
                    const buffer = atob(canvasURL.split(',')[1]) // 获取实际的文件内容
                    let length = buffer.length
                    // 将每一块的内容映射成对应的unicode编码
                    const bufferArray = new Uint8Array(new ArrayBuffer(length)).map((item, index) => buffer.charCodeAt(index))
                    // 将文件名后缀改成png,并修改文件格式为png格式
                    const miniFile = new File([bufferArray], file.name.replace(/.[a-zA-Z]+$/g, '.png'), { type: 'image/png' })
                    if(miniFile.size > targetSize) {
                        compress(file, quality * 0.9, targetSize).then(mini => resolve(mini));
                    } else {
                        resolve(miniFile);
                    }
                }
                image.onerror = (err) => {
                    reject(err);
                }
            }
            reader.onerror = (err) => {
                reject(err);
            }
        })
    }
    // 最近又发现可以通过尺寸压缩,实现起来更简单
    // ratio尺寸压缩比,需要压缩多少倍
    function compress(file, ratio) {
        return new Promise((resolve, reject) => {
            const size = file.size;
            const suffix = file.name.match(/.w+$/g)[0];
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = function(e) {
                const src = e.target.result;
                const img = new Image();
                img.src = src;
                img.onload = function() {
                    const w = img.width,
                        h = img.height;
                    const canvas = document.createElement('canvas');
                    canvas.width = w / ratio;
                    canvas.height = h / ratio;
                    const ctx = canvas.getContext('2d');
                    ctx.drawImage(img, 0, 0, w / ratio, h / ratio);
                    // 这里依然可以使用toBlob和toDataURL两种方式
                    const url = canvas.toDataURL(`image/${suffix}`);
                    const binary = btoa(url.split(',')[1]);
                    const bufferArray = new Uint8Array(new ArrayBuffer(binary.length)).map((item, index) => binary.charCodeAt(index));
                    const miniFile = new File([bufferArray], file.name, { type: `image/${suffix}` });
                    resolve(miniFile)
                }
                img.onerror = function() {
                    reject('Img load fail.')
                }
            }
            reader.onerror = function() {
                reject('Reader fail.')
            }
        })
    }
    // 调用
    compress(file).then(miniFile=> {
      console.log(miniFile);
    }).catch(err => {
      console.log(err)
    })
    

    注意点

    • 压缩很难精确到固定的尺寸,如果对于临界尺寸有偏差值的要求,需要在递归条件上自己拓展,目前只要小于临界值就可以,如果需要更精确,可以改成一个尺寸的范围判断,如果太小了,就把压缩比例再上调,直到满足条件为止。
    • 目前压缩过后的文件类型我自己改成了png,如果有其它要求,或者想动态的传递格式,可以自行拓展该方法,将该参数提炼出来。
    • 当递归的次数比较多的时候,可能就会比较耗时,所以建议采用二分法去获取最佳尺寸,但是依然会比较耗时,如果考虑到界面交互的话,可以先展示原图加一个filter的blur,然后等待压缩完成后替换成压缩后的图片。
  • 相关阅读:
    从头到尾彻底解析Hash表算法
    postgres模糊匹配大杀器
    postgres数据库表空间收缩之pg_squeeze,pg_repack
    pgcli安装
    pg_waldump的使用
    数据库表空间收缩之pg_squeeze,pg_repack
    数据库fillfactor
    pgbouncer连接池
    mysql-选择使用Repeatable read的原因
    postgresql-锁相关
  • 原文地址:https://www.cnblogs.com/aloneMing/p/14646905.html
Copyright © 2020-2023  润新知