由於在電商公司,常常作一些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攙和,「最乾淨」。