1、需求背景
正常情况下,我们是可以使用电脑自带的截图功能或者借助微信、QQ第三方的工具快捷键实现屏幕的截图,但是现在遇到一个需求就是需要你不采用这些方式,直接在浏览器页面中点击一个按钮就可以实现截图操作。放着现存的工具不用,非要去造轮子,但是还是没办法,就去调研了相关浏览器截图的实现方法与对应的实现原理。
2、实现方案与技术原理
总的来说有三种方式实现浏览器屏幕内容截屏
(1)dom转成canvas
原理:借助工具将页面dom样式布局转成canvas呈现效果,再通过canvas转成图片达到屏幕截图效果。 这类工具库比较多,如html2canvas、dom-to-image、modern-screenshot、rasterizehtml等工具库。这些库实现方式分为两类,如:
- Canvas截图:
html2canvas - SVG截图:
rasterizehtml
原理
虽然实现方式不太一致,但是核心思想是相同的、底层原理大同小异。
以html2canvas为代表的Canvas截图,简单来说就是克隆传入的dom,遍历克隆树,通过getBoundingClientRect获取元素所在页面位置、大小,再通过getComputedStyle获取元素样式,再使用canvas的底层API,将对应dom的样式布局位置效果一一画出来。转换过程可理解成:DOM→Canvas→Image。
以rasterizehtml为代表的SVG截图,通过遍历DOM克隆一份副本,利用SVG的foreignObject把DOM作为外部资源嵌套在SVG中,将此SVG在Canvas上重新绘制,并根据DOM的样式应用在对应的绘制元素上,再通过Canvas生成图片。转换过程可理解成:DOM→SVG的ForeignObject→Canvas→Image。
两种前端截图方式最后都是通过把DOM绘制到Canvas,再通过Canvas输出图片。
限制
虽然两种前端截图方式都有这两个封装得比较完善的第三方库。如html2canvas和rasterizehtml,但是由于在转换过程中存在一些自身的局限性
Canvas截图的限制性
- 无法渲染
跨域资源(支持同域) - 无法渲染
iFrame和Flash内容(支持SVG)
SVG截图的限制性
- 无法渲染
跨域资源(支持同域) - 无法渲染如
lazyload等通过JS加载的资源 - 无法渲染
内联background-image或JS操作background-image
同时当页面内容越多越复杂,渲染等待时间就越长,且对于某些特定的css样式效果并不能完全复现或者压根没办法支持,所以也导致截图可能出现一些不完美的问题。
(2)基于浏览器的webrtc技术
原理:使用浏览器的webrtc的录屏技术,将电脑的屏幕内容进行短暂共享,然后截取其中的一小段视频流转为图片,从而达到截图目的。截图效果也比较可观。
相关代码
const displayMediaOptions = {
video: {
cursor: "always"
},
audio: false
};
navigator.mediaDevices.getDisplayMedia(displayMediaOptions).then(stream => {
videoElem.srcObject = stream;
var canvas = document.createElement("canvas");
canvas.width = videoElem.clientWidth;
canvas.height = videoElem.clientHeight;
var needScreenShotEl = document.getElementById('needScreenShotArea');
var pos = getPosition(needScreenShotEl);
var toolBarHeight = window.outerHeight - window.innerHeight;
canvas.getContext("2d").drawImage(videoElem, windowX + pos.left,
windowY + (document.fullscreenElement ? pos.top : pos.top),
needScreenShotEl.clientWidth, needScreenShotEl.clientHeight, 0, 0, canvas.width, canvas.height
);
var dataURL = canvas.toDataURL("image/png");
document.getElementById('showScreenShout').src = dataURL;
let tracks = videoElem.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoElem.srcObject = null;
})
限制:每一次截屏都需要向用户请求开启webrtc功能权限,如下图
用户交互体验不太好。同时如果你的页面处于全屏状态时,这样的权限请求框会强制的退出全屏状态。 `
(3)借助浏览器插件
实现思路:
- 拿到用户想要截图的
DOM,或者选取的区域 - 截图整个页面
- 裁剪出来需要的区域(也可以是整个页面)
- 复制图片到用户的剪切板中(可配置要文件流还是base64)或者下载图片
限制:需要用户额外下载特定的插件、且不同浏览器都要下载,这样还不如直接使用电脑系统自带的截图工具。
3、总结
到头来才发现没有哪一个方案是完全符合要求。
于是介于用户体验与方便性综合考虑,决定摒弃第三种方案。但前两种方案各有优缺点,由于只好兼容这两种方式,操作交互体验上参考了微信截图工具,实现了一个截图工具 shot-web-screen - npm (npmjs.com),
- 功能支持:矩形绘制、圆型绘制、箭头绘制、画笔、马赛克、文本编辑以及相关组合操作的撤回、编辑与拖拽移动
- 语言支持:TS、js
下载
npm install shot-web-screen
使用方法
import captureScreen from "shot-web-screen";
let imgUrl = ''
new captureScreen({
saveCallback: (base64: string) => {
imgUrl = base64;
},
});
其他配置项
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| captureDom | HTMLElement(可选) | window.document.body | 当isWebRtc为true时无效,同时也会将操作时的相关dom挂载其上 |
| zIndex | number (可选) | 99999 | 挂载dom层级 |
| isWebRtc | boolean(可选) | false | 是否开启WebRtc,开启后会将整个屏幕进行截图 。默认为false,是使用html2canvas方式进行底图截取 |
| saveCallback | Function(可选) | 无 | 完成截图后点击完成后回调函数,会将截图结果通过base64形式进行回传 |
| loadCallback | Function(可选) | 无 | 截图完成截图底图资源渲染回调 |
| basicImage | HTMLImageElement(可选) | 无 | 截图底图,传入后可不借助html2canvas或 WebRtc方式进行底图图片截取 |