前端实现压缩图片上传

760 阅读1分钟

前端在做上传图片的时候往往需要压缩之后再传给服务器,下面就是通过利用canvas.toDataURL()方法实现压缩图片大小。具体步骤:

  • input 读取到文件 ,使用 FileReader 将其转换为 base64 编码
  • 新建 img ,使其 src 指向刚刚的 base64
  • 新建 canvas ,将 img 画到 canvas 上
  • 利用 canvas.toDataURL/toBlob 将 canvas 导出为 base64 或 Blob
  • 将 base64 或 Blob 转化为 File

直接上代码:

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传压缩图片</title>
</head>

<body>
    <input type="file" id="input" accept="image/*" onchange="fileChange(event)" multiple>
    <img id="img" src="" alt="">

    <script>
        let fileId = document.getElementById('input')
        let img = document.getElementById('img')
        let body = document.getElementsByTagName('body')[0]

        function creatImg(list) {
            const imgBox = document.createElement('div')
            list.forEach(item => {
                const img = document.createElement('img')
                img.src = item.afterSrc
                imgBox.appendChild(img)
            })
            body.appendChild(imgBox)
        }

        async function fileChange(e) {
            let fileList = e.target.files
            console.log('file', fileList)
            if (fileList.length > 1) {
                const resList = await Promise.all(Array.from(fileList).map(e => compressImg(e,
                    0.1))) // 如果是 file 数组返回 Promise 数组
                creatImg(resList)
                console.log('resList 多张', resList)
            } else {
                compressImg(fileList[0], 0.7).then(res => {
                    console.log('res 单张', res)
                    img.src = res.afterSrc
                })
            }

        }

        /**
         * 压缩方法 
         * @param {string} file 文件
         * @param {Number} quality  0~1之间
        */
        function compressImg(file, quality) {
            return new Promise((resolve) => {
                const fileSize = parseInt((file.size / 1024).toFixed(2))
                var qualitys = 0.52
                if (fileSize < 1024) {
                    qualitys = 0.85
                }
                if (5 * 1024 < fileSize) {
                    qualitys = 0.92
                }
                if (quality) {
                    qualitys = quality
                }

                const reader = new FileReader() // 创建 FileReader
                reader.onload = ({
                    target: {
                        result: src // src是文件读取完成后解构出的base64图片
                    }
                }) => {
                    const image = new Image() // 创建 img 元素
                    image.onload = async () => {
                        const canvas = document.createElement('canvas') // 创建 canvas 元素
                        const context = canvas.getContext('2d')
                        // 根据图片size控制图片宽高,以减少size体积
                        var targetWidth = image.width
                        var targetHeight = image.height
                        var originWidth = image.width
                        var originHeight = image.height

                        // 图片在1M到10M之间
                        if (1 * 1024 <= fileSize && fileSize <= 10 * 1024) {
                            var maxWidth = 1600
                            var maxHeight = 1600
                            targetWidth = originWidth
                            targetHeight = originHeight
                            // 图片尺寸超过的限制
                            if (originWidth > maxWidth || originHeight > maxHeight) {
                                if (originWidth / originHeight > maxWidth / maxHeight) {
                                    // 更宽,按照宽度限定尺寸
                                    targetWidth = maxWidth
                                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                                } else {
                                    targetHeight = maxHeight
                                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                                }
                            }
                        }
                        // 图片在10M到20M之间
                        if (10 * 1024 <= fileSize && fileSize <= 20 * 1024) {
                            console.log("图片在10M到20M之间")
                            maxWidth = 1400
                            maxHeight = 1400
                            targetWidth = originWidth
                            targetHeight = originHeight
                            // 图片尺寸超过的限制
                            if (originWidth > maxWidth || originHeight > maxHeight) {
                                if (originWidth / originHeight > maxWidth / maxHeight) {
                                    // 更宽,按照宽度限定尺寸
                                    targetWidth = maxWidth
                                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                                } else {
                                    targetHeight = maxHeight
                                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                                }
                            }
                        }
                        canvas.width = targetWidth
                        canvas.height = targetHeight
                        context.clearRect(0, 0, targetWidth, targetHeight)
                        // 解决背景为透明时背景变黑
                        context.fillStyle = "#fff"
                        context.fillRect(0, 0, targetWidth, targetHeight);
                        context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
                        let canvasURL = ""

                        // 图片小于300kb 不用压缩
                        if (fileSize < 300) {
                            canvasURL = canvas.toDataURL()
                            console.log("图片小于300kb 不用压缩")
                        } else {
                            // toDataURL 压缩图片质量,图片类型指定为image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量, 数值越接近1,图片质量越好,图片size也就越大
                            canvasURL = canvas.toDataURL('image/jpeg', quality)
                        }
                        const miniFile = convertBase64ToFile(file, canvasURL)
                        resolve({
                            file: miniFile, // 压缩后的file文件
                            origin: file, // 压缩前file文件
                            beforeSrc: src, // 压缩前base64图片
                            afterSrc: canvasURL, // 压缩后base64图片
                            beforeKB: Number((file.size / 1024).toFixed(2)), // 压缩前的大小
                            afterKB: Number((miniFile.size / 1024).toFixed(2)) // 压缩后的大小
                        })
                    }
                    image.src = src
                }
                // readAsDataURL:读取操作完成时,触发loadend事件,同时 result 属性将包含一个base64编码的内容
                reader.readAsDataURL(file)
            })
        }

        /**
         * 将base64的图片转成文件流形式
         * @param {string} file 原文件
         * @param {string} base64  压缩后的base64图片编码
         */
        function convertBase64ToFile(file, base64) {
            // atob:对base64进行解码
            const buffer = atob(base64.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: 'image/jpeg'
            })
            return miniFile
        }

    </script>
</body>

</html>