一行代码解决svg保存成png文件

2019-03-11  本文已影响0人  渴望认清自己

先说下我的功能需求,大家看看和大家的是否相同,能否借鉴。
下面是我的整个的思路过程,如果想直接找解决方案,直接看方案三。

功能需求:

添加一个按钮,点击按钮时,将拓扑图保存为图片文件并导出。

就是将在svg画布上画的图,保存成图片并导出。

问题描述:

在svg画布上画的拓扑图,每个节点使用的是图片,将整个svg导出时,导出的图片文件上面不显示节点图片。如图1-1、1-2:

图1-1 svg画布画好的拓扑图
图1-2 导出的图片

我svg中绘制过程中,引入图片的代码如图1-3:


图1-3 svg节点引入图片

图片放在static下面,如图1-4:

图1-4 引入的图片存放位置

方案思路:

方案一:无须引入插件

首先是制造一个button,用的element的框架。
<el-button size = 'mini' type="primary" @click="saveSvgToPng" v-loading.fullscreen.lock="fullscreenLoading">导出图片</el-button>

然后写这个点击事件响应函数:

saveSvgToPng: function () {
        //采用直接转成canvas的方式
        let width = 1000;
        let height = 666;
        var serializer = new XMLSerializer();
        var source = '<?xml version="1.0" standalone="no"?>\r\n'+serializer.serializeToString(svg.node());
        var image = new Image();
        image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
        var canvas = document.createElement("canvas");
        canvas.width = width;
        canvas.height = height;
        var context = canvas.getContext("2d")
        image.onload = function(){
          context.drawImage(image, 0, 0);

          var a = document.createElement("a");
          a.download = "aaaa.png";
          a.href = canvas.toDataURL('image/png');
          a.click();
        }
      },

导出内容如图1-2,未显示网元节点图片。未搞清楚原因,查了一些资料,好像是因为canvas画布被污染了,所以无法使用toDataURL这种方法了。而canvas画布被污染的原因好像是因为svg中网元节点图片跨域了,我现在有这个疑问:在static下面的图片,存在跨域问题吗,因为我导出的时候,并没有报出跨域的错误。

然后我又尝试了方案二:

方案二:使用插件html2canvas

按照官网步骤,引入html2canvas插件,修改html结构,将svg放入一个div内,并给div一个ref标签,便于引用,如图1-5.

图1-5 html结构

然后修改点击响应函数,代码如下:

函数1:

dataURLToBlob: function (dataurl) {
  var arr = dataurl.split(',');
  var mime = arr[0].match(/:(.*?);/)[1];
  var bstr = atob(arr[1]);
  var n = bstr.length;
  var u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], {type: mime})
},

单击响应函数2:

catchSvgToPng: function () {
        const _this = this
        let a = document.createElement('a');
        setTimeout(() => {
          // 此处用于解决截图不清晰问题,将生成的canvas放大,然后再填充到原有的容器中就会清晰
          const width = 1000;
          const height = 400;
          const canvas2 = document.createElement('canvas');
          const scale = 2;
          canvas2.width = width * scale;
          canvas2.height = height * scale;
          const context1 = canvas2.getContext('2d')
          if(context1) {
            context1.scale(scale, scale);
          }
          const opts = {
            scale,
            canvas: canvas2,
            // logging: true, //日志开关,便于查看html2canvas的内部执行流程
            width,
            height,
            // 【重要】开启跨域配置
            useCORS: true
          };
          html2canvas(_this.$refs.imageWrapper,opts).then((canvas) => {
            const context = canvas2.getContext('2d');
            if(context) {
              context.scale(2,2);
              context.mozImageSmoothingEnabled = false;
              context.webkitImageSmoothingEnabled = false;
              context.imageSmoothingEnabled = false;
            }
            // canvas转换成url,然后利用a标签的download属性,直接下载,绕过上传服务器再下载
//            _this.screenUrl = canvas.toDataURL()

//            var imgBlob = canvas.toDataURL('image/jpeg', 1.0); //将图片转为base64
//            imgBlob = imgBlob.toString().substring(imgBlob.indexOf(",") + 1);//截取base64以便上传
            let blob = _this.dataURLToBlob(canvas.toDataURL('image/jpeg', 1.0));
            a.setAttribute("href", URL.createObjectURL(blob));
            a.setAttribute("download", "xxxxxx.png");
            document.body.appendChild(a);
            a.click();
            URL.revokeObjectURL(blob);
            document.body.removeChild(a);
          });
        },1000)
      },

导出的图片如图1-6所示:


图1-6 html2canvas导出的图片

导出的图片依然无网元节点图片,已经按照网上查到的资料,对污染的canvas进行处理,不让canvas被污染,依然没有效果。现在不清楚是什么原因导致的导出的图片无节点图片,因此也不知如何解决。
这个问题到这里已经困扰了我将近一周之久,查了好多资料,试了又试,像一支无头苍蝇一样撞来撞去,毫无头绪。因为之前科学上网的软件到期了,一直没有续费,所以之前的查资料都是在百度上进行。
在这里!!!强烈建议大家科学上网!!!当我把软件续费以后,用谷歌重新试着查询之前的资料,发现之前尝试的一种插件,但苦于没有找到例子而不知道怎么使用,就一直未动。当我谷歌出使用流程以后,我抱着试一试的心态重新试了一下,这就是我的方案三。

方案二:使用插件saveSvgAsPng
废话不多说,上个官网网址,写的很清楚。
github:https://github.com/exupero/saveSvgAsPng
翻译版本:https://www.helplib.com/GitHub/article_110596
按照这上面的提示,一步步操作。
1.用npm把插件装上
npm install save-svg-as-png
2.在需要使用的vue文件中把js文件import进来
import saveSvg from 'save-svg-as-png';
3.对之前的按钮点击响应函数进行编写

saveSvgToPng: function () {
        //调研使用saveSvgAsPng 的方式
        saveSvg.saveSvgAsPng(document.getElementById("topologySvg"), "diagram.png",{height:800});
      },

官网上面给的例子直接使用的
saveSvgAsPng(document.getElementById("topologySvg"), "diagram.png")
但是这样的话,在我的vue框架下,是显示这个方法无效,所以需要使用前面import的文件名来进行调用。
我们调转这个函数,看下怎么回事。

out$.saveSvgAsPng = (el, name, options) => {
    requireDomNode(el);
    out$.svgAsPngUri(el, options || {}, uri => out$.download(name, uri));
  };

这个是引入插件中该方法的位置,很显然该方法需要三个参数,操作的元素、保存的文件名称以及一些参数。至于具体可以设置哪些参数来控制导出的png图片,官网给了一个清单,写的很清楚。比如我们想设置导出图片的宽度和高度为我们想要的值,就可以这么写:

saveSvg.saveSvgAsPng(document.getElementById("topologySvg"), "diagram.png",{height:800,width:1400});

至此,该功能就实现了,一行代码可以搞定的功能琢磨了一周多,以后一定要科学上网!!!百度真得往后稍稍。

上一篇下一篇

猜你喜欢

热点阅读