前端JS圖片壓縮插件(df_compressImg)

隨着手機攝像機像素的提高,動不動就要拍月亮做微焦整3D搞VR,圖片視頻的壓縮技術已經越來越被重視。因爲項目需要隨即看了幾天資料,自己動手封裝了一個前端壓縮圖片的方法。

預覽下效果先

壓縮前


壓縮後

第一步:

  • 理解前端壓縮的原理,理解下來發現吧前端壓縮實際上是個高損壓縮!無非是利用canvas重繪圖片降低像素和質量。網上有更好的tingPng tingJPEG更高大上!!可是奈何我實力有限,物質匱乏,即不能理解借鑑模仿其算法,亦囊中羞澀捨不得幾張鈔票,只好退而求其次用個高損方法擼一擼罷了。

第二步:

  • 兼容移動端,一番考察發現ios竟然對canvas支持不充分,不允許高像素圖片繪製!!還有圖片旋轉倒橫各種問題需要解決!!
  • 欲用算法腦不夠,將用畫布兼容爛,前端難,前端難,搞後臺,也很難!
  • 頭頂黑髮漸蕭索,臉滾鍵盤寫插件。

第三步:

  • 插件能幹嘛:對jpg/jpeg/png圖片進行高效壓縮,支持多張圖片同時壓縮,支持移動端圖片壓縮,可校正圖片角度。

第四步:

  • 封裝插件(df_compressImg.js)
/**
 * 
 * 作者:Z_D_F
 * 
 * 郵箱地址:[email protected]
 * 
 * 日期: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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章