本文总结了五种JavaScript实现网页截屏的方法,涵盖浏览器端与Node.js环境。重点对比了PhantomJS、Puppeteer、SlimerJS以及dom-to-image和html2canvas。针对包含WebGL的页面,分析了各工具的支持情况与局限性,并探讨了后端截屏结合前端库的实现思路。
近期调研了基于JavaScript实现网页截屏的技术方案,涵盖浏览器端脚本与Node.js后端环境。主要考察了以下五种工具:
PhantomJS
Puppeteer(Chrome Headless)
SlimerJS
dom-to-image
html2canvas
由于测试页面涉及WebGL技术,后续总结将重点关注各工具对WebGL的支持情况。
名词定义
Headless Browser(无界面浏览器)
指无需图形用户界面的浏览器,常用于网页自动化测试、截图生成及网络监控等场景。
PhantomJS
PhantomJS是一款支持JavaScript编程的无界面浏览器,基于QtWebKit内核构建。
实现截屏的核心代码示例,假设保存为github.js:
// 创建一个网页实例
var page = require('webpage').create();
// 加载页面
page.open('http://github.com/', function () {
// 给网页截屏,保存到github.png文件中
page.render('github.png');
phantom.exit();
})
执行命令:
phantomjs github.js
普通HTML页面截屏正常,但包含WebGL内容的页面截屏结果异常。经排查,PhantomJS不支持WebGL渲染(参考GitHub Issue)。
结论:
PhantomJS已停止维护,建议不再使用。其停更部分原因是受Chrome Headless版本推出的影响。
不支持WebGL。虽有开发者尝试为PhantomJS添加WebGL支持,但该方案超出当前研究范围,未深入探讨。
Puppeteer(Chrome Headless)
Puppeteer是一个Node.js库,提供控制Chrome或Chromium浏览器的API。默认以Headless模式运行,同时也支持有界面模式。
截屏实现代码示例(example.js):
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ // 设置视窗大小
width: 600,
height: 800
});
await page.goto('https://example.com'); // 打开页面
await page.screenshot({path: 'example.png'}); // path: 截屏文件保存路径
await browser.close();
})();
运行命令:
node example.js
接下来分析screenshot方法的实现原理:
screenshot的源码位于lib/cjs/puppeteer/common/Page.js文件中,是一个异步方法:
async screenshot(options = {}) {
// ...
return this._screenshotTaskQueue.postTask(() => this._screenshotTask(screenshotType, options));
}
async _screenshotTask(format, options) {
// ...
const result = await this._client.send('Page.captureScreenshot', {
format,
quality: options.quality,
clip,
});
// ...
}
这里的this._client.send具体指什么?回顾Puppeteer的定义:
“Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol.
”
注意末尾提到的DevTools Protocol,它是什么?
“The Chrome DevTools Protocol allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers.
”
详细解释可参考相关技术博客。
简而言之,Puppeteer通过WebSocket发送遵循Chrome DevTools Protocol的数据,命令浏览器执行操作,浏览器再通过WebSocket异步返回结果。源码中大量使用async/await即源于此机制。
因此,screenshot方法实际调用了Chrome DevTools Protocol中的captureScreenshot接口。
结论:
支持WebGL渲染。
对于复杂网页,截屏耗时较长,测试页面耗时数百毫秒。
Puppeteer封装了Chrome DevTools Protocol (CDP) 的功能,多数操作通过WebSocket传输给CDP处理。
SlimerJS
SlimerJS与PhantomJS类似,区别在于其基于Firefox的Gecko引擎,而非WebKit。
SlimerJS可通过npm安装,最新版本为1.x,但兼容的Firefox版本仅为53.0至59.0。鉴于当前Firefox最新版本已至82,需手动安装旧版Firefox(如59.0)。参考相关安装指南,在Mac系统下安装过程相对简便。
截屏实现代码(screenshot.js):
var page = require('webpage').create();
page.open("http://slimerjs.org", function (status) {
page.viewportSize = { width:1024, height:768 };
page.render('screenshot.png');
});
执行命令:
// mac操作系统设置火狐路径
export SLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox
./node_modules/.bin/slimerjs screenshot.js // 我是局部安装的slimer包
需要注意的是SLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox启动的是火狐默认的安装路径,因为我一开始就有火狐浏览器,所以启动的是最新版本的浏览器,然后就报错了,说不兼容。在前面我安装过一个59版本的火狐,那么这个火狐浏览器的路径是什么?
在应用程序中将旧版Firefox命名为Firefox59,其路径即为/Applications/Firefox59.app/Contents/MacOS/firefox。将SLIMERJSLAUNCHER设置为59.0版本Firefox路径后,截屏成功。
此外,Puppeteer默认以非Headless模式(即显示浏览器界面)运行。若需使用Headless模式,可配置:
./node_modules/.bin/slimerjs --headless screenshot.js
但在Headless模式下,SlimerJS不支持WebGL。
在编写示例时发现一个显著差异:Puppeteer截屏为异步函数,而SlimerJS截屏为同步函数。查阅源码(src/modules/slimer-sdk/webpage.js)后发现:
render: function(filename, options) {
// ...
let canvas = webpageUtils.getScreenshotCanvas(
browser.contentWindow,
finalOptions.ratio,
finalOptions.onlyViewport, this);
}
canvas.toBlob(function(blob) {
let reader = new browser.contentWindow.FileReader();
reader.onloadend = function() {
content = reader.result;
}
reader.readAsBinaryString(blob);
}, finalOptions.contentType, finalOptions.quality);
// ...
}
webpageUtils.getScreenshotCanvas(src/modules/webpageUtils.jsm):
getScreenshotCanvas : function(window, ratio, onlyViewport, webpage) {
// ...
// create the canvas
let canvas = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
let ctx = canvas.getContext("2d");
ctx.scale(ratio, ratio);
ctx.drawWindow(window, clip.left, clip.top, clip.width, clip.height, "rgba(0,0,0,0)");
ctx.restore();
return canvas;
}
关键代码行ctx.drawWindow。令人惊讶的是,JS原生API支持直接截屏?
CanvasRenderingContext2D.drawWindow():此为Firefox特有且已废弃的非标准API。
总结
1.0版本仅支持Firefox 53.0至59.0,不保证与最新版Firefox兼容。
Headless模式下不支持WebGL。
dom-to-image
dom-to-image是一个前端截屏开源库,其工作原理如下: SVG的foreignObject标签可包裹任意HTML内容。渲染节点的主要步骤包括:
递归复制原始DOM节点及其后代节点;
递归应用原始节点及后代节点的样式到复制后的节点;
处理字体;
处理图片;
序列化复制后的节点,插入foreignObject中,生成SVG,进而生成Data URL;
若需获取PNG内容或原始像素值,可先利用Data URL创建图片,通过离屏Canvas渲染该图片,再从Canvas中获取所需数据。
测试中发现无法加载外部资源,因此简要了解后放弃使用。
html2canvas
html2canvas。网上有一篇名为“浅析 js 实现网页截图的两种方式”的文章写得不错,感兴趣者可参考。
未验证的猜想
尽管后两种为前端实现方式,但结合前述Headless库,也可实现后端截屏。以Puppeteer API为例,可先使用page.addScriptTag(options)向网页注入前端截屏库,然后在page.evaluate(pageFunction[, ...args])的pageFunction函数中编写截屏代码。由于pageFunction的执行上下文为网页上下文,因此可获取到document等对象。
关于JavaScript实现网页截屏的5种方法小结到此结束,更多相关JavaScript 网页截屏内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!