前端图片压缩实战教程:原生JS实现,体积直降80%

白云博客网

前端图片压缩实战教程:原生JS实现,体积直降80%

在日常前端开发中,图片上传场景(头像上传、相册发布、表单附件提交等)经常会遇到一个痛点:原图体积过大,导致上传速度慢、接口超时、服务器带宽消耗过高,甚至会被后端接口直接拦截。

今天就给大家分享一篇纯前端实现图片压缩的实战教程,无需后端支持、无需引入任何框架,只用原生JS+Canvas,代码可直接复制到项目或博客中使用,新手也能轻松上手,压缩后图片体积直降80%,兼顾压缩效果与图片清晰度。

一、为什么必须做前端图片压缩?

很多开发者会觉得“图片压缩交给后端就行”,但前端压缩的优势的是后端无法替代的,尤其适合个人博客、小型项目:

  • 降低服务器压力:压缩后的图片体积变小,减少服务器存储和带宽消耗,降低运维成本;
  • 提升上传体验:大图上传动辄几秒、十几秒,压缩后可实现秒传,避免用户等待超时;
  • 优化页面性能:压缩后的图片加载更快,有效提升页面LCP指标,改善用户浏览体验;
  • 保护用户隐私:所有图片处理都在本地浏览器完成,无需将原图上传到服务器,避免隐私泄露;
  • 避免接口拦截:很多后端接口会限制图片大小(如5MB以内),前端提前压缩可避免上传失败。

二、核心实现原理(小白也能看懂)

前端图片压缩的核心依赖Canvas的绘图能力,整体流程非常简单,只需5步:

  1. 通过<input type="file">标签获取用户选择的图片File对象;
  2. 使用URL.createObjectURL()方法,将File对象转为临时URL,用于加载图片;
  3. 根据设定的最大宽高,按比例缩放宽高,避免图片拉伸变形;
  4. 将缩放后的图片绘制到Canvas上,通过调整quality参数控制压缩质量;
  5. 将Canvas内容导出为Blob或Base64格式,用于预览、下载或上传到服务器。

三、完整可运行代码(直接复制即用)

新建image-compress.html文件,复制以下代码,双击打开就能直接使用,无需任何配置,包含“选择图片→预览原图→生成压缩图→下载压缩图”全功能:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>前端图片压缩工具</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{padding:20px;background:#f5f7fa;font-family:Arial, "Microsoft YaHei"}
.container{max-width:800px;margin:0 auto;background:#fff;padding:24px;border-radius:12px;box-shadow:0 2px 10px rgba(0,0,0,0.1)}
.title{text-align:center;margin-bottom:20px;color:#333;font-size:24px}
.upload{margin:20px 0;text-align:center}
#file{display:none}
.upload-btn{display:inline-block;padding:10px 20px;background:#409eff;color:#fff;border-radius:6px;cursor:pointer;transition:background 0.3s}
.upload-btn:hover{background:#3086e8}
.preview{display:flex;gap:20px;margin:20px 0;flex-wrap:wrap;justify-content:center}
.box{flex:1;min-width:300px}
.box h4{margin-bottom:10px;color:#444;font-size:18px}
.box img{max-width:100%;border:1px solid #eee;border-radius:8px}
.info{font-size:14px;color:#666;margin:6px 0;line-height:1.5}
.download{margin-top:10px;padding:8px 16px;background:#67c23a;color:#fff;border:none;border-radius:6px;cursor:pointer;transition:background 0.3s}
.download:hover{background:#52a828}
</style>
</head>
<body>
<div class="container">
<h1 class="title">前端图片压缩工具(原生JS实现)</h1>
<div class="upload">
<label for="file" class="upload-btn">选择图片</label>
<input type="file" id="file" accept="image/*">
</div>
<div class="preview">
<div class="box">
<h4>原图</h4>
<img id="originImg" alt="原图预览">
<p class="info" id="originInfo">请选择图片查看原图信息</p>
</div>
<div class="box">
<h4>压缩图</h4>
<img id="compressImg" alt="压缩图预览">
<p class="info" id="compressInfo">压缩后图片信息将显示在这里</p>
<button class="download" id="download" style="display:none">下载压缩图</button>
</div>
</div>
</div>

<script>
// 监听图片选择事件
document.getElementById('file').addEventListener('change', async e => {
    const file = e.target.files[0];
    if (!file) return; // 未选择图片则退出
    // 显示原图及信息
    showOrigin(file);
    // 执行压缩,默认参数(可自定义)
    const compressBlob = await compressImage(file, {
        maxWidth: 1600,  // 最大宽度,超出则等比缩放
        maxHeight: 1600, // 最大高度,超出则等比缩放
        quality: 0.7     // 压缩质量(0-1,越大越清晰,体积越大)
    });
    // 显示压缩图及信息
    showCompress(compressBlob);
});

// 显示原图及大小信息
function showOrigin(file) {
    const originImg = document.getElementById('originImg');
    const originInfo = document.getElementById('originInfo');
    // 生成原图临时URL
    const originUrl = URL.createObjectURL(file);
    originImg.src = originUrl;
    // 显示原图大小(转换为MB)
    const originSize = (file.size / 1024 / 1024).toFixed(2);
    originInfo.textContent = `大小:${originSize}MB | 格式:${file.type}`;
}

// 图片压缩核心函数(返回压缩后的Blob对象)
function compressImage(file, { maxWidth = 1600, maxHeight = 1600, quality = 0.7 } = {}) {
    return new Promise((resolve) => {
        const img = new Image();
        // 加载图片
        img.src = URL.createObjectURL(file);
        img.onload = () => {
            let width = img.width;
            let height = img.height;

            // 等比缩放宽高,避免拉伸
            if (width > maxWidth) {
                height = height * (maxWidth / width);
                width = maxWidth;
            }
            if (height > maxHeight) {
                width = width * (maxHeight / height);
                height = maxHeight;
            }

            // 创建Canvas元素(隐藏,仅用于绘图)
            const canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            const ctx = canvas.getContext('2d');

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

            // 将Canvas导出为Blob对象(压缩核心)
            canvas.toBlob((blob) => {
                resolve(blob); // 返回压缩后的Blob
            }, file.type, quality);
        };
    });
}

// 显示压缩图、信息及下载按钮
function showCompress(blob) {
    const compressImg = document.getElementById('compressImg');
    const compressInfo = document.getElementById('compressInfo');
    const downloadBtn = document.getElementById('download');

    // 生成压缩图临时URL
    const compressUrl = URL.createObjectURL(blob);
    compressImg.src = compressUrl;

    // 显示压缩图大小及压缩比例
    const compressSize = (blob.size / 1024 / 1024).toFixed(2);
    const originSize = parseFloat(document.getElementById('originInfo').textContent.match(/\d+\.\d+/)[0]);
    const compressRatio = ((1 - compressSize / originSize) * 100).toFixed(0);
    compressInfo.textContent = `大小:${compressSize}MB | 压缩比例:${compressRatio}%`;

    // 显示下载按钮,并绑定下载事件
    downloadBtn.style.display = 'inline-block';
    downloadBtn.onclick = () => {
        const a = document.createElement('a');
        a.href = compressUrl;
        a.download = `压缩图_${new Date().getTime()}.${blob.type.split('/')[1]}`; // 自定义下载文件名
        a.click();
        // 释放临时URL,避免内存泄漏
        URL.revokeObjectURL(compressUrl);
    };
}
</script>
</body>
</html>

四、核心参数自定义(按需调整)

压缩效果可以通过调整核心参数来控制,适配不同场景,参数说明如下(都在compressImage函数中):

  • maxWidth / maxHeight:图片最大宽高,默认1600px。如果是头像上传,可设为800px;如果是文章配图,可设为1200px,按需调整。
  • quality:压缩质量,取值0~1,默认0.7。0表示最模糊、体积最小,1表示无压缩、体积最大;建议取值0.6~0.8,兼顾清晰度和体积。
  • accept="image/*":限制仅选择图片文件,若需限制特定格式(如仅jpg/png),可改为accept="image/jpeg,image/png"。

五、常见问题与解决方案(避坑必备)

实际使用中可能会遇到一些小问题,这里整理了最常见的4种情况及解决方案,新手必看:

问题1:手机竖拍图片,压缩后横显(方向旋转)

原因:手机竖拍图片会携带Orientation(方向)信息,Canvas绘图时不会自动校正。

解决方案:引入exif-js库读取图片方向,绘制Canvas时进行旋转校正,补充代码如下:

// 1. 引入exif-js(在head中添加)
<script src="https://cdn.jsdelivr.net/npm/exif-js@2.3.0/exif.min.js"></script>

// 2. 修改compressImage函数,添加方向校正
function compressImage(file, { maxWidth = 1600, maxHeight = 1600, quality = 0.7 } = {}) {
    return new Promise((resolve) => {
        const img = new Image();
        img.src = URL.createObjectURL(file);
        img.onload = () => {
            let width = img.width;
            let height = img.height;

            // 等比缩放(不变)
            if (width > maxWidth) {
                height = height * (maxWidth / width);
                width = maxWidth;
            }
            if (height > maxHeight) {
                width = width * (maxHeight / height);
                height = maxHeight;
            }

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            // 新增:读取图片方向,进行旋转校正
            EXIF.getData(file, function() {
                const orientation = EXIF.getTag(this, 'Orientation');
                // 根据方向旋转Canvas
                if (orientation === 6) { // 顺时针旋转90度
                    canvas.width = height;
                    canvas.height = width;
                    ctx.rotate(Math.PI / 2);
                    ctx.drawImage(img, 0, -height, width, height);
                } else if (orientation === 8) { // 逆时针旋转90度
                    canvas.width = height;
                    canvas.height = width;
                    ctx.rotate(-Math.PI / 2);
                    ctx.drawImage(img, -width, 0, width, height);
                } else if (orientation === 3) { // 旋转180度
                    ctx.rotate(Math.PI);
                    ctx.drawImage(img, -width, -height, width, height);
                } else {
                    canvas.width = width;
                    canvas.height = height;
                    ctx.drawImage(img, 0, 0, width, height);
                }

                // 导出Blob(不变)
                canvas.toBlob((blob) => {
                    resolve(blob);
                }, file.type, quality);
            });
        };
    });
}

问题2:透明PNG图片压缩后,背景变黑

原因:Canvas默认背景是透明的,但导出为JPG格式时,透明部分会被填充为黑色。

解决方案:两种方式任选其一:① 导出格式改为image/png(保留透明);② 绘制Canvas前,填充白色背景。

// 方案2:填充白色背景(在drawImage前添加)
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);

问题3:压缩后图片体积仍偏大

解决方案:① 降低quality参数(如改为0.6);② 缩小maxWidth/maxHeight(如改为1200px);③ 两种方式结合,效果更明显。

问题4:需要批量压缩多张图片

解决方案:修改input标签添加multiple属性,允许选择多张图片,然后遍历files数组,循环调用压缩函数:

<!-- 允许选择多张图片 -->
<input type="file" id="file" accept="image/*" multiple>

<!-- 批量处理代码 -->
document.getElementById('file').addEventListener('change', async e => {
    const files = Array.from(e.target.files);
    if (files.length === 0) return;
    files.forEach(async file => {
        const compressBlob = await compressImage(file);
        // 这里可添加批量预览、批量下载逻辑
    });
});

六、压缩后上传到服务器示例

如果需要将压缩后的图片上传到服务器,只需将Blob对象通过FormData提交即可,补充代码如下(适配所有后端接口):

// 压缩后上传(在showCompress函数中添加)
async function uploadCompress(blob) {
    const formData = new FormData();
    // 第一个参数是后端接口接收的字段名,第二个是Blob对象,第三个是文件名
    formData.append('image', blob, `compress_${new Date().getTime()}.${blob.type.split('/')[1]}`);

    try {
        const response = await fetch('/api/upload', { // 替换为你的后端接口地址
            method: 'POST',
            body: formData,
            // 无需设置Content-Type,浏览器会自动设置为multipart/form-data
        });
        const res = await response.json();
        if (res.code === 200) {
            alert('上传成功!');
            console.log('上传后的图片地址:', res.data.url);
        } else {
            alert('上传失败:' + res.message);
        }
    } catch (err) {
        alert('上传异常,请重试!');
        console.error(err);
    }
}

// 调用上传函数(可绑定到按钮点击事件)
downloadBtn.onclick = () => {
    // 下载逻辑(不变)
    const a = document.createElement('a');
    a.href = compressUrl;
    a.download = `压缩图_${new Date().getTime()}.${blob.type.split('/')[1]}`;
    a.click();
    URL.revokeObjectURL(compressUrl);

    // 新增:上传到服务器
    uploadCompress(blob);
};

七、适用场景汇总

这套代码通用性极强,适合绝大多数前端图片处理场景,尤其是:

  • 个人博客、自媒体平台的图片上传(优化加载速度);
  • 头像、身份证、营业执照等表单附件上传;
  • H5、小程序的图片优化(减少包体积,提升加载速度);
  • 本地图片预处理工具(无需上传,直接压缩下载)。

八、总结

前端图片压缩是前端开发者必备的实用技能,核心是利用Canvas的绘图能力,结合File API、Blob API实现本地压缩,无需后端介入,成本低、效果好。

本文的代码可直接复制使用,也可根据自身需求调整参数、扩展功能(如批量压缩、方向校正、上传服务器),不管是新手还是有经验的开发者,都能快速应用到项目中。

如果觉得有用,欢迎收藏、转发,也可以在评论区留言交流你的使用心得~


白云博客网

白云博客网

哈哈哈哈哈