当前位置: 首页 > news >正文

在线图片压缩工具核心JS实现

这篇只讲本项目“图片压缩”工具的功能层 JavaScript 实现。整体链路是:

选择图片 -> 读取原图信息 -> 提交压缩参数 -> 获取压缩结果 -> 更新预览与统计 -> 下载

在线工具网址:https://see-tool.com/image-compressor
工具截图:

1. 前端状态模型:围绕“多图批处理”设计

工具初始化后维护一个轻量状态对象,核心是images数组和当前预览索引。

conststate={images:[],currentIndex:0,recompressTimer:null,};

每张图片在内存里都是一个独立对象,既保存原图信息,也保存压缩结果信息:

  • 原图:originalUrloriginalSizeoriginalWidthoriginalHeightoriginalType
  • 压缩图:compressedUrlcompressedSizecompressedWidthcompressedHeightcompressedMimeType
  • 控制字段:compressControllercompressVersion

这种结构的好处是,单图更新不会影响其他图片,批量场景下逻辑更稳定。

2. 文件接入:先过滤,再补齐元信息

拖拽和文件选择最终都走同一入口processFiles,先过滤非图片文件,再逐个处理。

constprocessFiles=async(files)=>{constimageFiles=files.filter((file)=>file.type&&file.type.startsWith("image/"),);if(imageFiles.length===0)return;for(constfileofimageFiles){awaitaddImageFile(file);}};

addImageFile会为文件创建对象 URL,并用Image对象读取像素尺寸,形成完整的图片对象后立即触发一次压缩。

constaddImageFile=async(file)=>{consturl=URL.createObjectURL(file);constimg=awaitloadImage(url);constimageObj={id:`${Date.now()}-${Math.random()}`,name:file.name,file,originalUrl:url,originalIsObjectUrl:true,originalSize:file.size,originalType:file.type||"image/jpeg",originalWidth:img.width,originalHeight:img.height,compressedUrl:null,compressedSize:null,compressController:null,compressVersion:0,};state.images.push(imageObj);state.currentIndex=state.images.length-1;compressImage(imageObj);};

3. 参数解析:输出格式和“直出原图”判定

压缩前先计算输出格式配置。工具支持original/jpeg/png/webp/avif,并统一映射成mimeType + extension

同时有一个关键分支:当用户选择original且质量 100,并且没有勾选移除元数据时,直接复用原图,不发请求。

constshouldUseOriginal=(imageObj)=>{constformat=elements.outputFormat.value;constquality=Number(elements.qualitySlider.value);if(format!=="original")returnfalse;if(quality<100)returnfalse;if(elements.removeMetadata&&elements.removeMetadata.checked)returnfalse;returntrue;};

这个分支能保证结果与用户设置一致:既然参数不要求二次编码,就直接返回原始文件。

4. 压缩主流程:可取消请求 + 版本号防乱序

compressImage是核心函数,负责把当前图片和参数提交到接口并回写结果。

constformData=newFormData();formData.append("image",imageObj.file,imageObj.name);formData.append("format",elements.outputFormat.value);formData.append("quality",String(Number(elements.qualitySlider.value)));formData.append("removeMetadata",String(Boolean(elements.removeMetadata?.checked)),);formData.append("progressive",String(Boolean(elements.progressive?.checked)));constcontroller=newAbortController();constcompressVersion=Number(imageObj.compressVersion||0)+1;imageObj.compressVersion=compressVersion;imageObj.compressController=controller;constresponse=awaitfetch("/api/image-compress",{method:"POST",body:formData,signal:controller.signal,});

结果返回后不会立刻写入,而是先校验两件事:

  1. 这张图还在列表里(没被删除)
  2. 当前响应版本号仍是最新

只有通过校验才更新compressedUrlcompressedSizecompressedWidthcompressedHeight

这能避免“用户连续拖动质量滑块”时旧请求覆盖新结果。

5. 重压缩触发:延迟调度统一入口

质量滑块、输出格式、元数据开关变化后,都不会直接逐张同步请求,而是进入scheduleRecompress

constscheduleRecompress=(delay=120)=>{if(state.recompressTimer){clearTimeout(state.recompressTimer);state.recompressTimer=null;}state.recompressTimer=setTimeout(()=>{state.recompressTimer=null;updateCompressedImages();},delay);};

updateCompressedImages内部遍历state.images,逐张调用compressImage,让参数变化后的行为保持一致。

6. 服务端压缩:字段校验 + Sharp 编码

接口只接收POST multipart/form-data。先做参数清洗:

  • 文件体积上限(30MB)
  • 目标格式白名单
  • 质量范围(1~100)
  • 布尔参数标准化(true/false/1/0/yes/no

随后读取输入元信息,解析目标格式,再按格式进入不同编码分支:

switch(targetFormat){case"jpeg":transformer=transformer.jpeg({quality,mozjpeg:true,progressive});break;case"png":transformer=transformer.png({quality,compressionLevel:9,progressive,});break;case"webp":transformer=transformer.webp({quality,effort:6});break;case"avif":transformer=transformer.avif({quality,effort:6});break;}

返回体是压缩后的二进制数据,同时附带输出信息头:

  • X-Output-Ext
  • X-Output-Width
  • X-Output-Height
  • X-Output-Size

前端据此展示压缩图信息,不需要再额外解析文件。

7. 结果展示与资源回收

统计区通过聚合originalSizecompressedSize计算总原始体积、总压缩体积、节省体积和压缩率。预览区按currentIndex展示当前图的原图/压缩图与像素信息。

批量下载按顺序触发,清空或删除图片时会执行两步清理:

  1. 中断进行中的压缩请求(AbortController
  2. 释放对象 URL(URL.revokeObjectURL

这样整条链路能长期稳定运行:上传、重压缩、预览切换、删除、清空都在同一套状态模型里闭环。

http://www.jsqmd.com/news/489492/

相关文章:

  • Vue三元表达式
  • 论文问卷设计“救星”:一个工具解决毕业季调研全流程难题
  • k8s证书有效期修改为10年
  • 如何让Agent智能选工具?
  • OpenClaw v2026.3.7 史诗级更新!Context Engine 上线,AI 记忆可插拔,普通用户也能玩出极客级效果
  • Nginx 安全防护与 HTTPS 部署实战全解析
  • “你还在为树形结构处理犯难?一文掌握Java组合模式的应用场景!”
  • 软件测试实验室申请CNAS/CMA资质费用预算清单
  • UL+FDA双认证:福尔蒂医用导管色母量产前7轮迭代实录
  • Linux命令行最基础操作指南(新手入门必看)
  • Python 工程化实战:从目录结构到 VSCode 完美配置
  • HTB - VariaType
  • GD60920你一定很少听说,但它可能就在你身边:智能照明应用解决方案分享(全文干货)
  • 2026跑腿创业,市面上系统那么多,为什么我独推荐诚心呈意共享骑手系统?
  • 高可用:mysql主备keepAlived+vip
  • dhcp技术
  • 字符串!!!!
  • PMP认证备考全攻略:从报名到3A通过的实战经验分享
  • **Sora仿真人剧2025推荐,解锁沉浸式互动叙事新体验*
  • 无人机飞控系统专业术语
  • 东南大学提出 AutoIAD:多 Agent 驱动的工业异常检测自动化框架
  • 大晓机器人开源Kairos 3.0-4B:具身世界模型性能全面领跑
  • mysql转postgres 字段定义备忘
  • 事件相机 + RGB:如何实现高速6D姿态跟踪?这项研究给出了答案
  • RWKV-7 G1e 系列模型开源,性能表现亮眼
  • 什么牌子的头戴式耳机性价比高?精选十大高性价比头戴式耳机推荐
  • 由二叉树的前序结果来生成二叉树
  • 吃透YOLOv8:从结构拆解到实战优化,新手也能落地工业级目标检测
  • 从后台管理到 IoT 远程控表,这个 Spring Boot 3 开源项目把能耗管理链路做完整了
  • 论文写不完?这9款智能写作AI神器,从选题到答辩全包了