面试官:如何使用 JS 压缩图片上传

前言

相信大家都做过图片上传相关的功能,在图片上传的过程中,不知道大家有没有考虑过文件体积的问题,如果我们直接将原图片上传,可以图片体积比较大,一是上传速度较慢,二是前端进行渲染时速度也比较慢,比较影响客户的体验感。所以在不影响清晰度的情况下,前端可以在上传前对图片的大小体积进行压缩,压缩到一个比较合适的大小进行上传,本文就带大家一起来看看前端 JS 如何实现图片压缩,有需要的小伙伴抓紧收藏一下吧!

原理(必看)

省流:主要使用 canvas 的 drawImage 方法先绘制为 canvas 图像,再结合 toDataURL 转化为DataURl 进行存储图片链接以及图片质量调整。

drawImage简单介绍

Canvas 2D API 中的 CanvasRenderingContext2D.drawImage() 方法提供了多种在画布Canvas)上绘制图像的方式。

用法如下:

CanvasRenderingContext2D.drawImage() - Web API 接口参考 | MDN (mozilla.org)

语法如下:

js 复制代码
drawImage(image, dx, dy);
drawImage(image, dx, dy, dWidth, dHeight);
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

我们使用第二种进行绘制,参数含义如下:

image:绘制到上下文的元素。

dximage 的左上角在目标画布上 X 轴坐标。

dy: image 的左上角在目标画布上 Y 轴坐标。

dWidth: image 在目标画布上绘制的宽度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 宽度不会缩放。

dHeight: image 在目标画布上绘制的高度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 高度不会缩放。

简单示例

注意: 如果随意的修改图像的尺寸,会导致图像失真,我们可以先获取到图像资源的原始尺寸,然后进行等比缩放,意思就是当我们确定设置宽度之后,高度要进行等比调整。公式就是交叉相乘积相等。

// 如果宽度设置为 500, 那么高度也应该进行等比缩放

// naturalWidth => 500

// naturalHeight => X

// naturalWidth * X = naturalHeight * 500

// 计算得出高度

X = naturalHeight * 500 / naturalWidth

js 复制代码
  var can = document.querySelector('canvas')
  var context = can.getContext('2d')   
  var imgDom = new Image();
    imgDom.src = './img.jpg';
    imgDom.onload = function () {
        // 注意:图像绘制时,必须保证资源已经加载完成
        console.log('图片的原始宽度', imgDom.naturalWidth);
        console.log('图片的原始高度', imgDom.naturalHeight);

        context.drawImage(
            imgDom,
            0, 0,
            500, imgDom.naturalHeight * 500 / imgDom.naturalWidth
        );
    }

toDataURL简单介绍

我们将图片绘制到 canvas 之后,还需要将 canvas 转化为 Data URl,转化为 DataURl 之后可以显示到我们的屏幕上面,也可以存放到后端服务器,使用 canvas 所提供的 toDataURL 实例方法即可。

​ 官方解释:HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI ​

HTMLCanvasElement.toDataURL() - Web API 接口参考 | MDN (mozilla.org)

语法:canvas.toDataURL(type, encoderOptions);

type(可选) :图片格式,默认为 image/png

encoderOptions(可选) :在指定图片格式为 image/jpegimage/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

简单示例

js 复制代码
       // 获取压缩后的图片数据
      can.width = imgDom.naturalWidth
      can.height = imgDom.naturalHeight

      const compressedData = can.toDataURL('image/jpeg', 0.6) // 可调整质量参数
      console.log('compressedData: ', compressedData)

转化后 DataURL 结果如下

实现

先奉上全部代码,方便大家看,下面进行解释!

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>图片压缩上传</title>
    <meta charset="UTF-8">
</head>
<body>
    <input type="file" id="fileInput" accept="image/*">
    <button onclick="compressAndUpload()">压缩并上传图片</button>
    <canvas id="canvas" style="display: none;"></canvas>
    <script>
        function compressAndUpload() {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];
            if (!file) {
                alert('请先选择要上传的图片');
                return;
            }
            const reader = new FileReader();
            reader.onload = function () {
                const img = new Image();
                img.src = reader.result;
                img.onload = function () {
                    const canvas = document.getElementById('canvas');
                    const ctx = canvas.getContext('2d');
                    const maxWidth = 800; // 设置最大宽度为800像素
                    let width = img.width;
                    let height = img.height;

                    // 判断是否需要缩放
                    if (width > maxWidth) {
                        height *= maxWidth / width;
                        width = maxWidth;
                    }
                    // 设置 canvas 的宽高
                    canvas.width = width;
                    canvas.height = height;

                    // 将图片绘制到 canvas 上
                    ctx.drawImage(img, 0, 0, width, height);
                    // 获取压缩后的图片数据
                    const compressedData = canvas.toDataURL('image/jpeg', 0.7); // 可调整质量参数

                    // 创建一个新的压缩后的 File 对象
                    const compressedFile = dataURItoBlob(compressedData, file.type);
                    compressedFile.lastModifiedDate = file.lastModifiedDate;
                    compressedFile.name = file.name;

                    // 上传压缩后的图片文件
                    uploadImage(compressedFile);
                };
            };
            reader.readAsDataURL(file);
        }

        function dataURItoBlob(dataURI, mimeType) {
            const binary = atob(dataURI.split(',')[1]);
            const array = [];
            for (let i = 0; i < binary.length; i++) {
                array.push(binary.charCodeAt(i));
            }
            return new Blob([new Uint8Array(array)], { type: mimeType });
        }

        function uploadImage(compressedFile) {
            const formData = new FormData();
            formData.append('image', compressedFile);

            fetch('/upload', {
                method: 'POST',
                body: formData
            })
            .then(response => {
                if (response.ok) {
                    console.log('图片上传成功');
                } else {
                    console.error('图片上传失败');
                }
            })
            .catch(error => {
                console.error('发生错误:', error);
            });
        }
    </script>
</body>
</html>

我们看一下压缩前后体积对比,压缩前550290,压缩后31523,缩小了十几倍,这个压缩还是很明显的。

首先我们看这三行代码

我们先初始化一个 reader 是一个 FileReader 对象的实例

reader.readAsDataURL(file),这行代码的作用是将选择的文件读取为 Data URI 格式的字符串。

当执行 reader.readAsDataURL(file) 时,会发生以下几件事情:

  1. FileReader 对象开始异步读取 file 中的数据。
  2. 一旦读取完成,FileReaderonload 事件将被触发。
  3. 读取的结果将存储在 FileReader 对象的 result 属性中,格式为 Data URI 字符串。

看一下 FileReader 对象的 result 的打印结果,为 Data URL格式

最终我们将读取出来的 Data URI 字符串赋值给 Image 的 src,也就是下面这行代码,然后等待 img 加载完毕开始对 img 进行压缩,具体怎么压缩上面已经简单演示过。

js 复制代码
          const img = new Image()
          img.src = reader.result
          img.onload = function () {}

接下来我们设置了一个最大宽度为800,然后判断当前图片宽度是否大于该值,如果大于进行缩放计算,小于就不进行等比缩放计算。最后将计算出的值使用 drawImage 绘制到 canvas 上面。

js 复制代码
                    const maxWidth = 800; // 设置最大宽度为800像素
                    let width = img.width;
                    let height = img.height;

                    // 判断是否需要缩放
                    if (width > maxWidth) {
                        height *= maxWidth / width;
                        width = maxWidth;
                    }

                    // 设置 canvas 的宽高
                    canvas.width = width;
                    canvas.height = height;

                    // 将图片绘制到 canvas 上
                    ctx.drawImage(img, 0, 0, width, height);

现在我们将 canvas 转化成Data URI 字符串,canvas.toDataURL('image/jpeg', 0.7),这行代码的作用是将 canvas 上绘制的图像数据导出为 JPEG 格式的 Data URI 字符串,并设置图像质量为 0.7。

js 复制代码
// 获取压缩后的图片数据
const compressedData = canvas.toDataURL('image/jpeg', 0.7); // 可调整

我们看一下 compressedData,是一个Data URI 字符串,可以转化为文件格式进行存储。

接下来就是创建一个新的压缩后的 File 对象

js 复制代码
      const compressedFile = dataURItoBlob(compressedData, file.type)
      compressedFile.lastModifiedDate = file.lastModifiedDate
      compressedFile.name = file.name

      function dataURItoBlob(dataURI, mimeType) {
        const binary = atob(dataURI.split(',')[1])
        const array = []
        for (let i = 0; i < binary.length; i++) {
          array.push(binary.charCodeAt(i))
        }
        return new Blob([new Uint8Array(array)], { type: mimeType })
      }

新的文件对象

最后直接使用 FormData 进行上传即可,这一块就不说了。

总结

前端实现图片压缩主要是利用的 canvas 来实现,实现思路为使用 canvas 的 drawImage 方法先绘制为 canvas 图像,再结合 toDataURL 转化为 DataURl 进行存储图片链接以及压缩图像质量。在 toDataURL 中可以调整图像质量,需要注意的是我们在压缩图像时要注意等宽高缩放,否则会导致图像出现失真的情况。

相关推荐
小曲曲1 小时前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS3 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年7 小时前
react中useMemo的使用场景
前端·react.js·前端框架