JS实现网页截图的三种方案

实现方式

一.canvas html2canvas

过程或者原理(如何将dom转换成canvas图片?)

​ 梳理了其大致的思路:

  • 递归取出目标模版的所有DOM节点,填充到一个rederList,并附加是否为顶层元素/包含内容的容器 等信息
  • 通过z-index ,postion, float等css属性和元素的层级信息将rederList排序,计算出一个canvas的renderQueue
  • 遍历renderQueue,将css样式转为setFillStyle可识别的参数,依据nodeType调用相对应canvas方法,如文本则调用fillText,图片drawImage,设置背景色的div调用fillRect等
  • 将画好的canvas填充进页面

无论是排序优先级的计算还是从css到canvas的转换,毫无疑问都是些巨麻烦的事,尤其是放在真实的业务场景里,DOM模版中往往会包含复杂的样式与排版,html2canvas 足足用了20多个js来实现这层转换,复杂成度可见一斑。

代码实现过程:

1.在vue项目中安装依赖

npm install html2canvas

2.引入

import html2canvas from “html2canvas”;

3.使用并生成

//生成base64
getImg(cab){
    
    
      setTimeout(function () {
    
    
       html2canvas(document.getElementById("superMap"), {
    
     //superMap整个页面的节点
          backgroundColor: null, //画出来的图片有白色的边框,不要可设置背景为透明色(null)
          useCORS: true, //支持图片跨域
          scale: 1, //设置放大的倍数
        }).then((canvas) => {
    
    
          //截图用img元素承装,显示在页面的上
          let img = new Image();
          img.src = canvas.toDataURL("image/jpg"); // toDataURL :图片格式转成 base64
          this.imgUrl =img.src
          //回传子组件base64
          cab(this.imgUrl)
          //如果你需要下载截图,可以使用a标签进行下载
          // let a = document.createElement("a");
          // a.href = canvas.toDataURL("image/jpeg");
          // a.download = "test";
          // a.click();
      });
     }, 500);
   }

//根据业务需求,将base64赋值给img标签,相关的逻辑要依据情况自行处理
  <img width="100%" :src="imgUrl" alt="" />
    
//上传也是根据业务需求来定
    sendUrl () {
    
    
      // 如果图片需要formData格式,就自己组装一下,主要看后台需要什么参数
      const formData = new FormData()
      formData.append('base64', this.company.fileUrl)
      formData.append('userId', 123)
      formData.append('pathName', 'aaa')
      this.$ajax({
    
    
        url: apiPath.common.uploadBase,
        method: 'post',
        data: formData
      }).then(res => {
    
    
        if (res.code && res.data) {
    
    
          //to do
        }
      })
    }

优点:

1.稳定性:但自14年起便已经被Twitter 等用在了生产环境,所以虽然有诸多限制,稳定性应该还是保障的。

2.支持异步操作:html2canvas代码中大量使用了Promise

3.灵活性较高,环境依赖上也只需要确保浏览器支持canvas就可以了

缺点:

1.复杂,过程复杂

2.慢,本地运行大约4秒左右。原因自然是因为大量的计算与递归调用,这是无可避免的

3.两个限制:

  • 无法跨域跨域资源,现在目前看,是有跨域设置的,有待验证。

    解决方案1: 图片服务端设置允许跨域(返回 CORS 头),html2canvas设置useCORS:true,//(图片跨域相关)allowTaint:false,//允许跨域(图片跨域相关);对于微信头像,可通过配置服务端代理转发(forward)实现

    解决方案2:将文件读入到blob文件对象,然后用URL.createObjectURL()方法转换成img src可用的地址,然后再转canvas

    getImage:function (url,imgId) {
          
          
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.responseType = 'blob';
        xhr.onload = function () {
          
          
            if (this.status == 200) {
          
          
                document.getElementById(imgId).src =  URL.createObjectURL(this.response);
            }
        };
        xhr.send();
    },
    
  • 无法渲染iframe,flash等内容,但目前支持svg,但是在实践中,也是渲染不了svg格式的图片,需要曲线救国

  • 图片传输的数据格式,如果后台无法支持base64,前端要做处理:1.通过new File()将base64转换成file文件,此方式需考虑浏览器兼容问题。2.先将base64转换成blob,再将blob转换成file文件,此方法不存在浏览器不兼容问题。https://blog.csdn.net/yin13037173186/article/details/83302628

二、s v g

首先,svg本来就是矢量图形;其次,svg是可以用xml描述的;再其次,用来描述svg的标签里有个 foreignObject标签,这个标签可以加载其它命名空间的xml(xhtml)文档。也就是说,如果使用svg的话,我们不再需要一点点的遍历,转换节点;不用再计算复杂的元素优先级,只需要一股脑的将要渲染的DOM扔进就好了,剩下的就交给浏览器去渲染。

让我们理一理思路:

  • 首先,我们要声明一个基础的svg模版,这个模版需要一些基础的描述信息,最重要的,它要有这对标签
  • 将要渲染的DOM模版模版嵌入foreignObject
  • 利用Blob构建svg图像
  • 取出URL,赋值给

优点:

1.渲染速度快,没有了复杂的计算和递归,速度大大提升

2.使用SVG截图可获取同域< iframe>内容进行渲染

缺点:

1.跨域资源无法加载

2.如lazyload等通过js加载的资源无法加载

3.内联或js操作background-image无法加载

4.引入了外部样式表、图片、而且还可能某些标签不符合xml规范(如缺少闭合标签等)

核心代码:

import Rasterizehtml from "rasterizehtml";
const btn = document.getElementById("save-btn");
btn.addEventListener("click", () => {
    // drawURL()加载的URL必须是同域名URL或支持跨域的URL
    // 下面的URL是随便写的,记得换成同域名URL或支持跨域的URL
    const url = "https://www.baidu.com";
    const canvas = document.createElement("canvas");
    const opts = {
        executeJs: true,
        height: screen.height,
        width: screen.width
    };
    Rasterizehtml.drawURL(url, canvas, opts).then(res => {
        const base64 = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(res.svg)));
        Render(base64, opts.width, opts.height, img => {
            document.body.appendChild(img);
            Download(base64, "screenshot.png");
        });
    }, err => alert("截图失败,请重新尝试"));
});

三、火狐浏览器的官方网站drawWindow()

https://zhuanlan.zhihu.com/p/149483404

这个方法和上面提到html2canvas不同之处在于,它不分析页面元素,它只针对区域,也就是说,它接受的参数是四个数字标志的区域,不论这个区域中什么地方,有没有页面内容。这种方法,本人没用过,搬运参考了大佬的方案,以下内容仅供参考。

void drawWindow(
  in nsIDOMWindow window,
  in float x, 
  in float y,
  in float w,
  in float h,
  in DOMString bgColor,
  in unsigned long flags [optional]
);

这个原生的JavaScript方法看起来非常的完美,但这个方法不能使用在普通网页中,因为火狐官方发现这个方法会引起有安全漏洞,在这个bug修复之前,只有具有“Chrome privileges”的代码才能使用这个drawWindow()函数。

虽然有很大的限制,但周折一下还是可以用的,在火狐addon插件中,main.js就是具有“Chrome privileges”的代码。我在网上发现了一段火狐插件SDK里自带代码样例:

var window = require('window/utils').getMostRecentBrowserWindow();
var tab = require('tabs/utils').getActiveTab(window);
var thumbnail = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
thumbnail.mozOpaque = true;
window = tab.linkedBrowser.contentWindow;
thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
var aspectRatio = 0.5625; // 16:9
thumbnail.height = Math.round(thumbnail.width * aspectRatio);
var ctx = thumbnail.getContext("2d");
var snippetWidth = window.innerWidth * .6;
var scale = thumbnail.width / snippetWidth;
ctx.scale(scale, scale);
ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, snippetWidth * aspectRatio, "rgb(255,255,255)");
// thumbnail now represents a thumbnail of the tab

这段代码写的非常清楚,只需要依据它做稍微的修改就能适应自己的需求。

猜你喜欢

转载自blog.csdn.net/weixin_41884808/article/details/118306564