解决 html2canvas 无法截到iconfont中Svg+use标签内容

1,208 阅读3分钟

需求:截取页面某一区域生成图片

解决方法: 使用#  html2canvas,具体实现方式网上有很多就不一一叙述了

遇到的问题

区域内的svg无法正常生成图片;网络上很多文章介绍可以使用canvg;但自己观察后发现,页面中使用antv的svg图标可以正常绘制,但使用阿里妈妈矢量图标整理下载的svg图标集无法正常绘制,说明最新版本的 html2canvas是可以正常绘制svg图标的,但具体问题出现在哪里呢? 各种搜索过后发现主要处在svg中的use标签上,因为 <use> 标签引用的是在 HTML 文档之外的符号定义,而 html2canvas 在转换时无法正确处理这些引用。

可以正常转成图片的svg

image.png 不能正常转成图片的svg

image.png

解决方案找到三种:

1.使用canvg手动将 SVG 渲染到 canvas 上,然后再使用 html2canvas。

结果失败

2.将 SVG 符号转换为 Base64 编码,然后在 html2canvas 转换时使用这个编码。

结果生成的Base64为空白图片,失败

3.如何将带use的SVG转换为普通的SVG元素(将svg+use变为svg+path)

转化思路: 找到所有需要转化的use标签=》通过use标签的xlink:href的名称找到原始svg的相关的path内容=》将use中的内容替换成path的内容;

**下图为原始path内容**

image.png

具体代码如下:

   // 获取SVG中所有的use元素 
   var useElements = document.querySelectorAll('use'); 
   let arr = [...useElements]
   // 遍历每个use元素 
   arr.forEach(function(useElement) { 
       // 获取use元素引用的ID 
       var href = useElement.getAttribute('href'); 
       if (href && href.startsWith('#')) { 
           var id = href.substring(1); 
           // 查找对应的实际元素 
           var referencedElement = document.getElementById(id); 
           // 克隆实际元素并替换use元素 
           var clonedElement = referencedElement.cloneNode(true); 
           useElement.parentNode.replaceChild(clonedElement, useElement); 
       } 
   });

转换成功发现,依旧没有图标生成,翻看dom样式发现是因为原始的path的值与当前设置svg的大小比例不一致,原始的path没有后期设置大小,普遍偏大,所以下一步任务将原始的path的值按照当前svg的比例等比缩小

        // 查找对应的实际元素svg里面的path集合
        var paths  = document.getElementById(id).querySelectorAll("path");
        // 获取SVG的实际宽度和高度
        var svgW = useElement.parentNode.style.width;
        var svgWidth = useElement.parentNode.width
        var svgHeight = useElement.parentNode.height
        // 计算所有path的包围框
        var totalBBox = { x: Infinity, y: Infinity, width: 0, height: 0 };
        paths.forEach(function(path) {
            //获取每个path的位置信息
            var bbox = path.getBBox();
            totalBBox.x = Math.min(totalBBox.x, bbox.x);
            totalBBox.y = Math.min(totalBBox.y, bbox.y);
            totalBBox.width = Math.max(totalBBox.width, bbox.x + bbox.width);
            totalBBox.height = Math.max(totalBBox.height, bbox.y + bbox.height);
        });
        // 计算缩放比例
        var scaleX = svgWidth / totalBBox.width;
        var scaleY = svgHeight / totalBBox.height;
        var scale = Math.min(scaleX, scaleY);
        // 平移path使其居中
        var translateX = (svgWidth - totalBBox.width * scale) / 2 - totalBBox.x * scale;
        var translateY = (svgHeight - totalBBox.height * scale) / 2 - totalBBox.y * scale;
        // 将原始path转化为等比例的普通svg
        paths.forEach((path) => {
         var bbox = path.getBBox(); 
         var transform = 'scale(' + scale + ') translate(' + translateX + ',' + translateY + ')'; 
         path.setAttribute('transform', transform);
        });

结果:成功

最终代码如下

//将页面指定节点内容转为图片
function canvasToPicture(dom,useLabel) {
  //将svg+use转换为svg+path
  toSimpleSvg('svg')
  // 转成图片,生成图片地址 
  html2canvas(dom, { useCORS: true, allowTaint: true }).then((canvas) => {
      //将canvas通过toDataURL转成图片url
      let imgURL = canvas.toDataURL("image/png");
      return imgURL
  });
}
//将svg+use转换为svg+path
function toSimpleSvg(useLabel){
    // 获取所有iconfont的svg的use标签
    var useElements = document.querySelectorAll(useLabel);
    // 遍历每个svg中的use元素
    let arr = [...useElements]
    arr.forEach((useElement)=> {
      // 获取use元素引用的ID
      var href = useElement.getAttribute('xlink:href');
      if (href && href.startsWith('#')) {
        var id = href.substring(1);
        // 查找对应的实际元素svg里面的path集合
        var paths  = document.getElementById(id).querySelectorAll("path");
        // 获取SVG的实际宽度和高度
        var svgW = useElement.parentNode.style.width;
        var svgWidth = useElement.parentNode.width
        var svgHeight = useElement.parentNode.height
        // 计算所有path的包围框
        var totalBBox = { x: Infinity, y: Infinity, width: 0, height: 0 };
        paths.forEach(function(path) {
            //获取每个path的位置信息
            var bbox = path.getBBox();
            totalBBox.x = Math.min(totalBBox.x, bbox.x);
            totalBBox.y = Math.min(totalBBox.y, bbox.y);
            totalBBox.width = Math.max(totalBBox.width, bbox.x + bbox.width);
            totalBBox.height = Math.max(totalBBox.height, bbox.y + bbox.height);
        });
        // 计算缩放比例
        var scaleX = svgWidth / totalBBox.width;
        var scaleY = svgHeight / totalBBox.height;
        var scale = Math.min(scaleX, scaleY);
        // 平移path使其居中
        var translateX = (svgWidth - totalBBox.width * scale) / 2 - totalBBox.x * scale;
        var translateY = (svgHeight - totalBBox.height * scale) / 2 - totalBBox.y * scale;
        // 将原始path转化为等比例的普通svg
        paths.forEach((path) => {
            // 应用缩放和平移变换到每个path
            var transform = 'scale(' + scale + ') translate(' + translateX + ',' + translateY + ')';
            // 保存原始数据
            var clonedElement = path.cloneNode(true);
            // 缩放paht
            clonedElement.setAttribute('transform', transform);
            // 将path 放入到svg中转化为普通的svg
            useElement.parentNode.appendChild(clonedElement)
        });
      }
    });
}