美文网首页
前端JS图片压缩插件(df_compressImg)

前端JS图片压缩插件(df_compressImg)

作者: 锋叔 | 来源:发表于2021-07-28 19:43 被阅读0次

随着手机摄像机像素的提高,动不动就要拍月亮做微焦整3D搞VR,图片视频的压缩技术已经越来越被重视。因为项目需要随即看了几天资料,自己动手封装了一个前端压缩图片的方法。

预览下效果先

压缩前

image.png
压缩后
image.png

第一步:

  • 理解前端压缩的原理,理解下来发现吧前端压缩实际上是个高损压缩!无非是利用canvas重绘图片降低像素和质量。网上有更好的tingPng tingJPEG更高大上!!可是奈何我实力有限,物质匮乏,即不能理解借鉴模仿其算法,亦囊中羞涩舍不得几张钞票,只好退而求其次用个高损方法撸一撸罢了。

第二步:

  • 兼容移动端,一番考察发现ios竟然对canvas支持不充分,不允许高像素图片绘制!!还有图片旋转倒横各种问题需要解决!!
  • 欲用算法脑不够,将用画布兼容烂,前端难,前端难,搞后台,也很难!
  • 头顶黑发渐萧索,脸滚键盘写插件。

第三步:

  • 插件能干嘛:对jpg/jpeg/png图片进行高效压缩,支持多张图片同时压缩,支持移动端图片压缩,可校正图片角度。

第四步:

  • 封装插件(df_compressImg.js)
/**
 * 
 * 作者:Z_D_F
 * 
 * 邮箱地址:1564950920@qq.com
 * 
 * 日期:2020-7-28
 * 
 * 插件功能:前端图片压缩,可传单张或者多张图片数据,可选择返回图片类型,可手动选择压缩率(0-1)可校正图片倾斜度。
 * 
 * 兼容PC移动端(H5)
 * 
 * 
 * 
 * 原理:
 * 
 * 基于canvas重绘并生成新的图片,实现有损的前端压缩,可压缩png/jpeg/jpg格式的图片,压缩率可选(0.1-1),推荐0.5(可保证一定质量的低损压缩),无惧质量比如头像图片推荐0.2,可实现压缩效率。
 * 
 * 针对移动端图片的图片上传进行了细致的处理,例如角度调整(EXIF),超大型图片的处理。
 * 
 */
;(function (global, undefined) {
    "use strict" //使用js严格模式检查,使语法更规范
    let _global;
    /**
     * 
     * @param {fileArray} files 需要压缩的图片文件集合
     * @param {Function name(params) {}} getBlobList 钩子函数返回压缩后的文件集合
     * @param {Number} quality 压缩率
     * @param {String} returnType 
     * @returns 
     */
    function dfCompressImage(fileList, getBlobList, quality, returnType) {
        let blobList = []; //压缩后的二进制图片数据列表
        let qlty = quality || 0.2; //图片压缩品质,默认是0.2,可选范围是0-1的数字类型的值,可配置
        let rType = returnType || "bold"; // 返回压缩后图片的类型,默认二进制流,可选base64。
        // 判断参数fileList的长度是否大于0
        if (!fileList) {
            console.error('警告:参数fileList不能为空!!!')
            return;
        }
        // 判断参数fileList的长度是否大于0
        if (!fileList.length) {
            console.error('警告:传进方法process的参数fileList长度必须大于零!!!')
            return;
        }

        // 把传进来的fileList转为数组类型
        let files = Array.prototype.slice.call(fileList);
        // console.log(fileList, "fileList")
        return new Promise((resolve, reject) => {
            files.forEach((file, i) => {

                if (!/\/(?:jpeg|png)/i.test(file.type)) {
                    console.error('警告:图片必须是jpeg||png类型!!!');
                    return;
                }
                // 处理压缩图片
                readerFile(file, qlty, rType, blobList, getBlobList, fileList);
    
            })
    
            if (!getBlobList) {
                resolve(blobList);
            }
        });
    }


    function readerFile(file, qlty, rType, blobList, getBlobList, fileList) {
        let reader = new FileReader();
        reader.onload = function () {
            let image = new Image();
            image.src = this.result;

            let canvas = document.createElement("canvas"); //用于压缩图片(纠正图片方向)的canvas
            let context = canvas.getContext('2d');
            // 图片加载完毕之后进行压缩,否则获取不到你的图片宽高。
            if (image.complete) {
                callback();
            } else {
                image.onload = callback;
            }

            function callback() {
                // 这一步很关键
                let imgWidth = image.width;
                let imgHeight = image.height;
                var originWidth = image.width
                var originHeight = image.height
                if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
                    var maxWidth = 1600
                    var maxHeight = 1600
                    imgWidth = originWidth
                    imgHeight = originHeight
                    // 图片尺寸超过的限制
                    if (originWidth > maxWidth || originHeight > maxHeight) {
                        if (originWidth / originHeight > maxWidth / maxHeight) {
                            // 更宽,按照宽度限定尺寸
                            imgWidth = maxWidth
                            imgHeight = Math.round(maxWidth * (originHeight / originWidth))
                        } else {
                            imgHeight = maxHeight
                            imgWidth = Math.round(maxHeight * (originWidth / originHeight))
                        }
                    }
                }
                if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
                    maxWidth = 1400
                    maxHeight = 1400
                    imgWidth = originWidth
                    imgHeight = originHeight
                    // 图片尺寸超过的限制
                    if (originWidth > maxWidth || originHeight > maxHeight) {
                        if (originWidth / originHeight > maxWidth / maxHeight) {
                            // 更宽,按照宽度限定尺寸
                            imgWidth = maxWidth
                            imgHeight = Math.round(maxWidth * (originHeight / originWidth))
                        } else {
                            imgHeight = maxHeight
                            imgWidth = Math.round(maxHeight * (originWidth / originHeight))
                        }
                    }
                }
                let Orientation = ''; //图片方向角
                // 角度判断(如果角度不对就纠正)
                EXIF.getData(image, function () {
                    EXIF.getAllTags(image);
                    Orientation = EXIF.getTag(image, 'Orientation');
                    if (Orientation == "" || Orientation == undefined || Orientation == null) {
                        Orientation = 1;
                    }
                });
                if (Orientation && Orientation != 1) {
                    switch (Orientation) {
                        case 6: // 需要顺时针90度旋转
                            canvas.width = imgHeight;
                            canvas.height = imgWidth;
                            context.fillStyle = "#fff"; // 铺底色
                            context.fillRect(0, 0, width, height);
                            context.rotate(Math.PI / 2);
                            context.clearRect(0, 0, imgWidth, imgHeight)
                            context.drawImage(image, 0, -imgHeight, imgWidth, imgHeight);
                            break;
                        case 3: // 需要180度旋转
                            context.fillStyle = "#fff"; // 铺底色
                            context.rotate(Math.PI);
                            context.clearRect(0, 0, imgWidth, imgHeight)
                            context.drawImage(image, -imgWidth, -imgHeight, imgWidth, imgHeight);
                            break;
                        case 8: // 需要逆时针90度旋转
                            canvas.width = imgHeight;
                            canvas.height = imgWidth;
                            context.fillStyle = "#fff"; // 铺底色
                            context.rotate(3 * Math.PI / 2);
                            context.clearRect(0, 0, imgWidth, imgHeight)
                            context.drawImage(image, -imgWidth, 0, imgWidth, imgHeight);
                            break;
                    }
                } else {
                    canvas.width = imgWidth
                    canvas.height = imgHeight
                    context.fillStyle = "#fff"; // 铺底色
                    context.clearRect(0, 0, imgWidth, imgHeight)
                    context.drawImage(image, 0, 0, imgWidth, imgHeight);
                }


                // 获取图片压缩前大小,打印图片压缩前大小
                let size = file.size / 1024 > 1024 ? (~~(10 * file.size / 1024 / 1024)) / 10 + "MB" : ~~(file.size / 1024);
                let boldFile, canvasURL;
                if (size < 400) {
                    blobList.push(file);
                } else {
                    //获取压缩后的图片二进制数据
                    canvasURL = canvas.toDataURL('image/jpeg', qlty)
                    const buffer = atob(canvasURL.split(',')[1])
                    let length = buffer.length
                    const bufferArray = new Uint8Array(new ArrayBuffer(length))
                    while (length--) {
                      bufferArray[length] = buffer.charCodeAt(length)
                    }
                    boldFile = new File([bufferArray], file.name, {
                      type: 'image/jpeg'
                    })

                    // let boldFile = file.type == "image/jpeg" ? canvasURL : dataURItoBlob(canvasURL);

                    // boldFile = dataURItoBlob(canvasURL);

                    blobList.push(boldFile);
                }

                //将压缩后的二进制图片数据对象(blob)组成的list通过钩子函数(getBlobList)返回出去
                if (blobList.length === fileList.length) {
                    if (getBlobList){
                        getBlobList(blobList);
                    }
                        
                }

                //清除canvas画布的宽高
                canvas.width = canvas.height = 0;
                // 清除image对象
                image = null;
            }
        };
        reader.readAsDataURL(file);
    }

    // function dfCompressImage(){}
    // dfCompressImage.prototype.compress = compress
    // 最后将插件对象暴露给全局对象
    _global = (function () {
        return this || (0, eval)('this');
    }());

    if (typeof module !== "undefined" && module.exports) {
        module.exports = dfCompressImage;
    } else if (typeof define === "function" && define.amd) {
        define(function () {
            return dfCompressImage;
        });
    } else {
        !('dfCompressImage' in _global) && (_global.dfCompressImage = dfCompressImage);
    }
}());

依赖

  • 依赖于exif.js,可自行百度谷歌获取资源,我放个链接可能以后就失效了。

使用

  • 建立文件夹df_demo,新建js文件夹,放入exif.js和上面的df_compressImg.js,新建index.html
    index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <title>图片压缩</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .image-content {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }
        li {
            list-style-type: none;
        }

        a,
        input {
            outline: none;
            -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        }

        #fileInput {
            display: none;
        }
        .title {
            padding: 10px 20px;
            font-size: 16px;
            font-weight: bold;
        }
        canvas {
            width: 100%;
            border: 1px solid #000000;
        }

        #addImg {
            display: block;
            margin: 10px;
            height: 40px;
            width: 200px;
            text-align: center;
            line-height: 40px;
            border: 1px solid;
            border-radius: 5px;
            cursor: pointer;
        }

        .img_list,
        .imgCompress_list {
            width: calc(100% - 42px);
            max-width: 980px;
            min-height: 100px;
            margin: 10px 20px;
            border: 1px solid black;
            border-radius: 5px;
            padding: 10px;
        }

        .img_list li,
        .imgCompress_list li {
            position: relative;
            display: inline-block;
            width: 100px;
            height: 100px;
            margin: 5px 5px 10px 5px;
            border: 1px solid rgb(100, 149, 198);
            background: #fff no-repeat center;
            background-size: cover;
        }

        .progress {
            position: absolute;
            width: 100%;
            height: 20px;
            line-height: 20px;
            bottom: 0;
            left: 0;
            background-color: rgba(100, 149, 198, .5);
        }

        .progress span {
            display: block;
            width: 0;
            height: 100%;
            background-color: rgb(100, 149, 198);
            text-align: center;
            color: #FFF;
            font-size: 13px;
        }

        .size {
            position: absolute;
            width: 100%;
            height: 15px;
            line-height: 15px;
            bottom: -18px;
            text-align: center;
            font-size: 13px;
            color: #666;
        }

        .tips {
            display: block;
            text-align: center;
            font-size: 13px;
            margin: 10px;
            color: #999;
        }

    </style>
</head>

<body>
    <div class="image-content">
        <input type="file" id="fileInput" capture="camera" accept="image/*" multiple>
        
        <p class="title">压缩前</p>
        <ul class="img_list">
        </ul>
        
        <a id="addImg">选择压缩图片</a>
        <span class="tips">只允许上传jpg、png</span>
    
        <p class="title">压缩后</p>
        <ul class="imgCompress_list">
        </ul>
    </div>

    <!-- df_compressImg是压缩图片插件,exif.js是矫正图片方向的插件,前者是依赖后者的需要一起引入且需要后者在前面引入 -->
    <script src="js/exif.js"></script>
    <script src="js/df_compressImg.js"></script>

    <script type="text/javascript">
        var fileInputEle = document.getElementById("fileInput");

        //用来存储压缩后的图片二进制数据
        var blobFileList;

        //点击添加图片
        document.getElementById('addImg').onclick = function () {
            fileInputEle.click();
        }


        // 监听上传组件input的onchange事件,压缩图片
        fileInputEle.onchange = function () {
            var fileList = this.files;

            // console.log('fileList:', fileList);

            //预览压缩前的图片
            var files = Array.prototype.slice.call(fileList);

            files.forEach(function (file, i) {
                var reader = new FileReader();
                reader.onload = function () {
                    var li = document.createElement("LI")
                    li.style.backgroundImage = 'url(' + this.result + ')';
                    document.querySelector('.img_list').appendChild(li)
                }
                reader.readAsDataURL(file);
            });

            // 调用压缩方法传入钩子函数
            // dfCompressImage(this.files, getBlobList, 0.2);

            // 直接使用链式调用
            dfCompressImage(this.files, false, 0.2).then(res => {
                getBlobList(res)
            });
        }
        // 钩子函数
        function getBlobList(fileList) {
            fileList.forEach(function (blob) {
                var reader = new FileReader();
                reader.onload = function () {
                    var li = document.createElement("LI")
                    li.style.backgroundImage = 'url(' + this.result + ')';
                    document.querySelector('.imgCompress_list').appendChild(li)
                }
                reader.readAsDataURL(blob);
            })
        }
    </script>
</body>

</html>

相关文章

网友评论

      本文标题:前端JS图片压缩插件(df_compressImg)

      本文链接:https://www.haomeiwen.com/subject/cqhomltx.html