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

web前端批量下载图片(另存为)最佳实践

前言:

在Web前端开发过程中,我们有时会遇到需要实现图片批量下载的场景。本文将结合具体实践案例,系统地介绍如何高效地实现批量下载图片功能,并探讨如何触发系统“另存为”对话框,提升用户体验。作为开发者,我希望借此分享一些切实可行的技术思路与最佳实践,为正在解决类似问题的你提供有价值的参考与启发。

技术栈:

vue3

第三方包jszip

第三方包file-saver(可选)

思路:

我们在获取所有图片的实际地址后,调用 JSZip 的 generateAsync 方法生成压缩包的二进制数据,使用 FileSaver.saveAs 方法将生成的压缩包保存到本地。

安装第三方库:

npm i jszip file-saver -S

完整源码(一):

使用file-saver库,进行zip包的下载

<template><div><el-button type="primary" @click="handleBatchDownload(imageList)">批量下载</el-button><div><divv-for="(item, index) in imageList":key="index"style="display: inline-block; margin: 10px"><img:src="item"alt=""style="width: 100px; height: 100px; object-fit: cover; display: block"/><div style="text-align: center; margin-top: 5px">图片{{ index + 1 }}</div></div></div></div>
</template><script setup>
import JSZip from "jszip";
import FileSaver from "file-saver";const imageList = ["https://static.wxb.com.cn/frontEnd/images/common/dog-white.jpg","https://static.wxb.com.cn/frontEnd/images/common/cat-blue.png","https://static.wxb.com.cn/frontEnd/images/common/panda.png",
];const getFile = (url) => {return new Promise((resolve) => {const xhr = new XMLHttpRequest();// 避免 200 from disk cacheurl = url + `?r=${Math.random()}`;xhr.open("GET", url, true);xhr.responseType = "blob";xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.response);} else {resolve(); // 避免图片下载失败,导致批量导出失败}};xhr.send();});
};// 批量下载
const handleBatchDownload = async (selectImgList, zipName = "批量下载文件") => {const data = selectImgList;const zip = new JSZip();const cache = {};const promises = [];await data.forEach(item => {const promise = getFile(item).then((fileData) => {// 下载文件const arrName = item.split("/");let fileName = arrName[arrName.length - 1]; // 获取文件名// 转码文件名, 上传的文件用decodeURIComponent转汉字const endIndex = fileName.lastIndexOf(".");const format = fileName.slice(endIndex);const startName = fileName.slice(0, endIndex);const newFileName = decodeURIComponent(startName) + format;zip.file(newFileName, fileData, {binary: true,}); // 逐个添加文件cache[newFileName] = fileData;});promises.push(promise);});Promise.all(promises).then(() => {zip.generateAsync({type: "blob",}).then(async (content) => {// 生成二进制流;利用file-saver保存文件FileSaver.saveAs(content, `${zipName}.zip`);});});
};
</script>

完整源码(二):

不安装file-saver库,创建a标签元素,进行zip包的下载

<template><div><el-button type="primary" @click="handleBatchDownload(imageList)">批量下载</el-button><div><divv-for="(item, index) in imageList":key="index"style="display: inline-block; margin: 10px"><img:src="item"alt=""style="width: 100px; height: 100px; object-fit: cover; display: block"/><div style="text-align: center; margin-top: 5px">图片{{ index + 1 }}</div></div></div></div>
</template><script setup>
import JSZip from "jszip";const imageList = ["https://static.wxb.com.cn/frontEnd/images/common/dog-white.jpg","https://static.wxb.com.cn/frontEnd/images/common/cat-blue.png","https://static.wxb.com.cn/frontEnd/images/common/panda.png",
];const getFile = (url) => {return new Promise((resolve) => {const xhr = new XMLHttpRequest();// 避免 200 from disk cacheurl = url + `?r=${Math.random()}`;xhr.open("GET", url, true);xhr.responseType = "blob";xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.response);} else {resolve(); // 避免图片下载失败,导致批量导出失败}};xhr.send();});
};// 批量下载
const handleBatchDownload = async (selectImgList, zipName = "批量下载文件") => {const data = selectImgList;const zip = new JSZip();const cache = {};const promises = [];await data.forEach(item => {const promise = getFile(item).then((fileData) => {// 下载文件const arrName = item.split("/");let fileName = arrName[arrName.length - 1]; // 获取文件名// 转码文件名, 上传的文件用decodeURIComponent转汉字const endIndex = fileName.lastIndexOf(".");const format = fileName.slice(endIndex);const startName = fileName.slice(0, endIndex);const newFileName = decodeURIComponent(startName) + format;zip.file(newFileName, fileData, {binary: true,}); // 逐个添加文件cache[newFileName] = fileData;});promises.push(promise);});Promise.all(promises).then(() => {zip.generateAsync({type: "blob",}).then(async (content) => {// 创建a标签进行zip包的下载const url = URL.createObjectURL(content);const a = document.createElement('a');a.href = url;a.download = `${zipName}.zip`;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);});});
};
</script>

实际效果:

页面展示:

下载效果:

调起资源管理器另存为:

在这里,笔者不建议通过代码强行调用浏览器 API 来直接触发“资源管理器另存为”行为。主要原因有两点:其一,这类操作通常涉及修改浏览器的默认下载设置,存在安全风险,现代浏览器出于安全考虑也普遍限制此类行为;其二,不同浏览器的 API 实现差异较大,缺乏统一标准,兼容性难以保障。

实际上,浏览器的下载行为(包括是否弹出“另存为”对话框)应由用户自行通过浏览器设置来控制,这是更安全、更符合用户预期的做法。作为前端开发者,我们的职责是与产品团队充分沟通,明确需求边界,并在必要时提供清晰的使用指引,帮助用户了解如何在主流浏览器中配置相关设置,从而获得最佳体验。

以google浏览器为例:

那么,到这里,关于web前端批量下载图片的开发实践就演示完成了,希望能帮你解决正在面临的开发难题~

拓展:

为什么web前端会从最初不起眼的切图仔到至今起到举足轻重的作用?

Web前端领域在过去十多年里发生的翻天覆地的变化。前端开发者从“切图仔”到如今“举足轻重”的地位,是技术演进、用户需求、商业模式和开发理念共同作用的结果。

我们可以从以下几个关键维度来理解这个演变过程:

1. 核心驱动力:Web应用的复杂化 (从“网站”到“Web应用”)

  • 早期(“切图仔”时代): 早期的互联网主要是“内容门户”和“信息展示”。网页的核心是文档(Document)。前端工程师(那时甚至没有这个正式职称)的工作就是使用Photoshop切片,然后用HTML和CSS将设计图精确地还原成静态网页,再加上一些简单的jQuery动画效果。业务逻辑几乎全部由后端(如Java、PHP)处理,前端只是一个被动的“视图层”。因此,重要性不高,技术含量也被认为较低。

  • 现在(“举足轻重”时代): Web已经从一个简单的文档平台演变成一个成熟的应用程序平台。我们现在用Web做的很多事情,在过去都需要安装本地软件(Desktop Application),例如:

    • 复杂应用: Gmail、Office 365、Figma、Notion等在线办公套件。

    • 社交媒体: Facebook、Twitter、Instagram等单页应用(SPA)。

    • 流媒体服务: Netflix、YouTube。

    • Web游戏: 甚至出现了使用WebGL和WebAssembly开发的3A级游戏体验。
      这些Web应用(Web Applications) 拥有复杂的交互、状态管理和实时通信需求,其复杂度丝毫不亚于传统的桌面或移动应用。前端由此变成了一个应用层(Application Layer),其重要性自然急剧上升。

2. 技术基石:强大的现代前端技术栈

“切图仔”的工具是Dreamweaver和jQuery,而现代前端工程师的武器库已经发生了革命性的升级:

  • 框架和库的兴起: React, Vue, Angular 等框架的出现是前端发展的分水岭。它们引入了组件化、数据驱动视图、虚拟DOM等概念,使得开发大型、复杂、可维护的Web应用成为可能。前端从此有了自己的工程体系和架构思想。

  • 语言的增强: ECMAScript 6+ (ES6+) 为JavaScript带来了模块化、类、Promise、异步函数等现代语言特性,使其能力大大增强,能够支撑大型应用的开发。

  • 工程化与工具链: Webpack, Vite, Babel, npm/yarn 等构建工具和包管理器的发展,让前端开发进入了工程化时代。代码可以模块化、压缩、打包、做语法转换、进行版本管理,开发流程变得规范和高效。

  • 能力扩展: 浏览器提供了越来越多强大的API,如:

    • WebGL: 高性能2D/3D图形渲染。

    • WebSocket: 全双工实时通信。

    • WebRTC: 实时音视频通信。

    • WebAssembly: 允许在浏览器中运行接近原生性能的代码(如C++、Rust编译而来)。

    • Service Workers & PWA: 实现离线缓存、消息推送,让Web应用拥有媲美原生App的体验。
      这些技术让前端能做的事情远远超出了“展示页面”的范畴。

3. 用户体验(UX)成为核心竞争力

在互联网产品同质化严重的今天,用户体验往往是决定产品成败的关键。而前端是用户体验最直接的塑造者。

  • 性能: 页面加载速度、交互流畅度、首屏时间等直接影响用户留存和转化率。前端性能优化是一门深奥的学问。

  • 交互与动效: 流畅、自然、令人愉悦的交互动画可以极大地提升用户满意度和产品质感。

  • 可访问性(A11Y): 确保残障人士也能正常使用网站,这不仅是道德和法律要求,也体现了产品的专业性。

  • 跨端兼容: 需要适配从手机、平板到电脑等各种尺寸的屏幕和设备,提供一致且良好的体验。

所有这些关乎用户体验的细节,最终都落在前端工程师的肩上。公司意识到,一个优秀的前端工程师能直接为业务价值赋能。

4. 前后端分离与全栈开发

  • 前后端分离: 现代开发模式普遍采用前后端分离架构。后端专注于API、业务逻辑和数据存储,提供稳定的接口。前端则负责消费这些接口,完成所有渲染和交互逻辑。这种分工使得前端团队需要独立负责整个“客户端”,自主权和技术责任都变得更大了。

  • Node.js的出现: Node.js让JavaScript突破了浏览器的界限,可以运行在服务器端。这催生了“大前端”和“全栈开发”的概念。前端工程师现在可以利用熟悉的JavaScript语言向后端领域延伸,负责服务端渲染(SSR)、BFF(Backend For Frontend)层、中间件等,角色更加重要。

5. 商业模式的转变

互联网经济的核心是流量用户。所有的商业模式(广告、电商、增值服务等)都建立在良好的用户体验之上,以吸引和留住用户。而前端是打造这一体验的直接执行者。一个按钮的位置、一个流程的顺畅度,都可能直接影响转化率和收入。因此,企业愿意投入更多资源打造强大的前端团队。

总结

维度“切图仔”时代“举足轻重”时代
产品形态内容网站(Websites)Web应用(Web Applications)
技术栈HTML, CSS, jQueryReact/Vue/Angular, ES6+, Webpack, Node.js
核心职责还原视觉稿,简单交互复杂应用开发、状态管理、性能优化、用户体验
角色定位被动的视图实现者主动的应用开发者、用户体验Owner
价值体现支撑性、成本部门直接影响业务指标(转化率、留存率)

所以,前端地位的提升根本上是因为Web平台本身变得无比强大和重要。作为与这个平台交互的直接创造者,前端工程师的角色也从简单的“页面工匠”进化为了复杂的“应用工程师”,其重要性自然不可同日而语。


文章转载自:

http://OXrBMY1F.xnjgt.cn
http://zXllHqwW.xnjgt.cn
http://WkXLkVb7.xnjgt.cn
http://tZyVZtoh.xnjgt.cn
http://HfrqK08d.xnjgt.cn
http://UnLeVf4n.xnjgt.cn
http://eJgWNPz3.xnjgt.cn
http://0YkHuqem.xnjgt.cn
http://cp6hlIIF.xnjgt.cn
http://AIZL5EPx.xnjgt.cn
http://bBTYRR0r.xnjgt.cn
http://fCTGTrdX.xnjgt.cn
http://oViE0Kas.xnjgt.cn
http://YSmcKhYu.xnjgt.cn
http://49xR5cmV.xnjgt.cn
http://7rTEYIIG.xnjgt.cn
http://Xpfw93P5.xnjgt.cn
http://UXLrzP1w.xnjgt.cn
http://SnNGiylD.xnjgt.cn
http://0BjxFmUI.xnjgt.cn
http://GgRHj16f.xnjgt.cn
http://VKQ5IWPp.xnjgt.cn
http://YaAvROk8.xnjgt.cn
http://Np7Y2D0t.xnjgt.cn
http://n5Dbut8G.xnjgt.cn
http://yOmMWGaV.xnjgt.cn
http://akTbzzTe.xnjgt.cn
http://TXpVeaFh.xnjgt.cn
http://32nu71k8.xnjgt.cn
http://1fWCaCd9.xnjgt.cn
http://www.dtcms.com/a/387665.html

相关文章:

  • 水题记录2
  • 苏州金龙闪耀比利时世界客车展:纯电新V系“绿”动未来
  • 漫谈网页自动化与数据采集的发展历程
  • Python 中的封装
  • 实测AI Ping,一个大模型服务选型的实用工具
  • ngrok 深度解析:内网穿透的高效解决方案
  • 总共分为几种IP
  • A股大盘数据-20250917分析
  • PyQt5中QLineEdit控件数值显示与小数位数控制
  • DeepSeek V3 深度解析:MoE、MLA 与 GRPO 的架构革新
  • 金蝶云星空插件开发记录(二)
  • Linux服务器中CPU100%如何排查
  • 从源代码开始构建、部署和管理应用程序
  • Java虚拟线程原理与性能优化实践指南
  • Java注解+com.fasterxml.jackson信息脱敏
  • Docker 镜像瘦身实战:从 1.2GB 压缩到 200MB 的优化过程——Node.js 前端 SSR 场景的“node_modules 大屠杀”
  • 外网穿透到内网---访问公网IP映射到内网IP---frp使用
  • Google Veo 3 实战指南:三步告别AI视频“PPT感”
  • NVR接入录像回放平台EasyCVR视频融合平台语音对讲配置指南
  • 【Android】进程间如何通信
  • 从代码源码角度 解读 open-vla 算法架构
  • javaweb Tomcat及运行/HTTP
  • 深入解析 HTTP 状态码
  • PHP 常用函数及用法
  • WordPress 网站邮件通知功能实现指南:以 WP Mail SMTP 插件与 QQ 邮箱为例
  • 【CF】Day144——杂题 (交互 + 思维 | 整除分块)
  • Unity 实验功能实现:天敌捕食猎物(含对象池 + 点击交互)
  • 【docker】——docker国内可用的源
  • React Zustand存储token报错解决方案
  • I/O 多路复用器(select、poll、epoll)与 Reactor 模式详解