前端浏览器如何实现截图

661 阅读5分钟

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的样式布局位置效果一一画出来。转换过程可理解成:DOMCanvasImage

rasterizehtml为代表的SVG截图,通过遍历DOM克隆一份副本,利用SVG的foreignObject把DOM作为外部资源嵌套在SVG中,将此SVG在Canvas上重新绘制,并根据DOM的样式应用在对应的绘制元素上,再通过Canvas生成图片。转换过程可理解成:DOMSVG的ForeignObjectCanvasImage

两种前端截图方式最后都是通过把DOM绘制到Canvas,再通过Canvas输出图片

限制

虽然两种前端截图方式都有这两个封装得比较完善的第三方库。如html2canvasrasterizehtml,但是由于在转换过程中存在一些自身的局限性

Canvas截图的限制性

  • 无法渲染跨域资源(支持同域)
  • 无法渲染iFrameFlash内容(支持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功能权限,如下图

QQ图片20241030221739.png

用户交互体验不太好。同时如果你的页面处于全屏状态时,这样的权限请求框会强制的退出全屏状态。 `

(3)借助浏览器插件

实现思路

  1. 拿到用户想要截图的 DOM,或者选取的区域
  2. 截图整个页面
  3. 裁剪出来需要的区域(也可以是整个页面)
  4. 复制图片到用户的剪切板中(可配置要文件流还是base64)或者下载图片

限制:需要用户额外下载特定的插件、且不同浏览器都要下载,这样还不如直接使用电脑系统自带的截图工具。

3、总结

到头来才发现没有哪一个方案是完全符合要求。

R-C.jfif 于是介于用户体验与方便性综合考虑,决定摒弃第三种方案。但前两种方案各有优缺点,由于只好兼容这两种方式,操作交互体验上参考了微信截图工具,实现了一个截图工具 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;
  },
});

其他配置项

参数名类型默认值说明
captureDomHTMLElement(可选)window.document.body当isWebRtc为true时无效,同时也会将操作时的相关dom挂载其上
zIndexnumber (可选)99999挂载dom层级
isWebRtcboolean(可选)false是否开启WebRtc,开启后会将整个屏幕进行截图 。默认为false,是使用html2canvas方式进行底图截取
saveCallbackFunction(可选)完成截图后点击完成后回调函数,会将截图结果通过base64形式进行回传
loadCallbackFunction(可选)截图完成截图底图资源渲染回调
basicImageHTMLImageElement(可选)截图底图,传入后可不借助html2canvas或 WebRtc方式进行底图图片截取