Because in e-commerce companies, we often do some H5 activities and need to realize the function of sharing posters. The poster will have some customized information, such as scores and comments, combined with the background image to make a picture for sharing or downloading. Because each person’s word count is different, let content draw in the right place, need some “means”.

rendering

Scheme 1: Pure canvas drawing

We are most familiar with using canvas directly to synthesize this poster. The general steps are as follows:

  1. Buckle the immutable background image, see below.
  2. Use measureText to get the width of the content to be drawn, then calculate where it should be drawn and draw it to the canvas using fillText
  3. After drawing all the content, use the toDataURL or toBlob interface to get the image content, upload it to the server or pass it to the interface.
background

The core code is as follows

async function drawPoster(info){
	/ /... Some prep code

	// 1. Get the length of the converted string. Note that you need to set the font first. In addition, the width of "yuan Cash" we think is the same as it
	ctx.font = '42px "Kaiti SC"';
	let textWidth = ctx.measureText('converted').width;
	let spaceWidth = 40;
	// 2. Get the width of the amount
	ctx.font = '140px "Kaiti SC"';
	let moneyWidth = ctx.measureText(info.money).width;
	// 3. Total width of copy. Two paragraphs of plain text, the space between the two plain text and the amount, plus the width of the amount
	let totalWidth = textWidth*2+spaceWidth*2+moneyWidth;
	// 4. Draw the first and second paragraphs of common text
	ctx.textAlign = 'start';
	ctx.textBaseline = 'alphabetic';
	ctx.font = '42px "Kaiti SC"';
	ctx.fillText('converted', (posterWidth-totalWidth)/2.390);
	ctx.fillText('Dollar cash', posterWidth-(posterWidth-totalWidth)/2-textWidth, 390);
	// 5. Draw amount
	ctx.font = '140px "Kaiti SC"';
	ctx.fillStyle='#a70322';
	ctx.fillText(info.money, (posterWidth-moneyWidth)/2.400);

	/ /... Other processing code
}
Copy the code

Demo address view the complete source code

The code snippet above draws the copy for “converted $1.50 cash”. To center the text, measure the length of each piece of text with measureText, and then carefully calculate where they should be rendered based on their total width. There’s less text in the example, if there’s more; Or when the style is complex, using canvas to draw directly will make the code become bloated. Also, it’s not easy to debug, because you can’t modify it in the developer tools as you see it.

Let’s hack the layout tool to the browser.

Plan 2: Use the browser layout

Browsers can easily use CSS to control the layout of content, and there are ways to center content. So we’ll just leave it to it, and then we’ll get the position of each text, and we’ll just draw it on the canvas. In the example above, we put the content in HTML and CSS first.

<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;
}
/* See source */ for other styles
</style>
<div class="poster">
	<div class="uname"></div>
	<div class="money"></div>
</div>
Copy the code

Then fill the content with JS.

let moneyHtml = 'converted'.split(' ').map(word= >`<span>${word}</span>`).join(' ');
moneyHtml += info.money.split(' ').map(word= >`<strong>${word}</strong>`).join(' ');
moneyHtml += 'Dollar cash'.split(' ').map(word= >`<span>${word}</span>`).join(' ');
document.querySelector('.money').innerHTML = moneyHtml;
Copy the code

Note that the text is wrapped separately with the SPAN and strong tags, so that we can use JS to get the position of each character separately and render it directly.

The next step is to “copy” the contents of the DOM onto the canvas.

// Draw the amount
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);
}
Copy the code

It takes the DOM elements of each word and draws them one by one to the canvas.

This scheme is particularly suitable for large amounts of text, especially to solve the problem of line breaking. Because the text in canvas does not wrap itself, it is too troublesome to calculate where to wrap itself.

Demo address view the complete source code

Scheme 3: SVG drawing

Since SVG is an image, let’s use its structuring and CSS for easy layout, and just use it as a drawImage.

No, because canvas’s drawImage interface only supports CSSImageValue, HTMLImageElement, SVGImageElement, HTMLVideoElement, HTMLCanvasElement, ImageBitmap or post screen anvas. There is an SVGImageElement in there, but it is not SVG itself, it is used in SVGElements.

However,Support base64, SVG content just can be converted to Base64, suddenly you can save the country curve.

Start with SVG because SVG supports DOM, CSS, and even JS, which makes it easy to use.

<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">Has change</tspan>
		<tspan dx="40" class="money-count">1.50</tspan>
		<tspan dx="40" dominant-baseline="ideographic">Yuan in cash</tspan>
	</text>
</svg>
Copy the code

The annotation out is the poster background, which can be opened when debugging, you can directly see the finished product in the browser. Note that the above code directly writes the name and amount in, easy to understand, formal environment requires JS to write in.

The code to convert SVG content to Base64 is as follows:

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;
	});
}
Copy the code

It creates an Image element, and then converts the SVG content into Base64 code to display it as an Image.

Then draw it directly on the canvas.

let svgImg = await loadImg(document.querySelector('#poster'));
ctx.drawImage(svgImg, 0.0);
Copy the code

The advantage of this approach is the simplicity of using SVG for layout and debugging, and then rendering code.

Of course, compared with the second scheme, there are also some disadvantages:

  1. Wrapping requires extra processing;
  2. SVG inline images, font files, etc., these external files, are not loaded, need to be converted to Base64 directly write SVG

conclusion

The above three schemes have their own advantages and disadvantages. SVG is simple and easy to code, which makes it “elegant”. The DOM scheme allows for the ability to use a browser’s layout capability with no computation at all, which is a good solution for extremely large amounts of text or complex content; Pure Canvas scheme has the best compatibility and does not require extra DOM blending, which is “the cleanest”.