JS:SVG转图片的完美方法

原创
2018/08/07 11:22
阅读数 13.1W

因为项目的原因,在web端svg需要转成图片进行输出.其中svg里面涉及到图片的跨域,字体显示,特殊标签。生成方式想当复杂。总结下来,分为前端生成,后调api后端生成。我标题能加上完美,是因为我尝试了几乎google 的各种实现方式。

需求

  • 含有svg的网页转图片,俗称截屏。
  • Chart图转图片,比如highchart
  • Svg在线设计网站生成图片

Demo

html

下面我们就生成这个svg的图片

       `
       <style>
           @font-face{
           font-family: huxiaobo-gdh;
           src: url(http://xxxx/huxiaobo-gdh.woff) format('woff');
       }
       </style>
          <div class="preview">
            <svg>
                <foreignObject>
                    <div class="font-family:huxiaobo-gdh"></div>
                </foreignObject>
            </svg>
        </div>`

科普一下image生成base64, img.crossOrigin = 'Anonymous';这句话加上可跨域。当然图片本身最好设置了跨域。

          let canvas = document.createElement('canvas');
                let img = new Image()
                img.crossOrigin = 'Anonymous';
                let ctx = canvas.getContext('2d');
                img.src = src;
                img.onload = function () {
                    canvas.width = img.width;
                    canvas.height = img.height;
                    ctx.drawImage(img, 0, 0);
                    let url =canvas.toDataURL();
                }
            }

纯前端实现方式

html2canvas

或许这个是前端生成方式中最火的一个,很多公司网站都用这个插件进行截图。一开始他是不支持svg的,在0.5版本之后加入了svg的支持功能。 0.5之前的版本需要使用借助第三方一下组件canvg 或者是 faricjs。 虽然他支持了但是效果并不理想,一些含有字体样式的svg,生成不出来正确的svg,例 如 https://jsfiddle.net/AndreLovichi/571t86u4/ 试过其他的插件方法,我知道我不能放弃html2canvas,终于试验中找到了解决办法。参考 https://jsfiddle.net/571t86u4/93/ 原理就是讲字体转成dataURI放到svg里面。

JS

      `
      const request = new XMLHttpRequest();
      request.open("get", './xxx/demo.woff');
      request.responseType = "blob";
      request.onloadend = () => { 
                let svgStyle = `
                    @font-face {
                    font-family: "${item.name}";
                    src: url(${reader.result}) format('woff');
                    }
                `
                // 插入到svg内
                let svg = `<svg>
                     <style>
                            ${svgStyle}
                      </style>
                      ....此处省略一万字
                </svg>`
      }

      html2canvas($('.preview')[0]).then(function(canvas) {
              let url = canvas.toDataURL();
      }`

缺点:假如svg含有多个字体。每个都转baseURI,老慢了。。并且在微信小程序上直接卡掉。吼吼吼。。。。。。

简单粗暴的方式

   `data:image/svg+xml;base64,'+ btoa(unescape(encodeURIComponent(${svg}))) `
   or
   `DOMURL.createObjectURL(${svg})`

然后利用canvas,渲染生成base64,事实证明不好使。含有特殊字体生成图片不显示。

其他插件

svg-to-image 字体不出现,适合简单需求

字体转svg

text-to-svg const textToSVG = TextToSVG.loadSync('/fonts/Noto-Sans.otf'); const svg = textToSVG.getSVG('hello'); 就像上面写的这样,需要load一下字体,把你想转的字体转成path,然后根据样式,再插入到svg里面,置于插入回到svg,最后生成你想要的格式和结果,需要你慢慢研究,相当复杂的计算。然后配合前端html2canvas 等任何插件或者后端都可以实现svg转png了。

缺点:不简单,虽然我们也在使用这种方式,需要花点时间把生成好的path放回到svg里面,到时候你可以用snap类似的插件

后端NODEJS实现方式

node端整体分为phantomjschromium headless这两大分支。 大家都知道phantomjs 已经没人维护了,性能比chromium headless差个等级,我个人建议用后者。

phantomjs 类插件

svg2png https://github.com/domenic/svg2png

缺点:无法使用独立字体,可以配合上面的text-to-svg使用。

phantom https://github.com/amir20/phantomjs-node

你妹的起个名字就叫phantom,真的会让人误解。他的优点就是node封装的phantom,可以直接调用,创造属于你的DIY let { svg, width, height } = req.body

        let html = `
            <!DOCTYPE HTML>
            <html>
                <head>
                    <meta charset="utf-8">
                    <title>Shell</title>
                    <link rel="stylesheet" href="./font.css">
                </head>
                <style>
                    body,html{
                        background: #333333;
                        margin: 0;
                        width: 100%;
                        height:100%;
                        padding: 0px;
                        font-weight: normal;
                        -webkit-touch-callout: none;
                        -webkit-user-select: none;
                    }
                    #Viewport {
                        position:relative;
                        width:100%;
                        height:100%;
                    }
                </style>
                <body>
                    <div id="Viewport" style="display:inline-block;">
                        ${svg}
                    </div>
                </body>
            </html>
        `

        const instance = await phantom.create()
        const page = await instance.createPage()

        const fileName = uuid() + '.html'
        await writeFile(fileName, html, 'utf8')

        const status = await page.open(fileName)

        let viewportSize = { width: Number(width), height: Number(height) }

         await page.evaluate(function () {
         var w = document.querySelector(".ks-canvas-container").getAttribute('width');
         var h = document.querySelector(".ks-canvas-container").getAttribute('height');
         return {
             width: Number(w),
             height: Number(h)
         };
         }).then(function (svgInfo) {
             viewportSize = svgInfo;
         });*/

        await page
            .property('viewportSize', viewportSize)
            .then(function() {})

        await page
            .property('clipRect', { x: 0, y: 0, ...viewportSize })
            .then(function() {})
        const base64Data = await page.renderBase64('PNG')

chromium headless 类插件

convert-svg-to-png https://github.com/NotNinja/convert-svg/tree/master/packages/convert-svg-to-png

内部实际用的puppeteer,目前稍微有个小bug,我已经联系了作者。不过问题不大,我目前用着最好用的。

       // 将这段style插入到svg里面,不需要转baseURI 
	   // <svg><style>@fant-face {xxxxxxxxxx} ……….. …… 
	   let svgStyle = `
			@font-face { font-family: "demo";
			src: url(xxxx/www/demo.woff) format('woff');
		}`
		// 请求
		fetch('http://XXXXXX/convert',
			{
				method: 'POST',
				headers: {
				'Accept-Charset': 'utf-8', 
				'Accept': 'application/json;charset=utf-8',
				'Content-Type': 'application/json;charset=utf-8' 
			},
				body:JSON.stringify({
					svg: $('.preview').html()
				})
			}).then((response) => { 
				return response.json();  
			}) .then(data => {

            });
    // node 端
    app.post('/convert', async(req, res) => {
        const png = await convert(req.body.svg);
        res.set('Content-Type', 'image/png');
        res.send(png);
    });

其他的插件我就不介绍了svg-to-img。目前试着不大好用,主要还是字体生成的原因。

总结

svg转png真是个好傻的活,花费了很长时间,可能会有人说用java,那你才傻,java那个包旧的想拉屎。 建议纯前端生成的话用html2canvas,时间充裕讲究性能,用text-to-svg转一下。 后端用convert-svg-to-png。 我个人更建议后端生成,缺点也是很显著的,占用带宽。

展开阅读全文
加载中

作者的其它热门文章

打赏
3
3 收藏
分享
打赏
8 评论
3 收藏
3
分享
返回顶部
顶部