HTML前端图片压缩工具,附完整可运行源码

做自媒体或者临时需要图片压缩的朋友,一定都踩过图片压缩的坑:于是我自己用HTML写了一款纯浏览器本地图片压缩工具

线上地址:https://zhiyifenxiang.com/tupianyasuo/

图片[1]-前端本地图片压缩工具|完整 HTML 源码 - 智翼分享

工具核心亮点(全部前端实现,零后端)

✅ 全程本地运算,图片永不上传服务器,隐私100%安全

✅ 永久免费无次数限制,不加水印、无广告

✅ 支持JPG/PNG/WebP三种主流图片格式

✅ 10-100自定义压缩质量,尺寸锁定不裁剪原图

✅ 一键转JPG通用格式,压缩前后实时对比预览

✅ 不用登录、不用注册,打开网页直接免费使用

所有图片处理逻辑全部在你的浏览器内存中完成,不会发起任何网络请求上传原图,隐私敏感素材(证件照、产品图、写真)放心用。

一、前端本地压缩核心原理

市面上绝大多数在线压缩工具都会把图片上传到服务端处理,而本工具依靠浏览器原生API实现纯本地处理:

完整处理流程:本地选择图片 → 浏览器读取文件压缩 → 本地预览+下载,全程无网络上传

二、完整可运行前端源码(无第三方依赖)

1. HTML代码

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>本地图片无损压缩工具 | 免费不上传服务器</title>
<link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-100-M/font-awesome/4.7.0/css/font-awesome.min.css">
<div class="wp-photo-compressor">
    <style>
        * {box-sizing: border-box;margin:0;padding:0;}
        .wp-photo-compressor {font-family: system-ui,-apple-system,sans-serif;color:#1E293B;max-width:1000px;margin:0 auto;padding:20px;}
        /* 全局禁用模块内所有图片点击,彻底杜绝放大弹窗 */
        .wp-photo-compressor img {pointer-events:none !important;cursor:default !important;}
        .wp-photo-file-input {display:none !important;opacity:0;width:0;height:0;overflow:hidden;pointer-events:none;}
        .wp-photo-header {display:flex;justify-content:space-between;align-items:center;margin-bottom:30px;flex-wrap:wrap;gap:12px;}
        .wp-photo-header-title {display:flex;align-items:center;gap:10px;}
        .wp-photo-header-icon {color:#3B82F6;font-size:24px;}
        .wp-photo-header-text {font-size:24px;font-weight:700;background:linear-gradient(90deg,#3B82F6,#10B981);-webkit-background-clip:text;color:transparent;}
        .wp-photo-header-safe-tip {font-size:14px;color:#047857;font-weight:500;margin-top:6px;line-height:1.5;}
        .wp-photo-section {background:#fff;border-radius:10px;box-shadow:0 4px 20px rgba(0,0,0,0.08);padding:20px;margin-bottom:30px;}
        .wp-photo-upload-container {border:2px dashed #E2E8F0;border-radius:10px;padding:40px 20px;text-align:center;transition:border-color .3s ease;}
        .wp-photo-upload-container:hover {border-color:#3B82F6;}
        .wp-photo-upload-icon {font-size:60px;color:#3B82F6;margin-bottom:15px;}
        .wp-photo-upload-title {font-size:20px;font-weight:600;margin-bottom:10px;}
        .wp-photo-upload-desc {color:#64748B;margin-bottom:20px;max-width:520px;margin-inline:auto;line-height:1.6;}
        .wp-photo-btn {background:#3B82F6;color:#fff;border:none;padding:10px 20px;border-radius:6px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:8px;transition:all .3s ease;}
        .wp-photo-btn:hover {transform:scale(1.03);opacity:0.92;}
        .wp-photo-btn-secondary {background:#10B981;}
        .wp-photo-btn:disabled {opacity:0.5;cursor:not-allowed;transform:none;}
        .wp-photo-file-name {margin-top:15px;font-size:14px;color:#64748B;display:none;}
        .wp-photo-reset-btn {margin-top:10px;color:#64748B;background:transparent;border:none;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:color .3s ease;display:none;}
        .wp-photo-reset-btn:hover {color:#EF4444;}
        .wp-photo-settings-title {font-size:18px;font-weight:600;margin-bottom:20px;display:flex;align-items:center;gap:10px;}
        .wp-photo-settings-grid {display:grid;grid-template-columns:1fr;gap:20px;margin-bottom:20px;}
        @media(min-width:768px){.wp-photo-settings-grid {grid-template-columns:1fr 1fr;}}
        .wp-photo-setting-group {margin-bottom:15px;}
        .wp-photo-setting-label {display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:#475569;}
        .wp-photo-quality-slider {width:100%;height:6px;background:#E2E8F0;border-radius:3px;appearance:none;cursor:pointer;}
        .wp-photo-quality-slider::-webkit-slider-thumb {appearance:none;width:18px;height:18px;border-radius:50%;background:#3B82F6;}
        .wp-photo-quality-value {font-weight:500;}
        .wp-photo-format-select {width:100%;padding:8px 12px;border:1px solid #CBD5E1;border-radius:6px;font-size:14px;}
        .wp-photo-setting-desc {font-size:12px;color:#64748B;margin-top:5px;line-height:1.5;}
        .wp-photo-center-btn {text-align:center;}
        .wp-photo-preview-title {font-size:18px;font-weight:600;margin-bottom:20px;display:flex;align-items:center;gap:10px;}
        .wp-photo-preview-grid {display:grid;grid-template-columns:1fr;gap:30px;}
        @media(min-width:768px){.wp-photo-preview-grid {grid-template-columns:1fr 1fr;}}
        .wp-photo-preview-box {display:flex;flex-direction:column;align-items:center;}
        .wp-photo-preview-subtitle {font-size:16px;font-weight:500;margin-bottom:15px;}
        .wp-photo-image-container {width:100%;max-width:400px;height:300px;background:#F1F5F9;border-radius:8px;display:flex;align-items:center;justify-content:center;overflow:hidden;margin-bottom:15px;}
        .wp-photo-image {max-width:100%;max-height:100%;object-fit:contain;display:none;}
        .wp-photo-placeholder {text-align:center;padding:20px;}
        .wp-photo-placeholder-icon {font-size:50px;color:#CBD5E1;margin-bottom:10px;}
        .wp-photo-placeholder-text {color:#94A3B8;}
        .wp-photo-image-info {width:100%;max-width:400px;font-size:14px;color:#64748B;}
        .wp-photo-info-row {margin-bottom:5px;}
        .wp-photo-info-label {font-weight:500;}
        .wp-photo-save-rate {margin-top:8px;color:#10B981;font-weight:500;display:none;}
        .wp-photo-footer {text-align:center;font-size:14px;color:#64748B;margin-top:40px;padding-top:20px;border-top:1px solid #E2E8F0;line-height:1.6;}
        .wp-photo-loading-overlay {position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9998;display:none;}
        .wp-photo-loading-spinner {background:#fff;padding:20px 30px;border-radius:8px;display:flex;align-items:center;gap:15px;box-shadow:0 4px 20px rgba(0,0,0,0.2);}
        .wp-photo-spinner {width:24px;height:24px;border:3px solid #E2E8F0;border-top-color:#3B82F6;border-radius:50%;animation:spin 1s linear infinite;}
        .wp-photo-loading-text {font-weight:500;}
        @keyframes spin {to{transform:rotate(360deg);}}
        @media screen and (max-width:640px){
            .wp-photo-compressor {padding:12px;}
            .wp-photo-header-text {font-size:20px;}
            .wp-photo-header-icon {font-size:20px;}
            .wp-photo-section {padding:14px;margin-bottom:20px;}
            .wp-photo-upload-container {padding:28px 12px;}
            .wp-photo-upload-icon {font-size:48px;}
            .wp-photo-upload-title {font-size:18px;}
            .wp-photo-image-container {height:220px;}
            .wp-photo-btn {width:100%;justify-content:center;padding:12px;font-size:15px;}
            .wp-photo-settings-title,.wp-photo-preview-title {font-size:16px;}
            .wp-photo-preview-subtitle {font-size:15px;}
            .wp-photo-footer {font-size:12px;margin-top:24px;padding-top:14px;}
        }
    </style>
    <div class="wp-photo-content">
        <div class="wp-photo-header">
            <div class="wp-photo-header-title">
                <i class="fa fa-compress wp-photo-header-icon"></i>
                <h1 class="wp-photo-header-text">高效照片压缩工具</h1>
            </div>
            <div class="wp-photo-header-safe-tip">
                ✅ 纯本地浏览器处理 · 图片永不上传服务器<br>
                ✅ 永久免费 · 零隐私泄露风险
            </div>
        </div>
        <section class="wp-photo-section">
            <div class="wp-photo-upload-container" id="uploadBox">
                <div class="wp-photo-upload-inner">
                    <i class="fa fa-cloud-upload wp-photo-upload-icon"></i>
                    <h2 class="wp-photo-upload-title">上传图片</h2>
                    <p class="wp-photo-upload-desc">仅支持 JPG / PNG / WebP,全部图片仅在您本机浏览器运算,不会上传任何服务器,隐私安全永久免费</p>
                    <label id="fileTrigger" for="virtualFile" class="wp-photo-btn">
                        <i class="fa fa-file-image-o"></i>选择图片
                    </label>
                    <input id="virtualFile" class="wp-photo-file-input" type="file" accept="image/jpeg,image/png,image/webp">
                    <p id="fileNameTxt" class="wp-photo-file-name"></p>
                    <button id="resetBtn" class="wp-photo-reset-btn">
                        <i class="fa fa-times-circle"></i> 重新选择
                    </button>
                </div>
            </div>
        </section>
        <section id="setBox" class="wp-photo-section" style="display: none;">
            <h2 class="wp-photo-settings-title"><i class="fa fa-sliders"></i>压缩设置</h2>
            <div class="wp-photo-settings-grid">
                <div class="wp-photo-setting-group">
                    <label class="wp-photo-setting-label">压缩质量: <span id="qualityNum" class="wp-photo-quality-value">90</span>%</label>
                    <input type="range" id="qualitySlider" min="10" max="100" value="90" class="wp-photo-quality-slider">
                    <p class="wp-photo-setting-desc">统一锁定原图尺寸;10=极致小体积,100=无损最高画质;PNG不受质量数值影响,原生无损</p>
                </div>
                <div class="wp-photo-setting-group">
                    <label class="wp-photo-setting-label">输出格式</label>
                    <select id="formatSel" class="wp-photo-format-select">
                        <option value="jpg">JPG|通用兼容,有损高保真压缩</option>
                        <option value="png">PNG|无损透明,像素色彩完全不变</option>
                        <option value="webp">WebP|平衡画质体积,现代网页最优</option>
                    </select>
                    <p class="wp-photo-setting-desc">所有格式均不修改原图分辨率,全程本地运算不上传</p>
                </div>
            </div>
            <div class="wp-photo-center-btn">
                <button id="compressBtn" class="wp-photo-btn wp-photo-btn-secondary">
                    <i class="fa fa-cog"></i>开始压缩
                </button>
            </div>
        </section>
        <section id="previewBox" class="wp-photo-section" style="display: none;">
            <h2 class="wp-photo-preview-title"><i class="fa fa-eye"></i>压缩前后对比</h2>
            <div class="wp-photo-preview-grid">
                <div class="wp-photo-preview-box">
                    <h3 class="wp-photo-preview-subtitle">原图</h3>
                    <div class="wp-photo-image-container">
                        <img id="oriImg" alt="原图预览" class="wp-photo-image" data-no-lightbox data-lb-ignore="1">
                        <div id="oriPlaceholder" class="wp-photo-placeholder">
                            <i class="fa fa-image wp-photo-placeholder-icon"></i>
                            <p class="wp-photo-placeholder-text">暂无图片</p>
                        </div>
                    </div>
                    <div class="wp-photo-image-info">
                        <p class="wp-photo-info-row"><span class="wp-photo-info-label">尺寸:</span> <span id="oriSize">--</span></p>
                        <p class="wp-photo-info-row"><span class="wp-photo-info-label">大小:</span> <span id="oriByte">--</span></p>
                        <p class="wp-photo-info-row"><span class="wp-photo-info-label">格式:</span> <span id="oriType">--</span></p>
                    </div>
                </div>
                <div class="wp-photo-preview-box">
                    <h3 class="wp-photo-preview-subtitle">压缩后</h3>
                    <div class="wp-photo-image-container">
                        <img id="comImg" alt="压缩后预览" class="wp-photo-image" data-no-lightbox data-lb-ignore="1">
                        <div id="comPlaceholder" class="wp-photo-placeholder">
                            <i class="fa fa-image wp-photo-placeholder-icon"></i>
                            <p class="wp-photo-placeholder-text">暂无图片</p>
                        </div>
                    </div>
                    <div class="wp-photo-image-info">
                        <p class="wp-photo-info-row"><span class="wp-photo-info-label">尺寸:</span> <span id="comSize">--</span></p>
                        <p class="wp-photo-info-row"><span class="wp-photo-info-label">大小:</span> <span id="comByte">--</span></p>
                        <p class="wp-photo-info-row"><span class="wp-photo-info-label">格式:</span> <span id="comType">--</span></p>
                        <p id="saveTip" class="wp-photo-save-rate"></p>
                    </div>
                </div>
            </div>
            <div class="wp-photo-center-btn" style="margin-top: 20px;">
                <button id="downloadBtn" class="wp-photo-btn" disabled>
                    <i class="fa fa-download"></i>下载图片
                </button>
            </div>
        </section>
    </div>
    <div class="wp-photo-footer">
        <p>图片压缩工具 © <a href="https://zhiyifenxiang.com" target="_blank">智翼分享</a> 2026 | 全程本地浏览器处理,图片不会上传至任何服务器,永久免费无水印</p>
    </div>
    <div id="loadingMask" class="wp-photo-loading-overlay">
        <div class="wp-photo-loading-spinner">
            <div class="wp-photo-spinner"></div>
            <p class="wp-photo-loading-text">本地处理中,请勿关闭页面</p>
        </div>
    </div>
    <script>
        const dom = {
            virtualFile: document.getElementById('virtualFile'),
            fileTrigger: document.getElementById('fileTrigger'),
            fileNameTxt: document.getElementById('fileNameTxt'),
            resetBtn: document.getElementById('resetBtn'),
            uploadBox: document.getElementById('uploadBox'),
            setBox: document.getElementById('setBox'),
            previewBox: document.getElementById('previewBox'),
            qualitySlider: document.getElementById('qualitySlider'),
            qualityNum: document.getElementById('qualityNum'),
            formatSel: document.getElementById('formatSel'),
            compressBtn: document.getElementById('compressBtn'),
            downloadBtn: document.getElementById('downloadBtn'),
            oriImg: document.getElementById('oriImg'),
            comImg: document.getElementById('comImg'),
            oriPlaceholder: document.getElementById('oriPlaceholder'),
            comPlaceholder: document.getElementById('comPlaceholder'),
            oriSize: document.getElementById('oriSize'),
            oriByte: document.getElementById('oriByte'),
            oriType: document.getElementById('oriType'),
            comSize: document.getElementById('comSize'),
            comByte: document.getElementById('comByte'),
            comType: document.getElementById('comType'),
            saveTip: document.getElementById('saveTip'),
            loadingMask: document.getElementById('loadingMask')
        };
        const state = {sourceImg:null,compressBlob:null,baseName:"",isProcessing:false};
        dom.virtualFile.onchange = uploadHandle;
        dom.resetBtn.onclick = fullReset;
        dom.qualitySlider.oninput = ()=>dom.qualityNum.textContent = dom.qualitySlider.value;
        dom.compressBtn.onclick = compressHandle;
        dom.downloadBtn.onclick = downloadHandle;
        function formatSize(bytes){
            if(bytes<1024)return bytes+' B';
            if(bytes<1048576)return (bytes/1024).toFixed(2)+' KB';
            return (bytes/1048576).toFixed(2)+' MB';
        }
        function setLoading(show){dom.loadingMask.style.display = show?'flex':'none';}
        function fullReset(){
            dom.virtualFile.value = "";
            dom.fileNameTxt.style.display = "none";
            dom.resetBtn.style.display = "none";
            dom.uploadBox.style.borderColor = "#E2E8F0";
            dom.setBox.style.display = "none";
            dom.previewBox.style.display = "none";
            dom.saveTip.style.display = "none";
            dom.downloadBtn.disabled = true;
            state.sourceImg = null;state.compressBlob = null;state.baseName = "";state.isProcessing = false;
            dom.oriImg.src = "";dom.oriImg.style.display = "none";dom.oriPlaceholder.style.display = "block";
            [dom.oriSize,dom.oriByte,dom.oriType].forEach(el=>el.textContent="--");
            dom.comImg.src = "";dom.comImg.style.display = "none";dom.comPlaceholder.style.display = "block";
            [dom.comSize,dom.comByte,dom.comType].forEach(el=>el.textContent="--");
        }
        function uploadHandle(e){
            const file = e.target.files[0];
            if(!file)return;
            const allow = ["image/jpeg","image/png","image/webp"];
            if(!allow.includes(file.type))return alert("仅支持 JPG / PNG / WebP 图片");
            if(state.sourceImg)return alert("已上传图片,请先点击重新选择重置");
            state.baseName = file.name.replace(/\.\w+$/,"");
            const reader = new FileReader();
            reader.onerror = ()=>alert("图片读取失败,文件损坏");
            reader.onload = ev=>{
                const url = ev.target.result;
                const img = new Image();
                img.onerror = ()=>alert("图片加载异常");
                img.onload = ()=>{
                    state.sourceImg = {url,w:img.width,h:img.height,size:file.size,type:file.type.split("/")[1]};
                    dom.oriImg.src = url;dom.oriImg.style.display = "block";dom.oriPlaceholder.style.display = "none";
                    dom.oriSize.textContent = `${img.width} × ${img.height} 像素`;
                    dom.oriByte.textContent = formatSize(file.size);
                    dom.oriType.textContent = state.sourceImg.type.toUpperCase();
                    dom.fileNameTxt.textContent = file.name;dom.fileNameTxt.style.display = "block";
                    dom.resetBtn.style.display = "inline-flex";
                    dom.uploadBox.style.borderColor = "#3B82F6";
                    dom.setBox.style.display = "block";
                    dom.previewBox.style.display = "block";
                };
                img.src = url;
            };
            reader.readAsDataURL(file);
        }
        async function compressHandle(){
            if(!state.sourceImg||state.isProcessing)return;
            state.isProcessing = true;
            setLoading(true);
            dom.comImg.src = "";dom.comImg.style.display = "none";dom.comPlaceholder.style.display = "block";
            dom.saveTip.style.display = "none";
            [dom.comSize,dom.comByte,dom.comType].forEach(el=>el.textContent="--");
            try{
                const img = new Image();
                img.src = state.sourceImg.url;
                await new Promise(res=>img.onload=res);
                const w = img.width,h = img.height;
                const quality = Number(dom.qualitySlider.value)/100;
                const fmt = dom.formatSel.value;
                let mime,loss = false;
                switch(fmt){
                    case "jpg":mime="image/jpeg";break;
                    case "png":mime="image/png";loss=true;break;
                    case "webp":mime="image/webp";break;
                }
                const canvas = document.createElement("canvas");
                const ctx = canvas.getContext("2d");
                canvas.width = w;canvas.height = h;
                if(mime==="image/jpeg"){ctx.fillStyle="#fff";ctx.fillRect(0,0,w,h);}
                ctx.drawImage(img,0,0,w,h);
                const blob = await new Promise((res,rej)=>canvas.toBlob(res,mime,loss?undefined:quality));
                state.compressBlob = blob;
                const save = ((1-blob.size/state.sourceImg.size)*100).toFixed(1);
                const tempUrl = URL.createObjectURL(blob);
                dom.comImg.src = tempUrl;dom.comImg.style.display = "block";dom.comPlaceholder.style.display = "none";
                dom.comSize.textContent = `${w} × ${h} 像素`;
                dom.comByte.textContent = formatSize(blob.size);
                dom.comType.textContent = fmt.toUpperCase();
                dom.saveTip.textContent = blob.size>=state.sourceImg.size?`压缩后体积略大,节省 ${save}%(小图无损格式易出现)`:`本次压缩共节省 ${save}% 文件体积`;
                dom.saveTip.style.display = "block";
                dom.downloadBtn.disabled = false;
            }catch(err){alert("本地压缩失败:"+err.message);}
            state.isProcessing = false;
            setLoading(false);
        }
        function downloadHandle(){
            if(!state.compressBlob||!state.baseName)return;
            const fmt = dom.formatSel.value;
            const a = document.createElement("a");
            const url = URL.createObjectURL(state.compressBlob);
            a.href = url;a.download = `${state.baseName}.${fmt}`;
            document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(url);
        }
    </script>
</div>

三、线上工具使用教程

1、打开链接:https://zhiyifenxiang.com/tupianyasuo/

2、点击上传框选择本地图片;

3、滑动滑块调整压缩质量;

4、自动生成原图/压缩后对比,查看体积缩减比例;

5、点击下载,保存压缩后的图片到本地

有开发能力的朋友可以基于这份源码继续迭代

结尾

不管是自媒体博主,还是临时需要图片压缩小伙伴,这款零隐私风险的本地压缩工具都能解决日常配图压缩痛点。

源码完全开源,可直接复制部署到自己的静态站点使用,不用任何后端服务,静态页面托管即可运行。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享