(1)从零开发 Chrome 插件:构建你的第一个插件
(3)从零开发 Chrome 插件:实现 API 登录与本地存储功能
(3)从零开发 Chrome 插件:网页图片的批量下载
(4)从零开发 Chrome 插件:Chrome插件调试全攻略
我们常常会遇到需要保存页面图片的情况。一张两张手动保存还好,但要是遇到图片数量较多的页面,那就比较痛苦。基于这样的痛点,我开发了一款 Chrome 图片下载插件,能实现网页图片的批量下载。下面就来详细分享这款插件的开发过程。
一 插件核心功能

当用户点击浏览器工具栏中的插件图标时,会弹出一个操作界面。在这个界面里,用户可以进行一系列设置,比如给要下载的图片设置文件名前缀,用正则表达式来过滤不需要的图片 URL,还能选择是否只下载当前页面中可见的图片。
完成设置后,用户点击 "开始下载图片" 按钮,插件就会启动内容脚本。内容脚本会注入到当前正在浏览的网页中,收集页面里所有 img 标签对应的图片资源。在收集过程中,它会对图片的 URL 格式进行处理,比如把相对路径转换成绝对路径,然后根据用户设置的过滤规则,筛选出符合条件的图片,再把这些图片的信息发送到后台脚本。。
二 程序流程深度解析
用户点击插件图标,弹出操作界面加载完成。用户在界面中输入文件名前缀、设置过滤规则等信息,接着点击确认下载按钮。此时,内容脚本启动,开始提取图片的 src 或者 data-src 属性值,将其中的相对路径转换为绝对路径。然后,根据用户设置的规则对图片进行筛选,筛选出符合条件的图片后,把图片列表发送到后台脚本。后台脚本接收图片列表后,调用 Chrome 下载 API,循环下载图片并保存到本地指定路径。最后,判断所有图片是否都下载完成,如果完成,就显示下载完成的通知,整个流程到此结束。
三 功能详细说明
1. 用户交互层
这一层是用户与插件进行交互的主要部分。当用户点击浏览器工具栏中的插件图标时,会触发弹出界面(由 popup.html 实现)的加载。用户在这个界面中设置下载参数,比如文件名前缀、过滤规则等,然后确认下载操作,这一系列动作都属于用户交互层的范畴。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片下载器</title>
<script src="popup.js"></script>
<style>
body {
width: 350px;
padding: 10px;
font-family: Arial, sans-serif;
margin: 0;
}
.container {
padding: 10px;
}
h3 {
margin-top: 0;
color: #333;
}
.section {
margin-bottom: 15px;
}
.section label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.section input, .section textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ddd;
border-radius: 4px;
}
.checkbox-group {
margin-bottom: 10px;
}
.checkbox-group label {
font-weight: normal;
margin-left: 5px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
.status {
margin-top: 10px;
padding: 8px;
border-radius: 4px;
display: none;
}
.status.success {
background-color: #d4edda;
color: #155724;
}
.status.error {
background-color: #f8d7da;
color: #721c24;
}
.image-count {
margin-top: 5px;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<div class="container">
<h3>图片下载器</h3>
<div class="section">
<label for="prefix">文件名前缀 (可选):</label>
<input type="text" id="prefix" placeholder="例如: mysite_">
</div>
<div class="section">
<label for="filter">图片URL过滤 (正则表达式, 可选):</label>
<input type="text" id="filter" placeholder="例如: \.(jpg|png|gif)$">
</div>
<div class="checkbox-group">
<input type="checkbox" id="onlyVisible" checked>
<label for="onlyVisible">仅下载可见图片</label>
</div>
<button id="downloadBtn">开始下载图片</button>
<div id="status" class="status"></div>
<div class="image-count">
找到 <span id="imageCount">0</span> 张图片
</div>
</div>
</body>
</html>
2. 用户交互层脚 popup.js
javascript
document.addEventListener('DOMContentLoaded', () => {
const downloadBtn = document.getElementById('downloadBtn');
const status = document.getElementById('status');
const imageCount = document.getElementById('imageCount');
const prefixInput = document.getElementById('prefix');
const filterInput = document.getElementById('filter');
const onlyVisibleCheckbox = document.getElementById('onlyVisible');
// 新增目录输入元素
const directoryInput = document.getElementById('directory');
// 初始获取图片数量
updateImageCount();
// 监听下载按钮点击事件
downloadBtn.addEventListener('click', () => {
// 显示加载状态
showStatus('正在收集图片...', 'success');
// 获取当前活动标签页
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const activeTab = tabs[0];
// 向content script发送消息获取图片
chrome.tabs.sendMessage(activeTab.id, {
action: 'getImages',
filter: filterInput.value,
onlyVisible: onlyVisibleCheckbox.checked
}, (response) => {
if (chrome.runtime.lastError) {
showStatus('无法获取图片: ' + chrome.runtime.lastError.message, 'error');
return;
}
if (!response || !response.images || response.images.length === 0) {
showStatus('没有找到符合条件的图片', 'error');
return;
}
const images = response.images;
const prefix = prefixInput.value;
// 显示下载信息
showStatus(`找到 ${images.length} 张图片,开始下载...`, 'success');
// 发送消息到background.js进行下载
chrome.runtime.sendMessage({
action: 'downloadImages',
images: images,
prefix: prefix
});
// 关闭弹出窗口
window.close();
});
});
});
// 监听过滤条件变化,更新图片数量
filterInput.addEventListener('input', updateImageCount);
onlyVisibleCheckbox.addEventListener('change', updateImageCount);
// 更新图片数量
function updateImageCount() {
// 获取当前活动标签页
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const activeTab = tabs[0];
// 向content script发送消息获取图片
chrome.tabs.sendMessage(activeTab.id, {
action: 'getImages',
filter: filterInput.value,
onlyVisible: onlyVisibleCheckbox.checked
}, (response) => {
if (!chrome.runtime.lastError && response && response.images) {
imageCount.textContent = response.images.length;
} else {
imageCount.textContent = '?';
}
});
});
}
// 显示状态信息
function showStatus(message, type) {
status.textContent = message;
status.className = `status ${type}`;
status.style.display = 'block';
}
});
3.配置文件
json
{
"manifest_version": 3,
"name": "图片下载器",
"version": "1.0",
"description": "一键下载当前页面上的所有图片",
"permissions": [
"activeTab",
"downloads",
"storage",
"tabs",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon16.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"icons": {
"16": "icon16.png"
}
}
这个一定要配置
**downloads ** 下载权限
**storage ** 存储权限
4.连接层context
是连接网页和后台脚本的桥梁。它会被注入到当前正在浏览的网页中,负责提取页面中图片的 src 或者 data-src 属性值。在提取过程中,它会处理 URL 路径,把相对路径转换为绝对路径,以确保图片能够被正确访问。之后,它会根据用户设置的过滤规则,筛选出符合条件的图片,并把这些图片的列表发送给后台脚本。
javascript
// 监听来自popup的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'getImages') {
const { filter, onlyVisible } = message;
// 收集页面上的所有图片
let images = Array.from(document.querySelectorAll('img')).map(img => {
let src = img.src;
debugger;
// 处理data-src等替代属性
if (!src && img.dataset.src) src = img.dataset.src;
if (!src && img.dataset.origSrc) src = img.dataset.origSrc;
if (!src && img.dataset.lazySrc) src = img.dataset.lazySrc;
// 跳过没有有效src的图片
if (!src) return null;
// 处理相对URL
if (!src.startsWith('http') && !src.startsWith('data:')) {
try {
src = new URL(src, window.location.href).href;
} catch (e) {
return null;
}
}
// 跳过data:URL的图片(通常是小图标或临时图片)
if (src.startsWith('data:')) return null;
return {
src: src,
width: img.naturalWidth || img.offsetWidth,
height: img.naturalHeight || img.offsetHeight,
alt: img.alt || '',
isVisible: isElementInViewport(img)
};
}).filter(img => img !== null);
// 过滤可见图片
if (onlyVisible) {
images = images.filter(img => img.isVisible);
}
// 应用正则表达式过滤
if (filter && filter.trim() !== '') {
try {
const regex = new RegExp(filter, 'i');
images = images.filter(img => regex.test(img.src) || regex.test(img.alt));
} catch (e) {
console.error('无效的正则表达式:', filter);
}
}
// 返回图片列表
sendResponse({ images });
}
return true; // 保持消息通道打开,直到sendResponse被调用
});
// 检查元素是否在视口中
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
5. 后台处理层
后台脚本(background.js)是插件的核心处理部分。它接收内容脚本发送过来的图片列表后,会调用 Chrome 浏览器的 downloads API,循环对列表中的图片进行下载操作,并将图片保存到本地用户指定的路径中。在下载过程中,它还会监控下载进度,判断所有图片是否都下载完成。
javascript
// background.js
// 监听来自content script的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'downloadImages') {
const { images, prefix } = message;
let downloaded = 0;
let failed = 0;
// 逐个下载图片
images.forEach((image, index) => {
// 生成唯一的文件名
const filename = generateFilename(image.src, prefix, index);
debugger;
// 创建下载任务
chrome.downloads.download({
url: image.src,
filename: filename,
saveAs: false
}, (downloadId) => {
if (!downloadId) {
console.error('下载失败:', chrome.runtime.lastError, image.src);
} else {
downloaded++;
}
// 当所有下载任务都完成后,发送通知
if (index === images.length - 1) {
sendNotification(downloaded, failed);
}
});
});
}
});
// 生成文件名
function generateFilename(url, prefix, index) {
try {
// 尝试从URL中提取文件名
const urlObj = new URL(url);
let filename = urlObj.pathname.split('/').pop();
// 如果没有扩展名,添加.jpg
if (!filename.includes('.')) {
filename += '.jpg';
}
// 移除不合法的文件名字符
filename = filename.replace(/[\\/:*?"<>|]/g, '_');
// 添加前缀
if (prefix && prefix.trim() !== '') {
return `${prefix.trim()}_${filename}`;
}
return filename;
} catch (e) {
// 如果URL解析失败,生成一个通用文件名
return `${prefix || 'image'}_${index}.jpg`;
}
}
// 发送下载完成通知
function sendNotification(downloaded, failed) {
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon128.png',
title: '图片下载完成',
message: `成功下载 ${downloaded} 张图片,${failed} 张失败。`
});
}
6.反馈层
当后台脚本判断所有图片都下载完成后,会通过 Chrome 浏览器的 notifications API 向用户发送下载完成的通知,让用户及时了解下载状态,这就是反馈层的功能。