由于在电商公司,常常作一些h5的活动,须要实现分享海报功能。海报上会有一些我的定制信息,好比得分、评语等,和背景图合成在一块儿,作成一张图用于分享或下载。由于每一个人字数不同,让内容绘制在合适的位置,就须要一些“手段”。javascript
直接用canvas来合成这张海报,是咱们最熟悉的方案。大体步骤以下:css
核心代码以下html
async function drawPoster(info){
// ... 一些准备代码
// 1. 得到“已兑换”字符串的长度。注意,须要先设置字体。另外,“元现金”的宽度咱们认为和它同样
ctx.font = '42px "Kaiti SC"';
let textWidth = ctx.measureText('已兑换').width;
let spaceWidth = 40;
// 2. 如法得到金额的宽度
ctx.font = '140px "Kaiti SC"';
let moneyWidth = ctx.measureText(info.money).width;
// 3. 文案的总宽度。两段普通文案,两个普通文案和金额之间的空白,加金额的宽度
let totalWidth = textWidth*2+spaceWidth*2+moneyWidth;
// 4. 绘制先后两段普通文案
ctx.textAlign = 'start';
ctx.textBaseline = 'alphabetic';
ctx.font = '42px "Kaiti SC"';
ctx.fillText('已兑换', (posterWidth-totalWidth)/2, 390);
ctx.fillText('元现金', posterWidth-(posterWidth-totalWidth)/2-textWidth, 390);
// 5. 绘制金额
ctx.font = '140px "Kaiti SC"';
ctx.fillStyle='#a70322';
ctx.fillText(info.money, (posterWidth-moneyWidth)/2, 400);
// ... 其它处理代码
}
复制代码
上面代码片段绘制了“已兑换1.50元现金”的文案。为了让这段文字居中,就须要用measureText得到每一段文本的长度,而后根据总宽度,当心计算它们应该的渲染位置。例子中的文字比较少,若是多了;或者样式复杂的时候,使用canvas直接绘制,会让代码变得很臃肿。并且,不方便调试,由于不能直接在开发者工具里修改即所见。git
下面用一种“hack”方式,把布局工具交给浏览器。github
浏览器里能够方便的用css控制内容的布局,对于居中咱们有的是办法。那咱们索性把这个任务交给它,而后咱们得到每一个文字的位置,直接绘制在画布中就好了。好比上例中,咱们先用html和css把内容放好。canvas
<style> .poster{ position: absolute; width: 721px; height: 920px; left: -10000px; border: 1px solid #000; font-family: "Kaiti SC"; text-align: center; } .uname{ font-size: 36px; padding-top:150px; } /* 其它样式内容见源码 */ </style>
<div class="poster">
<div class="uname"></div>
<div class="money"></div>
</div>
复制代码
而后使用js填充内容。浏览器
let moneyHtml = '已兑换'.split('').map(word=>`<span>${word}</span>`).join('');
moneyHtml += info.money.split('').map(word=>`<strong>${word}</strong>`).join('');
moneyHtml += '元现金'.split('').map(word=>`<span>${word}</span>`).join('');
document.querySelector('.money').innerHTML = moneyHtml;
复制代码
注意,上面把文本都用span
和strong
标签分开包裹起来,目的是方便接下来用js单独得到每一个字符的位置,直接渲染。服务器
而后就是把dom中的内容,“复制”到canvas上。markdown
//绘制金额
ctx.font = '42px "Kaiti SC"';
for(let word of document.querySelectorAll('.money span')){
let rect = word.getBoundingClientRect();
ctx.fillText(word.innerHTML, rect.left-left, rect.top-top);
}
ctx.font = '140px "Kaiti SC"';
ctx.fillStyle='#a70322';
for(let word of document.querySelectorAll('.money strong')){
let rect = word.getBoundingClientRect();
ctx.fillText(word.innerHTML, rect.left-left, rect.top-top);
}
复制代码
它获取每一个字的dom元素,而后挨个儿绘制到canvas上。
这种方案特别适合大量文字的状况,尤为是能够方便解决换行问题。由于canvas里的文本不会自动换行,要本身算在哪里换行,太麻烦了。
既然svg就是图片,让使用它的结构化和css来方便布局,而后直接把它当图drawImage行不行?
不行,由于canvas的drawImage接口中,只支持CSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap或者OffscreenCanvas。这里面虽然有SVGImageElement,但它不是svg自己,只是svg里使用元素。
可是,支持base64,svg的内容正好能够转化为base64,一会儿就能够曲线救国了。
先用svg布局海报内容,由于svg即支持dom,也支持css,甚至js,用起来实在太方便。
<svg id="poster" viewBox="0 0 721 920">
<style type="text/css"> .uname{ dominant-baseline: middle; text-anchor: middle; fill: #282521; font: 36px 'Kaiti SC'; } .money{ text-anchor: middle; fill: #282521; font: 42px 'Kaiti SC'; } .money-count{ font-size: 140px; fill: rgb(167, 3, 34); } </style>
<!-- <image x="0" y="0" width="721" height="920" href="https://inagora.github.io/svg-guide/res/poster-bg.jpg" /> -->
<text x="360" y="165" class="uname">poker</text>
<text x="360" y="400" class="money">
<tspan dominant-baseline="ideographic">已兑换</tspan>
<tspan dx="40" class="money-count">1.50</tspan>
<tspan dx="40" dominant-baseline="ideographic">元现金</tspan>
</text>
</svg>
复制代码
其中注释掉的是海报背景图,在调试时能够打开,就能够直接在浏览器当作品效果了。注意,上面代码里把名字和金额直接写进去了,方便理解,正式环境中须要js写进去。
把svg内容转化成base64代码以下:
function loadImg(url){
return new Promise((resolve) => {
let img = new Image();
img.setAttribute('crossOrigin', 'Anonymous');
img.onload = function () {
resolve(this);
};
if(url instanceof window.SVGSVGElement){
var xml = new XMLSerializer().serializeToString(document.querySelector('#poster'));
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(xml)));
} else
img.src = url;
});
}
复制代码
它建立了一个Image元素,而后把svg的内容转成base64代码,用图片展现出来。
而后直接把这个图绘制在canvas上就好了。
let svgImg = await loadImg(document.querySelector('#poster'));
ctx.drawImage(svgImg, 0, 0);
复制代码
这种方案的好处是即便用svg方便的布局和调试,而后渲染的代码又很简单。
固然,相比于第二种方案,也有一些缺点:
上面三种方案,各有利弊,svg简单、代码也行,很“优雅”;dom方案能够完成使用浏览器的布局能力,彻底不须要计算,对于特别多文案或内容很复杂的时候,这个方案不错;纯canvas方案的兼容性最好,也不须要额外的dom搀和,“最干净”。