前端图片压缩同时保留图片旋转信息

1,343 阅读3分钟

前言

一般情况下前端使用canvas进行图片压缩,但是这样不能保留图片的旋转信息,导致压缩后图片旋转,下面我就讲述下如何保证图片正常旋转的。

代码

先祭献出代码啦

import Exif from "exif-js";

/**
 * @desc 获取图片信息,使用exif.js库,具体用法请在github中搜索
 * @param {Object} file 上传的图片文件
 * @param {String} tag 需要获取的信息 例如:'Orientation'旋转信息
 * @return {Promise<Any>} 读取是个异步操作,返回指定的图片信息
 */
const getImageTag = (file, tag) => {
    return new Promise((resolve) => {
        Exif.getData(file, function () {
            const o = Exif.getTag(this, tag);
            resolve(o);
        });
    });
};


/**
 * @desc 获取旋转后的图片
 * @param {Object} img 图片文件
 * @param {Number} or 旋转信息
 */
const getRotateImg = (canvas, img, or, compval) => {
    const ctx = canvas.getContext('2d');
    // 使用图片的原始尺寸
    let tH = img.naturalWidth * compval;
    let tW = img.naturalHeight * compval;
    canvas.width = tH;
    canvas.height = tW;
    ctx.drawImage(img, 0, 0, tH, tW);
    switch (or) {
        case 6: // 顺时针旋转90度
            rotateImg(img, 'right', canvas);
            break;
        case 8: // 逆时针旋转90度
            rotateImg(img, 'left', canvas);
            break;
        case 3: // 顺时针旋转180度
            rotateImg(img, 'right', canvas, 2);
            break;
        default:
            break;
    }
    return canvas.toDataURL('image/png', 1);
}

/**
 * @desc 旋转canvas,会对源数据canvas进行修改
 * @param {Object} img 图片文件
 * @param {String} dir 方向 left逆时针|right顺时针
 * @param {Object} canvas 画布
 * @param {Number} s 向指定方向旋转几步,1步为90度
 */
const rotateImg = (img, dir = "right", canvas, s = 1) => {
    const MIN_STEP = 0;
    const MAX_STEP = 3;

    const width = canvas.width || img.width;
    const height = canvas.height || img.height;
    let step = 0;

    if (dir === "right") {
        step += s;
        step > MAX_STEP && (step = MIN_STEP);
    } else {
        step -= s;
        step < MIN_STEP && (step = MAX_STEP);
    }

    const degree = (step * 90 * Math.PI) / 180;
    const ctx = canvas.getContext("2d");

    switch (step) {
        case 1:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, 0, -height, width, height);
            break;
        case 2:
            canvas.width = width;
            canvas.height = height;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, -height, width, height);
            break;
        case 3:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, 0, width, height);
            break;
        default:
            canvas.width = width;
            canvas.height = height;
            ctx.drawImage(img, 0, 0, width, height);
            break;
    }
};


/**
 * 读取文件数据
 *
 * @param {HTMLFILE} [file=File]
 */
const GetFileData = (file) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = async e => {
            resolve(e.target.result);
        };
    });
}


/**
 *  图片压缩功能
 *
 * @param {HTMLFILE} [file=File]
 * @param {number} [targetSize=5 * 1024]
 */
const CompressImg = async (file, targetSize = 5 * 1024) => {
    return new Promise(async (resolve, reject) => {
        if (!file) {
            reject('未传入文件');
        }
        // 判断文件格式
        const isIMG =
            [
                "image/jpeg",
                "image/jpeg",
                "image/jpg",
                "image/png",
                "image/bmp"
            ].indexOf(file.type) >= 0;

        if (!isIMG) {
            reject('文件格式错误');
        }
        const FileBaseData = await GetFileData(file);
        // 根据指定的目标大小进行压缩
        let compVal = targetSize / FileBaseData.length;
        compVal = compVal > 1 ? 1 : Math.floor(compVal);
        let img = document.createElement('img');
        let canvas = document.createElement('canvas');
        img.onload = async () => {
            // 获取图片的旋转信息
            let or = await getImageTag(file, 'Orientation');
            // 获取到压缩后的图片数据
            let data = getRotateImg(canvas, img, or, compVal);
            resolve(data);
        }
        img.src = FileBaseData
    })
}

export default CompressImg;

原理

使用canvas直接压缩图片会把原图的属性信息全部删除掉,但是我们可以通过获取File对象中的属性,在执行压缩前执行旋转,这样子就能保证压缩后的图片依然能够正常预览了。这里我们使用了 exif.js 库,它可以方便的获取File对象的属性信息,讲到这里,下面的基本可以不用看了。

步骤

根据目标的文件大小获取压缩值

这里默认未5M大小,根据图片本身大小,小于 5M 则不进行压缩,大于5M则根据比例压缩,虽然实际使用存在较大误差,但是还是可以覆盖多数需求场景的。

    const FileBaseData = await GetFileData(file);
    // 根据指定的目标大小进行压缩
    let compVal = targetSize / FileBaseData.length;
    compVal = compVal > 1 ? 1 : Math.floor(compVal);

获取图片的旋转信息

const getImageTag = (file, tag) => {
    return new Promise((resolve) => {
        Exif.getData(file, function () {
            const o = Exif.getTag(this, tag);
            resolve(o);
        });
    });
};

执行旋转

使用了cavans的rotate方法对幕布进行旋转,同时也不要忘记绘入图片的时候对图片进行翻转。

const rotateImg = (img, dir = "right", canvas, s = 1) => {
    const MIN_STEP = 0;
    const MAX_STEP = 3;

    const width = canvas.width || img.width;
    const height = canvas.height || img.height;
    let step = 0;

    if (dir === "right") {
        step += s;
        step > MAX_STEP && (step = MIN_STEP);
    } else {
        step -= s;
        step < MIN_STEP && (step = MAX_STEP);
    }

    const degree = (step * 90 * Math.PI) / 180;
    const ctx = canvas.getContext("2d");

    switch (step) {
        case 1:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, 0, -height, width, height);
            break;
        case 2:
            canvas.width = width;
            canvas.height = height;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, -height, width, height);
            break;
        case 3:
            canvas.width = height;
            canvas.height = width;
            ctx.rotate(degree);
            ctx.drawImage(img, -width, 0, width, height);
            break;
        default:
            canvas.width = width;
            canvas.height = height;
            ctx.drawImage(img, 0, 0, width, height);
            break;
    }
};

很老之前的代码了,内容有部分借鉴别人的代码,已经忘记来源了,侵删