一、需求说明
我现在有个需求:
1.列表中有个下载按钮,点击下载,将列表中所有的图片打成压缩包,并下载
2.效果演示点击查看效果
最终效果:
二、安装下载插件
实现此功能需要两个插件:jszip、file-saver
如名字简易理解:一个是可以打成压缩包,另一个是可以保存下载的
// 安装下载
npm install jszip
npm install file-saver --save
三、引入插件
//npm引入方式
import JSZip from 'jszip'
import { saveAs } from 'file-saver'
//cdn引入方式
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
四、代码实现
话不多说,上代码,为了方便,以下代码直接复制即可实现效果,后续自己修改也可
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>图片URL批量下载工具</title><script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);min-height: 100vh;padding: 20px;}.container {max-width: 1200px;margin: 0 auto;background: white;border-radius: 15px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);overflow: hidden;}header {background: linear-gradient(90deg, #3498db, #8e44ad);color: white;padding: 25px 40px;text-align: center;}h1 {font-size: 2.5rem;margin-bottom: 10px;letter-spacing: 1px;}.subtitle {font-size: 1.1rem;opacity: 0.9;max-width: 700px;margin: 0 auto;}.content {padding: 30px;display: grid;grid-template-columns: 1fr 350px;gap: 30px;}.image-grid {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));gap: 20px;}.image-card {background: white;border-radius: 10px;overflow: hidden;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);transition: all 0.3s ease;}.image-card:hover {transform: translateY(-5px);box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);}.image-preview {height: 180px;background-color: #f8f9fa;display: flex;align-items: center;justify-content: center;overflow: hidden;}.image-preview img {max-width: 100%;max-height: 100%;object-fit: cover;transition: transform 0.3s ease;}.image-card:hover .image-preview img {transform: scale(1.05);}.image-info {padding: 15px;}.image-info h3 {font-size: 1.1rem;margin-bottom: 8px;color: #2c3e50;}.image-url {font-size: 0.85rem;color: #7f8c8d;word-break: break-all;line-height: 1.4;}.control-panel {background: #f8f9fa;border-radius: 12px;padding: 25px;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);}.control-panel h2 {color: #2c3e50;margin-bottom: 20px;padding-bottom: 15px;border-bottom: 1px solid #e0e0e0;}.stats {background: white;border-radius: 10px;padding: 15px;margin-bottom: 25px;box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);}.stat-item {display: flex;justify-content: space-between;margin-bottom: 10px;padding-bottom: 10px;border-bottom: 1px solid #f0f0f0;}.stat-item:last-child {margin-bottom: 0;padding-bottom: 0;border: none;}.progress-container {margin: 25px 0;}.progress-label {display: flex;justify-content: space-between;margin-bottom: 8px;font-size: 0.9rem;color: #7f8c8d;}.progress-bar {height: 10px;background: #e0e0e0;border-radius: 5px;overflow: hidden;}.progress-fill {height: 100%;background: linear-gradient(90deg, #3498db, #8e44ad);border-radius: 5px;width: 0%;transition: width 0.5s ease;}.download-btn {display: block;width: 100%;padding: 15px;background: linear-gradient(90deg, #3498db, #8e44ad);color: white;border: none;border-radius: 8px;font-size: 1.1rem;font-weight: 600;cursor: pointer;transition: all 0.3s ease;box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3);}.download-btn:hover {transform: translateY(-3px);box-shadow: 0 8px 20px rgba(52, 152, 219, 0.4);}.download-btn:active {transform: translateY(0);}.download-btn:disabled {background: #bdc3c7;transform: none;box-shadow: none;cursor: not-allowed;}.status-message {margin-top: 20px;padding: 12px;border-radius: 8px;text-align: center;font-weight: 500;display: none;}.success {background: #e8f6ef;color: #27ae60;display: block;}.error {background: #fdecea;color: #e74c3c;display: block;}footer {text-align: center;padding: 20px;color: #7f8c8d;font-size: 0.9rem;border-top: 1px solid #eee;}@media (max-width: 900px) {.content {grid-template-columns: 1fr;}}</style>
</head>
<body><div class="container"><header><h1>图片URL批量下载工具</h1><p class="subtitle">将列表中的图片URL批量下载并打包成压缩包,支持预览和进度显示</p></header><div class="content"><div class="image-grid" id="imageGrid"><!-- 图片卡片将通过JS动态生成 --></div><div class="control-panel"><h2>下载控制面板</h2><div class="stats"><div class="stat-item"><span>图片总数:</span><span id="totalCount">0</span></div><div class="stat-item"><span>下载进度:</span><span id="progressCount">0/0</span></div><div class="stat-item"><span>压缩包大小:</span><span id="zipSize">0 MB</span></div></div><div class="progress-container"><div class="progress-label"><span>下载进度</span><span id="progressPercent">0%</span></div><div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div></div><button id="downloadBtn" class="download-btn">下载所有图片(ZIP压缩包)</button><div id="statusMessage" class="status-message"></div></div></div><footer><p>© 2023 图片批量下载工具 | 支持多格式图片下载 | 使用JSZip实现压缩功能</p></footer></div><script>// 示例图片数据const imageList = [{ id: 1, title: "山脉风景", url: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" },{ id: 2, title: "海滩日落", url: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" },{ id: 3, title: "森林小径", url: "https://images.unsplash.com/photo-1448375240586-882707db888b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" },{ id: 4, title: "城市夜景", url: "https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" }];// DOM元素const imageGrid = document.getElementById('imageGrid');const downloadBtn = document.getElementById('downloadBtn');const totalCountEl = document.getElementById('totalCount');const progressCountEl = document.getElementById('progressCount');const zipSizeEl = document.getElementById('zipSize');const progressPercentEl = document.getElementById('progressPercent');const progressFillEl = document.getElementById('progressFill');const statusMessageEl = document.getElementById('statusMessage');// 渲染图片列表function renderImageList() {imageGrid.innerHTML = '';imageList.forEach(image => {const card = document.createElement('div');card.className = 'image-card';card.innerHTML = `<div class="image-preview"><img src="${image.url}" alt="${image.title}" loading="lazy"></div><div class="image-info"><h3>${image.title}</h3><p class="image-url">${image.url}</p></div>`;imageGrid.appendChild(card);});totalCountEl.textContent = imageList.length;progressCountEl.textContent = `0/${imageList.length}`;}// 获取安全的文件名function getSafeFilename(url, id, title, blob) {try {// 尝试从URL中提取文件名const urlObj = new URL(url);const pathParts = urlObj.pathname.split('/');let filename = pathParts.pop() || `image_${id}`;// 移除查询参数部分filename = filename.split('?')[0];// 检查是否有文件扩展名const hasExtension = filename.includes('.') && filename.lastIndexOf('.') > filename.lastIndexOf('/');// 如果没有扩展名,从blob类型获取if (!hasExtension) {const extension = blob.type.split('/')[1] || 'jpg';filename += `.${extension}`;}// 清理文件名中的特殊字符filename = filename.replace(/[^a-z0-9_.-]/gi, '_');// 添加标题前缀使文件名更易读const cleanTitle = title.replace(/[^a-z0-9\s]/gi, '').replace(/\s+/g, '_').substring(0, 20);return `${cleanTitle}_${filename}`;} catch (e) {// 如果URL解析失败,使用默认文件名return `image_${id}.jpg`;}}// 下载所有图片并打包成ZIPasync function downloadImagesAsZip() {try {// 重置状态statusMessageEl.className = 'status-message';downloadBtn.disabled = true;downloadBtn.textContent = '下载中...';// 创建JSZip实例const zip = new JSZip();const folder = zip.folder("images");let downloadedCount = 0;// 更新进度function updateProgress() {downloadedCount++;const percent = Math.round((downloadedCount / imageList.length) * 100);progressCountEl.textContent = `${downloadedCount}/${imageList.length}`;progressPercentEl.textContent = `${percent}%`;progressFillEl.style.width = `${percent}%`;}// 下载所有图片for (const image of imageList) {try {// 获取图片const response = await fetch(image.url);if (!response.ok) throw new Error(`HTTP错误! 状态码: ${response.status}`);// 获取图片Blobconst blob = await response.blob();// 获取安全的文件名const filename = getSafeFilename(image.url, image.id, image.title, blob);// 将图片添加到ZIPfolder.file(filename, blob);updateProgress();} catch (error) {console.error(`下载失败: ${image.url}`, error);updateProgress();}}// 生成ZIP文件const content = await zip.generateAsync({type: "blob"}, metadata => {const sizeInMB = (metadata.currentZipSize / (1024 * 1024)).toFixed(2);zipSizeEl.textContent = `${sizeInMB} MB`;});// 下载ZIP文件saveAs(content, "downloaded_images.zip");// 更新状态statusMessageEl.textContent = '下载成功!压缩包已开始下载';statusMessageEl.className = 'status-message success';} catch (error) {console.error('打包过程中出错:', error);statusMessageEl.textContent = '下载失败:' + error.message;statusMessageEl.className = 'status-message error';} finally {downloadBtn.disabled = false;downloadBtn.textContent = '下载所有图片(ZIP压缩包)';}}// 初始化页面document.addEventListener('DOMContentLoaded', () => {renderImageList();downloadBtn.addEventListener('click', downloadImagesAsZip);});</script>
</body>
</html>