← 文章 / 编程开发
freeCodeCamp 5小时前 · 2026-09-19 02:21:54 · 2 阅读

使用 HTML、CSS 和 JavaScript 构建批量图像压缩工具

高分辨率图片虽然好看,但会明显拖慢页面加载速度,还会占用大量存储空间。

后端压缩工具很常见,但做一个纯客户端的图片压缩器有一个巨大优势:隐私。图片直接在浏览器里处理,用户数据完全不会经过服务器。

本教程将带你构建一个功能完整的浏览器端批量图片压缩工具。你将使用 HTML5 Canvas API 压缩图片体积,并结合 JSZip 库把多个压缩后的文件打包成一个 ZIP 方便下载。

为了让这个项目更实用,代码会被组织成一个可嵌入的组件。省略标准 HTML 骨架标签后,你可以直接把这段代码丢进 WordPress 的 Custom HTML 区块或任何其他 CMS 中,不会引起布局冲突。

前置知识

学习本教程前,你需要对以下内容有基本了解:

  • HTML & CSS:搭建 UI 结构,实现悬停和拖拽等交互状态。

  • JavaScript Promises:处理文件读取、ZIP 生成等异步操作。

  • Canvas API:了解浏览器如何原生绘制和操作图片数据。

目录

第一步:搭建 HTML 结构

首先创建可视化界面。你需要构建一个拖拽上传区、一个隐藏的文件输入框,以及一个面板——在用户选好图片后才会显示出来。

把下面的代码添加到你的文件中。注意开头就引入了 JSZip 的 CDN 链接,这样后面的脚本才能调用它。

<!-- JSZip 库,用于批量下载 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

<div class="ic-main-wrapper">
    <div class="ic-title">图片压缩工具</div>
    
    <!-- 拖放区域 -->
    <div class="ic-dropzone-area" id="icDropzone">
        <input type="file" id="icFileInput" multiple accept="image/jpeg, image/png, image/webp, image/gif, image/bmp, image/tiff, image/avif" style="display: none;">
        
        <div class="ic-icon-box">
            <svg viewBox="0 0 24 24">
                <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5-7l-3 3.72L9 13l-3 4h14l-4-5z"/>
            </svg>
        </div>
        
        <div class="ic-primary-text">
            将图片拖至此处,或<span class="ic-browse-link" id="icBrowseBtn">点击浏览</span>
        </div>
        <div class="ic-secondary-text">
            支持多图——批量压缩并打包为 ZIP 下载
        </div>
        
        <div class="ic-format-tags">
            <span>JPEG</span><span>PNG</span><span>WebP</span><span>GIF</span><span>BMP</span><span>TIFF</span><span>AVIF</span>
        </div>
    </div>
    
    <!-- 操作面板(默认隐藏) -->
    <div class="ic-action-panel" id="icActionPanel">
        <div id="icStatusText" class="ic-status-msg">已选择 0 张图片</div>
        <button class="ic-btn-primary" id="icCompressBtn">压缩并下载 ZIP</button>
    </div>
</div>

理解 HTML 结构

  • accept="..." 属性:限制隐藏的 <input type="file"> 只接受图片格式,防止用户上传 PDF 或文本文档。

  • 可嵌入的结构:该标记依赖单一的 .ic-main-wrapper 容器,而非完整的 <html><body> 标签,因此可以安全地嵌入现有网页,不会破坏父级主题。

第二步:使用 CSS 设计界面

接下来,应用样式让工具显得专业且支持响应式布局。采用简洁现代的设计风格,并以特定品牌强调色(#1A73E8)来突出可交互元素。

将下方 <style> 代码块放在 HTML 上方:

<style>
.ic-main-wrapper {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    max-width: 850px;
    margin: 20px auto;
    background: #ffffff;
    border-radius: 12px;
    padding: 30px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.04);
}

.ic-title {
    font-size: 2.2rem;
    font-weight: 700;
    color: #0f172a;
    text-align: center;
    margin-bottom: 30px;
}

.ic-dropzone-area {
    border: 1.5px dashed #cbd5e1;
    border-radius: 12px;
    padding: 60px 20px;
    text-align: center;
    background-color: #f8fafc;
    transition: all 0.3s ease;
}

/* 拖拽激活状态 */
.ic-dropzone-area.dragover {
    border-color: #1A73E8;
    background-color: #f1f5f9;
}

.ic-icon-box {
    width: 64px;
    height: 64px;
    background-color: #e8f0fe;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 24px;
    color: #1A73E8;
}

.ic-icon-box svg {
    width: 28px;
    height: 28px;
    fill: currentColor;
}

.ic-primary-text {
    font-size: 1.15rem;
    color: #0f172a;
    font-weight: 500;
    margin-bottom: 12px;
}

.ic-browse-link {
    color: #1A73E8;
    cursor: pointer;
    text-decoration: none;
    font-weight: 500;
}

.ic-browse-link:hover {
    text-decoration: underline;
}

.ic-secondary-text {
    font-size: 0.95rem;
    color: #64748b;
    margin-bottom: 24px;
}

.ic-format-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    justify-content: center;
}

.ic-format-tags span {
    border: 1px solid #e2e8f0;
    background: #ffffff;
    color: #64748b;
    font-size: 0.8rem;
    padding: 6px 18px;
    border-radius: 20px;
}

.ic-action-panel {
    margin-top: 25px;
    display: none;
    text-align: center;
    background: #f8fafc;
    padding: 20px;
    border-radius: 8px;
    border: 1px solid #e2e8f0;
}

.ic-btn-primary {
    background: #1A73E8;
    color: #ffffff;
    border: none;
    padding: 12px 28px;
    border-radius: 6px;
    font-size: 1rem;
    cursor: pointer;
    font-weight: 500;
    transition: background 0.3s;
}

.ic-btn-primary:hover {
    background: #1557b0;
}

.ic-btn-primary:disabled {
    background: #94a3b8;
    cursor: not-allowed;
}

.ic-status-msg {
    margin-bottom: 15px;
    font-size: 0.95rem;
    color: #334155;
    font-weight: 500;
}
</style>

CSS 代码解析:

  • .dragover 类:该类用于改变拖放区域的边框和背景颜色。你将通过 JavaScript 动态应用此类,当用户将文件悬停在区域上方时提供关键的视觉反馈。

  • 唯一前缀:注意每个类都以 ic-(Image Compressor)开头。这充当了 CSS 命名空间,确保你的工具样式不会意外覆盖或被网站上的其他样式覆盖。

至此,用户界面已完成结构和样式搭建。效果如下所示:

2e75b26b-e1ae-443b-a78f-6f9833a4774a

第 3 步:添加 JavaScript 逻辑

接下来是工具的核心引擎。JavaScript 将处理文件选择,使用 Canvas API 压缩图片,并将其打包为 ZIP 文件。

在 HTML 下方添加此 <script> 代码块:

<script>
document.addEventListener('DOMContentLoaded', () => {
    const dropzone = document.getElementById('icDropzone');
    const fileInput = document.getElementById('icFileInput');
    const browseBtn = document.getElementById('icBrowseBtn');
    const actionPanel = document.getElementById('icActionPanel');
    const statusText = document.getElementById('icStatusText');
    const compressBtn = document.getElementById('icCompressBtn');
    
    let filesArray = [];

    // 1. 处理文件输入和拖放
    browseBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        fileInput.click();
    });
    
    dropzone.addEventListener('dragover', (e) => {
        e.preventDefault();
        dropzone.classList.add('dragover');
    });
    
    dropzone.addEventListener('dragleave', () => {
        dropzone.classList.remove('dragover');
    });
    
    dropzone.addEventListener('drop', (e) => {
        e.preventDefault();
        dropzone.classList.remove('dragover');
        if (e.dataTransfer.files.length) {
            processSelectedFiles(e.dataTransfer.files);
        }
    });

    fileInput.addEventListener('change', () => {
        if (fileInput.files.length) {
            processSelectedFiles(fileInput.files);
        }
    });

    // 2. 验证并显示操作面板
    function processSelectedFiles(files) {
        filesArray = Array.from(files).filter(file => file.type.startsWith('image/'));
        if (filesArray.length > 0) {
            actionPanel.style.display = 'block';
            statusText.innerText = `已选择 ${filesArray.length} 张图片。准备压缩。`;
        }
    }

    // 3. 核心压缩引擎
    function compressImage(file) {
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.onload = (event) => {
                const img = new Image();
                img.onload = () => {
                    const canvas = document.createElement('canvas');
                    canvas.width = img.width;
                    canvas.height = img.height;
                    const ctx = canvas.getContext('2d');
                    
                    // 将图像绘制到画布上
                    ctx.drawImage(img, 0, 0);
                    
                    // 强制兼容性:将非常规格式转换为 JPEG 或 WebP
                    let mimeType = file.type;
                    if (mimeType !== 'image/jpeg' && mimeType !== 'image/webp') {
                        mimeType = 'image/jpeg';
                    }
                    
                    // 使用 canvas.toBlob 方法以 70% 质量(0.7)进行压缩
                    canvas.toBlob((blob) => {
                        let finalName = file.name;
                        
                        // 确保文件扩展名与新的 mimeType 匹配
                        if (mimeType === 'image/jpeg' && !finalName.match(/\.(jpg|jpeg)$/i)) {
                            finalName = finalName.substring(0, finalName.lastIndexOf('.')) + '.jpg';
                        }
                        
                        resolve({ name: finalName, blob: blob });
                    }, mimeType, 0.7); 
                };
                img.src = event.target.result;
            };
            reader.readAsDataURL(file);
        });
    }

    // 4. 处理批量 ZIP 打包
    compressBtn.addEventListener('click', async () => {
        if (filesArray.length === 0) return;
        
        compressBtn.disabled = true;
        
        try {
            const zip = new JSZip();
            const folder = zip.folder("compressed_images");
            
            // 遍历每个文件并等待其压缩完成
            for (let i = 0; i < filesArray.length; i++) {
                statusText.innerText = `正在压缩 ${i + 1} / ${filesArray.length}...`;
                const compressedData = await compressImage(filesArray[i]);
                folder.file(compressedData.name, compressedData.blob);
            }
            
            statusText.innerText = '正在创建 ZIP 文件...';
            
            // 生成并触发 ZIP 下载
            const zipContent = await zip.generateAsync({ type: "blob" });
            const downloadLink = document.createElement('a');
            downloadLink.href = URL.createObjectURL(zipContent);
            downloadLink.download = "compressed_images.zip";
            downloadLink.click();
            
            statusText.innerText = '下载成功!';
        } catch (error) {
            statusText.innerText = '压缩过程中发生错误。';
            console.error(error);
        } finally {
            compressBtn.disabled = false;
        }
    });
});
</script>

JavaScript 逻辑解析:

  • 拖拽事件:dragoverdragleavedrop 三个事件监听器配合使用,把用户从桌面拖进浏览器窗口的文件捕获进来。

  • Canvas 技巧:浏览器本身并不能直接压缩文件,所以 compressImage 函数会先读取图片,把它绘制到一个不可见的 HTML5 <canvas> 上,再通过 canvas.toBlob() 方法导出一份优化后的新图片。参数 0.7 表示压缩质量为 70%。

  • 扩展名处理:由于 canvas 只能导出 JPEG 或 WebP 格式的图片,脚本会主动检查并重命名文件扩展名(比如把 .png 改成 .jpg),然后再打包。

  • 异步打包:for 循环中使用 await 逐张压缩图片。全部完成后,JSZip 把这些 blob 打包成一个文件夹,并在临时的 <a> 标签上程序化触发点击事件,从而下载最终的 .zip 文件。

结语

至此,你已经成功构建了一个快速的纯前端批量图片压缩工具。

借助 Canvas API 和 JSZip,你打造了一个非常实用的工具,既帮用户节省了带宽和存储空间,又不会泄露隐私。整个代码完全自包含、无需额外 HTML 样板,可以直接部署到几乎所有现代 Web 平台上。

如果想看这套代码在实际生产环境中的运行效果,可以试试这个在线 Image Compressor Tool。祝编程愉快!

原始来源: freeCodeCamp

评论 (0)