解决 html2canvas 无法截到Svg标签内容

4,990 阅读3分钟

前言

image.png 携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

大家好,我是前端小张同学,接着上一次的demo所遇到的问题就是,项目需要根据一张大的流程图,生成一个小的缩略图,当时我........,这是什么需求,做不了,于是我经过自己的努力还是做出来了中间也遇到了很多问题,在这里跟大家分享一下,如果你觉得有用请点个 Star

需求:

项目希望根据大的流程图绘制小的缩略图,具体内容我们看下方大图。

大图

image.png

缩略图

image.png

思考,这应该怎么做......

刚拿到这个需求的时候我楞了一下,应该怎么做?我的第一想法 居然是.....Alt + A微信截图快捷键) ,我手动截一张图,然后保存,再然后...... 很明显这不现实,但是我们思路没错,截图,对,你没听过就是截图,我完成了这个需求。

接下来 (心机之蛙一直摸你肚子)

我利用了一个库截取了Dom的节点内容。

介绍一下这个库

什么是 html2canvs?

来源 : github ----> html2canvas

html2canvas 的作用就是允许让我们直接在用户浏览器上拍摄网页或其部分的“截图”。 它的屏幕截图是基于 DOM 的,因此可能不会 100% 精确到真实的表示,因为它不会生成实际的屏幕截图,而是基于页面上可用的信息构建屏幕截图。

html2canvas 可以用来做什么?

从上的面的介绍可以知道, html2canvas 的作用就是根据 DOM 生成对应的图片,所以一个比较常见的应用场景就是我们可以使用它在 H5 端生成分享图,或者生成缩略图等。

如何使用 html2canvas呢?

三部曲

下包 ---> 引入 ----> 使用

1:下包

npm i html2canvas
或
yarn add html2canvas

2:引入

import html2canvas from 'html2canvas'

3:使用(use)

在Vue中我们可以通过 给DOM绑定 ref来获取到当前DOM节点。如果你是原生JS, 请document.querySelector()

配置项请见 html2canvas官方文档:

html2canvas.hertzen.com/configurati…

// 获取DOM实例对象 你获取哪个DOM节点,截图的区域就是哪个元素的内容
   const el = this.$refs.efContainer;
   // 获取元素的宽
   const width = el.offsetWidth
   //获取元素的高
   const height = el.offsetHeight
   
   // html2canvas- 我这里接收 两个参数 
   
   // 参数1 : 传递需要截图的DOM元素 
   // 参数2 : 需要的配置对象,可以配置截图的宽高,跨域,缩放比例等等
   // 具体可见 :http://html2canvas.hertzen.com/configuration
   let canvas = html2canvas(el,{
   // 意思就是 如果你的资源是从服务器来的,可以尝试在服务端加载图像,我们设置为true
   useCORS:true, //官方:是否尝试使用CORS从服务器加载图像
   scale: 2, // 图像的缩放比例
   // 如果不知道简写是什么意思 请看 # 阮一峰js教程
   width, // 图像的宽度,因为我这里属性名和属性值一致可以简写
   height, // 来自于  const height = el.offsetHeight
   allowTaint:true, // allowTaint 允许绘制画布
   windowHeight:height,// 渲染Element时使用的窗口宽度
   
   // backgroundColor: "#fff"那你还可以设置颜色
   // 这样你已经完成了一个截图了
   })

接下来,你可以通过 canvas.toDataURL('images/jpg') 获取到一张base64位的图片进行展示

//toDataURL(生成图片格式)
const img = canvas.toDataURL('images/jpg')

问题

如果你出现了白屏 ,请考虑你的方法调用时机,确保DOM真正绘制完后在进行截取操作,你可以在点击某个按钮去执行该方法。例如 像我这样

image.png

image.png

如果有滚动条的DOM截图出现白屏 请参考 www.51sjk.com/b24b265777/

解决 无法截取Svg

相信根据上方的操作你应该可以生成一个base64的字符串了 放到 image src 属性身上可以进行展示了。

接下来 ,很明显看的出,我想截取这个 idefContainer DOM元素里的内容,很明显可以看的出该元素的子节点是有svg标签的。

image.png

原图

image.png

截取之后的

image.png

我们使用 html2canvas截取一个DOM内容是无法截取到SVG的内容的,这也是一个缺陷吧。如何解决呢

解决方案

解决思路

也是来自于github issues 里的大佬 github.com/niklasvh/ht…);

采取的是将Svg标签提取出来,绘制成进canvas行截取 展示的,同时相当于是 障眼法 ,我用 canvas 去代替你的 SVG这样就可以正常的截取了

怎么做呢?

你需要先 下载一个包

三部曲

下包 ---> 引入 ----> 使用

npm install canvg@1.5.3
import canvg from 'canvg'

思路:

1:准备一个获取Svg DOM元素的数组;

2:获取SVG DOM元素;

3: 循环遍历Svg DOM 数组,并且解决SVG 透明度问题;

4:去除元素的一个空白字符;

5:动态创建一个 canvas标签;

6: 设置 创建好的 canvas标签的宽高;

7:canvg() 使用这个方法 将 SVG 转换成 canvas;

8:设置 canvas的坐标;

9:添加到你想截图的DOM元素中;

10:截取完成后 将将绘制好的 canvas 删除避免影响后续操作

async getContainerImg () {
      const el = this.$refs.efContainer;
      //Svgdom数组
      let svgNodesToRemove = [];
      // 获取到所有的SVG 得到一个数组
      let svgElements = document.body.querySelectorAll('#efContainer svg');
      // 遍历这个数组
      svgElements.forEach(function(item) {
      let children = item.childNodes  
      // 处理 SVG子节点  children 透明度
      children.forEach(function(child) {
      //解决svg透明度问题
      child.style.opacity = 1;
      })          
     //去除空白字符
     let svg = item.outerHTML.trim();      
     // 创建一个 canvas DOM元素
     let canvas = document.createElement('canvas');
     //设置 canvas 元素的宽高 
     canvas.width = item.getBoundingClientRect().width;
     canvas.height = item.getBoundingClientRect().height;
     // 将 SVG转化 成 canvas
     canvg(canvas, svg); 
     //设置生成 canvas 元素的坐标  保证与原SVG坐标保持一致
     if (item.style.position) {
     canvas.style.position += item.style.position;
     canvas.style.left += item.style.left;
     canvas.style.top += item.style.top;
    }
   //添加到 你需要截图的DOM节点中
   item.parentNode.appendChild(canvas);
   // 删除这个元素 
   svgNodesToRemove.push(canvas)
   });
   
  const width = el.offsetWidth
  const height = el.offsetHeight
  let canvas = await html2canvas(el,{
      useCORS:true,
      scale: 2,
      width,
      height,
      allowTaint:true,
      // dpi: window.devicePixelRatio * 4,//设备像素比
      // windowWidth:document.body.scrollWidth,
      windowHeight:height,
      // x:0,
      // y:window.pageYOffset
      // backgroundColor: ""
      })
      this.thumbnail = canvas.toDataURL('images/jpg')
      // 截取完成后
      // 删除 canvas 元素
     document.querySelectorAll("#efContainer canvas").forEach(item => {
      item.remove()
      })
      },

那缩略图的话,相信你通过CSS就可以搞定它了。大概内容就是这样咯。

这样就可以完美解决 无法截取到SVG的问题啦,非常谢谢大家的支持,如果你觉的本文对你有帮助请点个👍,谢谢,如果有更好的思路,请在文章下方留言,谢谢。