乐趣区

关于前端:在上传前用JavaScript压缩图片

在这个疾速教程中,咱们将应用 JavaScript 压缩带有文件输出元素的图像。咱们将压缩图像并将它们保留回文件输出,以便上传。

为了确保用户能够上传图片,并避免超大图片被上传,咱们能够在上传前压缩图片数据,而不用向用户提出各种要求。

如果您赶时间,或者感觉浏览代码自身更不便,能够跳转到这里的最终代码片段。

获取选定的图像文件

在上面的示例中,咱们将承受所有类型的文件,但只压缩图像。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 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, 0, 0);

        // 变成 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: 没有压缩
                quality: 0.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, 0, 0);

        // 变成 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: 没有压缩
                quality: 0.5,

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

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

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

总结

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

当然,咱们还有很多须要改良的中央,例如,咱们能够

  • 调整图像大小以适应最大边界框。
  • 将图像裁剪成肯定的长宽比。
  • 更好地处理错误输出并显示适合的错误信息。
  • 确保咱们能解决 Safari 内存问题。

如果咱们须要一个更弱小的解决方案,咱们能够应用 FilePond 来解决文件上传,它能够调整图像大小、压缩等。

如果咱们须要更多的管制,咱们能够应用 Pintura 让用户在上传前编辑图片,Pintura 也能够间接代替咱们的 compressImage 性能。

退出移动版