前后端主流截图方案

主流截图方案

前端截图

若采用前端截图的方案,大致过程如下。将html转图片提交,三次兜底上传至服务端,成功后获得回传id,若三次失败,则提示错误。
在这里插入图片描述
前端截图的方案有两个缺陷不能避免,第一转图片比较费时(具体查看方案实践);其次提交的过程受网络情况影响。这两点加起来一次过程可能就要2秒以上。

下面说说前端主流的转图片方案。

canvas转换

大致流程如下图,首先克隆dom树(获取开发者定义的样式等),使用iframe重新渲染一次dom,通过getComputedStyle获得最终每个节点的样式,使用StackingContext模拟层叠上下文,最后绘制到画布上(绘制过程有好几种方法,其中一种就是svg转换)。
在这里插入图片描述

svg转换

svg中有一个<foreignObject>,具体介绍可以查阅MDN。它允许包含来自不同的XML命名空间的元素(即xhtml/html)。利用这个特性,只需要将节点样式转换为内联后,用<foreignObject>包裹即可。其大致流程如下:
在这里插入图片描述

webtrc方式

利用webrtc获取媒体流,通过video的srcObject属性播放媒体流,最后在canvas中画出video中的某一帧。

服务端截图

使用无头浏览器,如phantomJS、puppeteer、chrome headless等,访问资源,直接截图。大致流程如下:
在这里插入图片描述

前端截图方案实践

目前知名22.8k star开源项目的 html2canvas 就将上述两种方案进行整合,通过修改配置项foreignObjectRendering的值切换转换方式,默认为false,使用第一种。

dom-to-image 7k star开源项目,就是以svg转换为核心。

canvas方式

  • 图片生成需要遍历dom树,获得renderLayers和getComputedStyle,从实践结果来看,这种方式速度并不稳定。
  • 因为是通过draw的方法画到画布上,在计算定位时会舍弃后面的小数位,比如 width: calc(100px / 3)结果就会省略
    在这里插入图片描述

svg方式

这种方式平均速度比上一种方式更快,且更加稳定,因为只需要样式内联转换
在这里插入图片描述

webrtc方式

  • 需要用户授权
  • 不太好控制截取的帧
class ScreenShot {
    
    
  private readonly videoController: HTMLVideoElement
  private readonly canvasController: HTMLCanvasElement
  private canvasContext: CanvasRenderingContext2D | undefined

  constructor() {
    
    
    this.videoController = document.createElement('video')
    this.videoController.autoplay = true

    this.canvasController = document.createElement('canvas')
    this.canvasController.height = window.innerHeight
    this.canvasController.width = window.innerWidth
  }

  // 开始捕捉屏幕
  async startCapture() {
    
    
    let captureStream = null;

    try {
    
    
      // @ts-ignore
      // 捕获屏幕
      captureStream = await navigator?.mediaDevices?.getDisplayMedia();
      // 将MediaStream输出至video标签
      this.videoController.srcObject = captureStream;
    } catch (err) {
    
    
      throw "浏览器不支持webrtc" + err;
    }
    return captureStream;
  }

  // 停止捕捉屏幕
  stopCapture() {
    
    
    const srcObject = this.videoController.srcObject;
    if (srcObject && "getTracks" in srcObject) {
    
    
      const tracks = srcObject.getTracks();
      tracks.forEach(track => track.stop());
      this.videoController.srcObject = null;
    }
  };

  // 截屏
  shotPrint() {
    
    
    // 开始捕捉屏幕
    return this.startCapture().then(() => {
    
    
      return new Promise(resolve => {
    
    
        setTimeout(() => {
    
    
          // 获取截图区域canvas容器画布
          const context = this.canvasController?.getContext("2d");
          if (this.canvasController == null || context == null) return;

          // 赋值截图区域canvas画布
          this.canvasContext = context;
          // 将获取到的屏幕截图绘制到图片容器里
          this.canvasContext
            ?.drawImage(
              this.videoController,
              0,
              0,
              this.canvasController?.width,
              this.canvasController?.height
            );
          // 停止捕捉屏幕
          this.stopCapture();
          resolve(this.canvasController)
        }, 300)
      })
    });
  };
}

export default ScreenShot

服务端截图方案实践

使用puppeteer截图,代码demo如下:

const puppeteer = require('puppeteer');
(async () => {
    
    
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  try {
    
    
    const startTime = Date.now()
    console.log('@@@ time start', startTime)
    await page.goto(
      'https://www.baidu.com',
      {
    
    
        waitUntil: 'networkidle0'
      }
    );
    await page.screenshot({
    
    path: 'baidu.png', fullPage: true});
    console.log('@@@ time end', Date.now())
    console.log('@@@ time cost', Date.now() - startTime)
    console.log('@@@ -----')
    await browser.close();
  } catch(e) {
    
    
    console.log(e)
  }
})();
  • 截图效果:
    在这里插入图片描述

方案对比

前端截图

  • 优点:
    • 在用户侧完成截图全过程,只需依赖后端提供的上传接口
    • 主要流程前端控制
  • 缺点:
    • 样式支持率有限,html2canvas支持的样式如下https://html2canvas.hertzen.com/features,可以看到不支持的css属性也比较多
    • 互动题的图片比较多,跨域图片需要设置crossOrigin=“anonymous”,且服务端配合支持cors
    • 单位精度丢失,在转换过程中一些单位计算会忽略
    • 非常依赖用户的设备硬件情况,无法保持稳定

服务端

  • 优点
    • 克服前端场景下的缺点
  • 缺点
    • 比较消耗服务器资源,需要扩容

文档

猜你喜欢

转载自blog.csdn.net/sinat_36521655/article/details/113917266