作者:luckness

原文:https://segmentfault.com/a/1190000037673677

最近研究了下如何利用JavaScript实现网页截屏,包括在浏览器运行的JS,以及在后台运行的nodeJs的方法。主要看了以下几个:

  1. PhantomJS

  2. Puppeteer(chrome headless)

  3. SlimerJS

  4. dom-to-image

  5. html2canvas

测试的网页使用了WebGL技术,所以下面的总结会和WebGL相关。

1名词定义

2headless browser

无界面浏览器,多用于网页自动化测试、网页截屏、网页的网络监控等。

3PhantomJS

PhantomJS是可以通过JS进行编程的headless浏览器,使用的是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

普通的页面没有问题,但是如果运行包含WebGL的页面,发现截屏不对。经过一些调查,发现不支持WebGL,github issue。

总结:

  1. PhantomJs已经停止维护了,所以不太建议继续使用。停止维护的一个原因是chrome发布的headless版本对它造成了一定冲击。

  2. 不支持WebGL。但是,还是有开发者说可以自己给PhantomJS添加WebGL支持,不过,这个方案目前超出我的知识范围了,就没有继续研究。

4Puppeteer(chrome headless)

Puppeteer是一个Node库,提供了控制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把结果返回给Puppeteer。这个过程是异步的,所以看源代码会发现好多async/await。

所以screenshot方法是调用了Chrome Devtools Protocol的captureScreenshot。

总结:

  1. 支持WebGL。

  2. 网页比较复杂的话,截屏时间也挺长的,我测试的页面是几百毫秒。

  3. Puppeteer是对(CDP)Chrome Devtools Protocol功能的封装。大部分功能都是通过WebSocket传输给CDP处理的。

欢迎关注公众号:前端印象

5SlimerJS

SlimerJS和PhantomJS类似。不同点是SlimerJS是基于火狐的浏览器引擎Gecko,而不是Webkit。

SlimerJS可以通过npm安装,最新版本是1.x。不过兼容的火狐版本是53.0到59.0。我看现在火狐最新版本都82了。因为我本机是安装了火狐最新版本的,所以我还得安装一个老版本的火狐,比如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版本的火狐,那么这个火狐浏览器的路径是什么?

在应用程序里面我把这个旧版本的火狐命名为Firefox59,然后这个路径就是/Applications/Firefox59.app/Contents/MacOS/firefox。重新设置SLIMERJSLAUNCHER为59版本的火狐浏览器之后,发现就能成功了。

不过,Puppeteer默认会打开浏览器界面,也就是non-headless模式。如果要使用headless模式,可以

    ./node_modules/.bin/slimerjs --headless screenshot.js

不过,headless模式下,不支持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 canvaslet 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。what?JS原生API还支持直接截屏?CanvasRenderingContext2D.drawWindow():只有火狐支持,已经被废弃掉的非规范定义的标准API。

总结

  1. 1.0版本支持的火狐版本是53.0到59.0。不保证最新版本火狐可用。

  2. headless模式下,不支持WebGL。

6dom-to-image

dom-to-image:前端截屏的开源库。工作原理是:SVG的foreignObject标签可以包裹任意的html内容。那么,为了渲染一个节点,主要进行了以下步骤:

  1. 递归地拷贝原始dom节点和后代节点;

  2. 把原始节点以及后代节点的样式递归的应用到对应的拷贝后的节点和后代节点上;

  3. 字体处理;

  4. 图片处理;

  5. 序列化拷贝后的节点,把它插入到foreignObject里面,然后组成一个svg,然后生成一个data URL;

  6. 如果想得到PNG内容或原始像素值,可以先使用data URL创建一个图片,使用一个离屏canvas渲染这张图片,然后从canvas中获取想要的数据。

测试的时候,发现外部资源不能加载,所以简单的了解了后就放弃了。

7html2canvas

html2canvas。网上查了下感觉有一篇文章写的挺好的:浅析 js 实现网页截图的两种方式。感兴趣的可以看下。

8未验证的猜想

虽然后面这两种是前端的实现方式,但是结合前面讲的headless库,也是可以实现后端截屏的。以Puppeteer的API为例,可以首先使用page.addScriptTag(options)往网页中添加前端截屏的库,然后在page.evaluate(pageFunction[, ...args])中的pageFunction函数里面写相应的截屏代码就可以了,因为pageFunction的执行上下文是网页上下文,所以可以获取到document等对象。

1. JavaScript 重温系列(22篇全)

2. ECMAScript 重温系列(10篇全)

3. JavaScript设计模式 重温系列(9篇全)

4. 正则 / 框架 / 算法等 重温系列(16篇全)

5. Webpack4 入门(上)|| Webpack4 入门(下)

6. MobX 入门(上) ||  MobX 入门(下)

7. 120+篇原创系列汇总

回复“加群”与大佬们一起交流学习~

点击“阅读原文”查看 120+ 篇原创文章

【JS】1007- JavaScript实现网页截屏的5种方法相关推荐

  1. JavaScript实现网页截屏的5种方法(详解+代码)

    最近研究了下如何利用JavaScript实现网页截屏,包括在浏览器运行的JS,以及在后台运行的nodeJs的方法.主要看了以下几个: PhantomJS Puppeteer(chrome headle ...

  2. JavaScript实现网页截屏方法总结

    " 关注『前端开发博客』公众号,回复 加群 " 最近研究了下如何利用JavaScript实现网页截屏,包括在浏览器运行的JS,以及在后台运行的nodeJs的方法.主要看了以下几个: ...

  3. JavaScript 实现网页截屏五种方法

    JavaScript 实现网页截屏五种方法 最近研究了下如何利用JavaScript实现网页截屏,包括在浏览器运行的JS,以及在后台运行的nodeJs的方法.主要看了以下几个: PhantomJS P ...

  4. android华为怎么截屏快捷键,华为p10怎么截图 华为p10截屏的三种方法

    华为p10怎么截屏和保存图片以及华为p10截屏快捷键在哪是很多朋友问到的,对于刚入手华为p10的朋友来说可能有些基本操作是不知道的,这里我们一起来了解一下华为p10怎么截屏和保存图片以及华为p10截屏 ...

  5. 华为手机滚动截屏的2种方法

    华为手机有一个超强的隐藏功能就是滚屏截长图:一般的手机只能截一屏,华为手截图可以是几屏,下面就分享一下华为手机滚动截屏的2种方法,如下: 方法一:画"S"长截图 1.开启指关节截屏 ...

  6. 怎么用计算机直接截图,电脑怎么快速截屏?分享电脑快速截屏的五种方法

    工作的时候经常遇到需要屏幕截图,只要提到截屏,大家立马就想到了QQ截屏,虽然此方法也不错,但每次都要登录QQ,略微有点麻烦,有些什么其他的快捷方法吗?答案是有的,这里教大家电脑截图快捷键操作方法. 具 ...

  7. 截取网页全屏的两种方法

    截取网页全屏的方式 很多时候我们需要对网页的全部内容进行一个截屏,但像微信或者QQ的截图方式只能截取当前页面的内容,下面介绍几种比较简单的截取页面全部内容的方法. 1.谷歌浏览器自带截图功能 2.使用 ...

  8. 在struts2中push方法的使用_电脑使用中怎么截屏的几种方法

    电脑在日常工作中经常需要用到截屏的操作,为了截取画面提供证明或者说明,像我就经常需要用到,当然我在写文章的时候更是需要用到,来配合文字的描述,使大家能更直观更容易的去操作,以达到快速解决电脑问题的目的 ...

  9. android平板怎么截图,在安卓手机或平板电脑上截屏的5种方法,学起来!

    在Android手机或平板电脑上截屏并不是随便按下一个按钮那样简单,尤其是当你尝试捕获运动中的特定场景(例如游戏或视频),或者你担心可能会消失的屏幕时,以及当你正打算截屏,但是一不小心把手机屏幕关了的 ...

最新文章

  1. Python图像处理介绍--Python中的图像表示
  2. 基于Python进行相机校准
  3. 11:菜单自动化软件部署经典案例
  4. Linux上 Can‘t connect to X11 window server using XX as the value of the DISPLAY 错误解决方法
  5. Jmeter性能测试-GC相关
  6. 突破C++瓶颈,在此一举!
  7. 深度学习(莫烦 神经网络 lecture 3) Keras
  8. Mantle--国外程序员最常用的iOS模型字典转换框架
  9. java this()函数_java中this关键字的三种用法
  10. 前端—每天5道面试题(4)
  11. 文献管理与信息分析_全球酒店PMS行业市场现状分析,酒店信息管理全链条的灵魂...
  12. 计算机网络第五次笔记
  13. Ubuntu 安装tftp服务器
  14. php 织梦symbol,dede织梦代码调用
  15. c语言指针的应用实验报告6,C语言实验六实验报告——指针.doc
  16. 【数字图像处理5.3】SLIC算法 超像素分割(无监督聚类方式)python
  17. 2018北航计算机考研复试经验
  18. STM32-GPRS模块连接系统主站
  19. 【lomoyi笔记】2020.7.31VMware虚拟机安装黑苹果macOS Catalina10.15
  20. 产品读书《魔鬼经济学3:用反常思维解决问题》

热门文章

  1. Cadence修改打开默认版本
  2. [Docer]docker镜像操作
  3. 信用评分卡建模:决策树模型
  4. BLE service, characteristic以及CCCD概念 9
  5. matlab2014simulink中的三相晶闸管整流桥怎么找_哈尔滨有源滤波组件HPD2000-100-4L坏了怎么办 - 哈尔滨照明工业...
  6. Android BKS 格式证书制作,JKS 制作 BKS,解决 java.security.KeyStoreException: JKS not found 问题
  7. Python上位机与C51单片机串口通信
  8. android恢复出厂设置
  9. 灰度共生矩阵(GLCM)计算速度快很多,用numpy写的
  10. c语言求解线性方程组ax=b,用C语言求解N阶线性矩阵方程Ax=b的简单解法