zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

前端:Canvas进行简单图片压缩算法

2023-03-14 22:37:36 时间

图片压缩算法

通过canvasAPI进行在Web端上传的时候进行图片压缩。

通过宽高压缩(第一次压缩 - Canvas宽高) 通过API压缩(第二次压缩 - HTMLCanvasDom.toDataURL())

简述步骤

  1. 通过input输入框用来坐上传,通过chang事件获得上传的文件。
  2. 对上传的文件进行一些简单的类型,大小的判断然后开始压缩。
  3. 压缩图片第一步将用户上传的图片(file)转为base64格式-new FileReader() -> ReaderAsDataUrl()异步读取 -> load事件读取完成获取base64
  4. 计算压缩图片宽高(等比例),创建Canvas画布赋值新的宽高。(第一次压缩尺寸)然后将canvasDom进行隐藏,挂载到dom节点上。
  5. getContext('2d')获得canvas对象,调用drawImage方法进行绘制。
  6. 然后通过CanvasHTMLDOM调用toDataURL方法将整个canvas画布输出成base64格式。(第二次压缩)
  7. 删除CanvasDOM,然后将压缩后的base64通过callback传入到服务端中就可以了。

Tips

  • 通过change事件进行异步完成监听
  • new FileReader() -> reader.readAsDataURL(file) 读取上传文件 => reader.result获得base64格式的上传图片
  • 压缩对宽度和高度进行压缩 naturalWidth/naturalHeihgt HTML5属性获得源生图片高度宽度
  • 创建Canvas标签赋值压缩后的宽高
  • 隐藏使用visibility-hidden 仅仅是样式隐藏。因为visibility属性隐藏可以获取DOM
  • ctx.clearReact() -> 每次重新上传文件要清除Canvas画布,防止图片覆盖。
  • ctx.drawImage()通过canvas绘制对应文件。
  • HTMLCanvasDOM.toDataUrl() 将canvas画布转化成base64格式同时进行压缩,得到返回值。
  • 压缩成功,使用callback传入新的Data URL进行向后台发送逻辑请求。

代码演示

<!DOCTYPE html>
<html lang="en">

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

<body>
    <!-- 图片上传到服务端之前进行图片压缩 -->
    <input type="file" id="file" />
    <script>
        const ACCEPT = ['image/jpg', 'image/png', 'image/jpeg']
        const upload = document.getElementById("file")
        const MAXSIZE = 1024 * 1024;
        const MAXTIP = "1MB"

        function convertImageToBase64(file, callback) {
            // 创建一个FileReader对象,它允许Web应用程序异步读取存储在计算机上的文件
            // 也就是file对象
            let reader = new FileReader()
            // 添加一个load事件,load会在加载完毕之后进行触发(也就是readAsDataURL读取完毕后触发)
            reader.addEventListener("load", function (e) {
                const base64Image = e.target.result // 相当于reader.result 获取文件的Base64
                // 回收内存
                callback && callback(base64Image) // 调用callback压缩
                reader = null
            })

            // readAsDataURL方法读取指定的file或blob对象
            reader.readAsDataURL(file)
        }
        // 压缩算法函数
        /* 
        1.首先拿到了base64的图片字符串 
        2.创建一个image对象,获得原始图片的宽度和高度
        3.对原始图片的宽度和高度进行压缩达到符合条件(第一次压缩-从尺寸压缩)
        4.调用canvasAPI进行绘制新的图片
        5.绘制成功之后调用canvasAPI进行绘制(canvasAPI支持压缩-二次压缩-从质量压缩)
        6.得到压缩后的base64
         */
        function compress(base64Image, callback) {
            let maxW = 1024;
            let maxH = 1024;
            const image = new Image() // 创建image对象 相当于创建a标签
            image.addEventListener('load', function (e) {
                // image加载完成后就会触发 也就是src加载后
                let radio; // 压缩比例
                let needCompress = false; // 是否需要压缩
                // image.naturalWidth/naturalHeight H5新属性 获取源生图片的宽高
                if (image.naturalWidth > maxW) {
                    needCompress = true;
                    // 获得压缩宽高过后的大小(保证等比例缩放)
                    radio = image.naturalWidth / maxW
                    maxH = image.naturalHeight / radio
                }
                // 同样宽度压缩之后 还要看压缩后的高度是否满足 不满足则继续压缩宽高
                if (image.naturalHeight > maxH) {
                    needCompress = true;
                    radio = image.naturalHeight / maxH
                    maxW = image.naturalWidth / radio
                }
                // 不需要压缩
                if (!needCompress) {
                    maxW = image.naturalWidth;
                    maxH = image.naturalHeight;
                }
                // 第一次压缩完成
                // 接下来使用canvas进行质量压缩
                const canvas = document.createElement('canvas')
                canvas.height = maxH;
                canvas.width = maxW;
                canvas.setAttribute("id", "_compress_")
                // visibility hidden 需要创建的canvas隐藏 而不是不渲染DOM
                canvas.style.visibility = 'hidden'
                document.body.appendChild(canvas)

                const ctx = canvas.getContext('2d')
                // canvas.clearRect() 方法清空给定矩形内的指定像素。(x1,y1,width,height)
                // 防止重新上传覆盖 
                ctx.clearRect(0, 0, maxW, maxH)
                // canvas.drawImage() 方法在画布(canvas)上绘制图像、画布或视频。
                // 传入 视频/图片对象 起始点x 起始点y 绘制宽 绘制高 
                ctx.drawImage(image, 0, 0, maxW, maxH)
                // 接来下就是压缩canvas 通过API将canvas输出成base64格式

                /**
                 * @HTMLCanvasElement.toDataURL(type, encoderOptions);  注意调用这是 Canvas的Dom对象而非ctx
                 * @param {String} 可以使用 type 参数其类型, type 图片格式,默认为image/png,图片的分辨率为96dpi。
                 * @param {Number}  encoderOptions 可选 指定图片格式是image/jpeg或image/webp的情况下,可以从0到1区间内进行选择图片的质量(1原质量)。如果超出取值方位,使用默认0.92 
                 * @returns {dataURL}  方法返回一个包含图片展示的 data URI (Base64)
                 */
                console.log(base64Image, 'base64Image')
                /* 注意这里是Canvas DOM 节点 而非canvas对象*/
                const compressImage = canvas.toDataURL('image/jpeg', 0.8) // 通常压缩是0.8-0.9
                callback && callback(compressImage); // 压缩完成进行后台传输逻辑
                // 压缩完的图片就已经保存在内存(compressImage)中了 
                // 接下来移除canvas元素 调用DOM.remove()
                canvas.remove()

                // 需要的上传预览的话可以单独建一个new image进行预览
                const _image = new Image()
                _image.src = compressImage;
                document.body.appendChild(_image)
                //计算压缩比 使用src(base)的长度就可以对比了 
                console.log(`压缩比${image.src.length/_iamge.src.length}`)


            })
            image.src = base64Image;
            // document.body.appendChild(image) // 挂载

        }
        // 绑定上传图片的change事件
        // mock上传后台逻辑
        function uploadToServer(compressImage) {
            console.log("上传后台")
        }
        upload.addEventListener('change', function (e) {
            // 获取上传的文件 解构Arry 拿到第一个元素
            const [file] = e.target.files;
            if (!file) {
                return;
            }
            const {
                type: fileType,
                size: fileSize
            } = file;
            // 上传校验逻辑
            if (!ACCEPT.includes(fileType)) {
                alert(`不支持[${fileType}]类型`)
                upload.value = ""
                return
            }
            if (fileSize > MAXSIZE) {
                alert(`文件超${MAXTIP}`)
                upload.value = ""
                return
            }
            // 压缩图片 需要转成base64进行压缩
            convertImageToBase64(file, (base64Image) => {
                compress(base64Image, uploadToServer)
            })
        })
    </script>
</body>

</html>
复制代码