在上传前用JavaScript压缩图片

共 10843字,需浏览 22分钟

 ·

2023-08-22 18:35

在这个快速教程中,我们将使用JavaScript压缩带有文件输入元素的图像。我们将压缩图像并将它们保存回文件输入,以便上传。

为了确保用户可以上传图片,并防止超大图片被上传,我们可以在上传前压缩图片数据,而不必向用户提出各种要求。

如果您赶时间,或者觉得阅读代码本身更方便,可以跳转到这里的最终代码片段[1]

获取选定的图像文件

在下面的示例中,我们将接受所有类型的文件,但只压缩图像。multiple 属性允许选择多个文件。

为了防止添加其他文件,我们可以将文件输入接受属性设置为 image/*

让我们设置一个文件输入框,当用户选择了一个或多个文件时,我们将侦听 change 来检测。我们将在下一节处理 TODO 项目。

<input type="file" multiple class="my-image-field" />

<script>
    // 从文件输入中获取所选文件
    const input = document.querySelector('.my-image-field');
    input.addEventListener('change', (e) => {
        // 获取文件
        const { files } = e.target;

        // 未选定文件
        if (!files.length) return;

        // 对于文件列表中的每个文件
        for (const file of files) {
            // 我们不必压缩非图像文件
            if (!file.type.startsWith('image')) {
                // TODO: 不是图像
            }

            // TODO: 压缩图像
        }

        // TODO: 储存文件
    });
</script>

现在,当用户选择一个或多个图像文件时,我们的代码就会运行。接下来是压缩图像。

压缩图像

我们将实现 compressImage 函数。该函数将把传递的文件对象转换为 ImageBitmap,并将其绘制到 <canvas> 元素中,然后画布将使用  toBlob[2] API 返回压缩后的 JPEG。

请注意,我们也可以要求WEBP,但这(在撰写本文时)在 Safari 上不支持🤷‍♂️

让我们开始吧。

<input type="file" multiple class="my-image-field" />

<script>
    const compressImage = async (file, { quality = 1, type = file.type }) => {
        // 获取图像数据
        const imageBitmap = await createImageBitmap(file);

        // 绘制到画布上
        const canvas = document.createElement('canvas');
        canvas.width = imageBitmap.width;
        canvas.height = imageBitmap.height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(imageBitmap, 00);

        // 变成Blob
        return await new Promise((resolve) =>
            canvas.toBlob(resolve, type, quality)
        );
    };

    // 从文件输入中获取所选文件
    const input = document.querySelector('.my-image-field');
    input.addEventListener('change'async (e) => {
        // 获取文件
        const { files } = e.target;

        // 未选择文件
        if (!files.length) return;

        // 对于文件列表中的每个文件
        for (const file of files) {
            // 我们不必压缩非图像文件
            if (!file.type.startsWith('image')) {
                // TODO: 不是图像
            }

            // 我们将文件压缩 50%
            const compressedFile = await compressImage(file, {
                // 0: 为最大压缩率
                // 1: 没有压缩
                quality0.5,

                // 我们想要一个 JPEG 文件
                type'image/jpeg',
            });
        }

        // TODO: 储存文件
    });
</script>

现在,compressedFile 变量将包含压缩后的图像文件。

接下来就是将压缩后的图像文件(以及忽略的文件)保存回文件输入端。

将压缩图像保存回文件输入端

我们将创建一个 DataTransfer 对象,用它来创建我们的新文件列表。之后,我们可以将该列表保存回文件输入端

DataTransfer 对象只接受 File 对象,因此我们需要将 Blob 转换为文件,让我们更新 compressImage 函数。

<input type="file" multiple class="my-image-field" />

<script>
    const compressImage = async (file, { quality = 1, type = file.type }) => {
        // 获取图像数据
        const imageBitmap = await createImageBitmap(file);

        // 绘制到画布上
        const canvas = document.createElement('canvas');
        canvas.width = imageBitmap.width;
        canvas.height = imageBitmap.height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(imageBitmap, 00);

        // 变成Blob
        const blob = await new Promise((resolve) =>
            canvas.toBlob(resolve, type, quality)
        );

        // 将 Blob 变为 File
        return new File([blob], file.name, {
            type: blob.type,
        });
    };

    // 从文件输入中获取所选文件
    const input = document.querySelector('.my-image-field');
    input.addEventListener('change'async (e) => {
        // 获取文件
        const { files } = e.target;

        // 未选择文件
        if (!files.length) return;

        // 对于文件列表中的每个文件
        for (const file of files) {
            // 我们不必压缩非图像文件
            if (!file.type.startsWith('image')) {
                // TODO: 不是图像
            }

            // 我们将文件压缩 50%
            const compressedFile = await compressImage(file, {
                // 0: 为最大压缩率
                // 1: 没有压缩
                quality0.5,

                // 我们想要一个 JPEG 文件
                type'image/jpeg',
            });

            // 保存压缩文件而不是原始文件
            dataTransfer.items.add(compressedFile);
        }

        // 将文件输入值设置为我们的新文件列表
        e.target.files = dataTransfer.files;
    });
</script>

总结

我们已经学会了如何使用 canvas 和 DataTransfer 来设置一个不起眼的小脚本,帮助我们在上传前压缩图片。该脚本将使我们的用户更容易上传图片。

当然,我们还有很多需要改进的地方,例如,我们可以

  • 调整图像大小以适应最大边界框。

  • 将图像裁剪成一定的长宽比。

  • 更好地处理错误输入并显示合适的错误信息。

  • 确保我们能处理 Safari 内存问题。

如果我们需要一个更强大的解决方案,我们可以使用 FilePond[3] 来处理文件上传,它可以调整图像大小、压缩等。

如果我们需要更多的控制,我们可以使用 Pintura[4] 让用户在上传前编辑图片,Pintura 也可以直接替代我们的 compressImage 功能。


原文:https://pqina.nl/blog/compress-image-before-upload

参考资料

[1]

跳转到这里的最终代码片段: https://pqina.nl/blog/compress-image-before-upload#saving-the-compressed-image-back-to-the-file-input

[2]

toBlob: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob

[3]

FilePond: https://pqina.nl/filepond/

[4]

Pintura: https://pqina.nl/pintura/

浏览 223
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报