官方示例中提供了这个例子,不过是基于v2
版本的。这里我们学习一下实现的原理,开发一个基于v3
的demo
官方例子:download_images
问题
查看了一下源码,发现这个demo比较简单。是通过获取页面中的img
标签来获取链接的,但是这种方式有个问题就是,图片有可能是相对路径。
相对路径存在一下两种情况:
1、网站的基础路径就是图片的根路径(简单)
2、网站的基础路径不是图片的根路径(复杂)
无论是简单的还是复杂的都需要进行消息传递,通过扩展往页面里注入脚本,由这部分脚本来获取到页面里所有img
标签的src
属性值。然后再由脚本向扩展发送消息,将这些属性值传递给扩展。
以下内容都是基于react
进行开发的,第一次用react
开发脚本,应该会有不少理解错误的地方。(在这个过程中,很多实现都是问的new bing,真的是太方便了。我向她描述问题,她给我提供思路)
实际上手后才发现,起始还有很多的小细节。一开始是直接使用document.querySelectorAll('img')
来获取图片,但是如果存在iframe
标签的话,document.querySelectorAll('img')
是无法获取的iframe
里的图片
原因是:
document.querySelectorAll(‘img’) 只能获取当前文档中匹配选择器的元素,而不能获取iframe标签里的元素,因为iframe标签里的内容是另一个文档。如果你想获取iframe标签里的img元素,你需要先获取iframe标签的引用,然后使用它的contentDocument属性来访问它的文档,再使用querySelectorAll(‘img’)方法
因此获取图片应该如下:
export const funaa = () => {
// 存储src属性
let srcArray = []
// 获取body中所有的image标签的currentSrc属性
let bodyImages = document.querySelectorAll('img')
Array.from(bodyImages).forEach(img => {
srcArray.push(img.currentSrc)
})
// 获取iframes中所有的image标签的currentSrc属性
let iframes = document.querySelectorAll('iframe')
Array.from(iframes).forEach(iframe => {
// 获取当前iframe标签里的img元素
let iframeImages = iframe.contentDocument.querySelectorAll('img');
Array.from(iframeImages).forEach(img => {
srcArray.push(img.currentSrc)
})
})
console.log("图片是:", srcArray)
}
为什么是currentSrc而不是src属性
说实话,还真不知道currentSrc
属性。自己真的是个大菜逼,还一直沾沾自喜,觉得自己水平还可以。
img标签的src属性和currentSrc属性有一些区别。
src属性是用来指定图片的源地址的,它可以是一个绝对的或相对的URL。
currentSrc属性是用来表示当前显示在img标签里的图片的完整URL的,它是一个只读的属性,不能被修改
currentSrc
属性在你使用sizes
和srcset
属性来提供多个图片选项的时候很有用,因为它可以让你知道浏览器选择了哪一个图片。例如,下面的代码提供了一个时钟图片的两个不同尺寸,一个是200px宽,另一个是400px宽。sizes属性用来指定图片在不同视口宽度下的显示大小。你可以通过currentSrc
属性来查看浏览器实际选择了哪个图片。
<img src="clock-200.jpg" srcset="clock-200.jpg 200w, clock-400.jpg 400w" sizes="(max-width: 400px) 50vw, 90vw">
<script>
var img = document.querySelector('img');
console.log(img.currentSrc); // 打印出当前显示的图片的URL
script>
上面还在分析如果src
属性是相对路径时该如何获取到这个图片的地址呢,现在直接被打脸了。这下也不用考虑了,直接用currentSrc
属性就好了
除了img
标签中存在src
和currentSrc
属性外,video
标签中同样存在这两个属性。获取网页中的图片,起始也可以变成获取网页中的视频。
但是获取视频其实还有些别的问题:
下载视频这个如果以后有时间,会专门写一篇文章来介绍。
言归正传,继续来实现图片下载这个功能。获取到图片后我们将这些图片地址给缓存起来,这里使用chrome.storage.local
chrome.storage.local是一个用来存储数据的API,它可以让你在Chrome扩展中保存和读取对象。 它和localStorage
类似,但是有一些区别:
chrome.storage.local
可以保存任何类型的数据,而localStorage
只能保存字符串类型的数据。chrome.storage.local
可以在不同的上下文中共享数据,例如background.js
和popup.html
,而localStorage
只能在同一个网页中访问数据。chrome.storage.local
有一个存储限制,一般是5MB,除非你申请了unlimitedStorage
权限,而``localStorage`的存储限制取决于浏览器和网站。chrome.storage.local
是异步的,需要使用回调函数来处理结果,而localStorage
是同步的,可以直接返回结果。可以使用chrome.storage.local.set
方法来保存数据,使用chrome.storage.local.get
方法来读取数据,使用chrome.storage.local.remove
方法来删除数据。还可以使用chrome.storage.onChanged
事件来监听数据的变化
// 保存图片链接
const [imgList, setImgList] = useState([]);
// 注入脚本
const insertScripts = () => {
checkPage(async (tab) => {
//依赖注入
const result = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: getAllImage,
});
//console.log(result);
// 这个API暂时无用,如果向打开一个新页面展示这些图片时有用
chrome.storage.local.set('imgs',result[0]?.result || [])
setImgList(result[0]?.result || []);
});
};
渲染这些图片
import React from 'react';
import './img-list.scss';
import { Checkbox } from 'antd';
const ImgList = (props) => {
let img = props.list.map((e) => {
return (
<div key={e} >
<img src={e} alt="图片" className="img-item" />
<Checkbox value={e} style={{position:'relative',right:'20px',top:'-10px'}}></Checkbox>
</div>
);
});
return (
<Checkbox.Group className="img-list">{img}</Checkbox.Group>
);
};
export default ImgList;
图片下载这里需要用到 chrome插件提供的api,先添加下载权限 downloads
,然后直接使用下面的方法即可进行下载。
除此之外还需用到 FileSaver
、jszip
。chrome.downloads.download
只能根据url
进行下载,不能下载文件流。
chrome.downloads.download({url: 'https://scpic.chinaz.net/Files/pic/pic9/202011/apic28854_s.jpg'});
npm i jszip
npm i file-saver
这里打算进行简单的优化:
// 转换图片为base64格式
const getImageBase64 = (url) => {
let image = new Image();
image.crossOrigin = 'Anonymous';
image.src = url;
// 创建一个画布
const canvas = document.createElement('canvas');
// 让画布的宽高等于图片的宽高
canvas.width = image.width;
canvas.height = image.height;
// 然后在画布上进行画画
const ctx = canvas.getContext('2d');
// 开始画图片,1.绘制的对象2.绘制的位置,3绘制的宽高
ctx.drawImage(image, 0, 0, image.width, image.height);
return canvas.toDataURL();
};
// 将文件打包并下载
const downloadZip = async (sourceList, zipName) => {
const zip = new JSZip(); // 创建一个压缩对象
const fileFolder = zip.folder(zipName); // 创建 zipName 文件夹
const fileList = [];
// 遍历图片数据
for (let i = 0; i < sourceList.length; i++) {
let image = sourceList[i];
// 路径base64转换
const url = await getImageBase64(image);
let filename = image.split('/').pop();
// 往压缩包里添加图片数据
fileList.push({
name: filename, // 压缩名
img: url.substring(22), // 去掉base64文件标识,img是文件形式
});
// 确保每个数据都遍历完
if (fileList.length === sourceList.length) {
// 确保数据不为空
if (fileList.length) {
for (let k = 0; k < fileList.length; k++) {
// 往文件夹中,添加每张图片数据fileFolder文件夹压缩包
fileFolder.file(fileList[k].name, fileList[k].img, {
base64: true,
// 下面的可以省略
compression: 'DEFLATE', // STORE:默认不压缩 DEFLATE:需要压缩
compressionOptions: {
level: 9, // 压缩等级1~9 1压缩速度最快,9最优压缩方式
// [使用一张图片测试之后1和9压缩的力度不大,相差100字节左右]
// 注意到达这里的时候image的路径必须是带有data:image/png;base64,
},
});
}
zip
.generateAsync({
// 压缩的结果为blob类型(二进制流),可用作文件上传
type: 'blob',
})
.then((content) => {
// 直接在浏览器打成zipName.zip包并下载,saveAs依赖的js是FileSaver.js
FileSaver.saveAs(content, zipName + '.zip');
})
.catch((msg) => {
console.log(msg);
});
}
}
}
};
下载插件
关注公众号回复关键字:图片下载插件 ,进行下载,下载完成后,将压缩包解压,将解压后的文件夹拖进浏览器的扩展程序里。
下载插件及源码(收费)
关注公众号,回复关键词 图片下载插件源码。会收到一张1元的支付二维码,付款后将付款截图在公众号里发给我。收到付款截图后,我将在6小时以内发你源码。