面试官:如何使用 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 中可以调整图像质量,需要注意的是我们在压缩图像时要注意等宽高缩放,否则会导致图像出现失真的情况。

相关推荐
uhakadotcom1 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰1 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪1 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪1 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy2 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom2 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom3 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom3 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom3 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试