因为项目的原因,在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端整体分为phantomjs和chromium 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。 我个人更建议后端生成,缺点也是很显著的,占用带宽。