Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Jared Rypka-Hauer
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Jared Rypka-Hauer

Canvas "alphabetic" textBaseline Is Consistent Across Browsers

By
Published in ,

Earlier this week, I took a look at rendering text to <canvas> using an adjusted X,Y offset for cross-browser consistency. In that demo, I was using a textBaseline of top, which is inherently different from browser to browser. After I posted that, Jan Sedivy - creator of InVision Freehand - told me that while top may be inconsistent, a textBaseline of alphabetic is rendered perfectly across all browsers. And, in fact, also matches the baseline rendering of the browser's native text (outside of Canvas). As such, I wanted to perform a fast-follow demo to look at how the alphabetic baseline renders in Chrome, Firefox, and Safari.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

Just as with my previous demo, I'm going to first render a grid of lines to the Canvas element so that we can clearly see where text is being rendered. Then, I'm going to render sample text at various pixel-sizes so that we can see the relationship between the font-sizes and the rendering:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<title>
		Canvas "alphabetic" textBaseline Is Consistent Across Browsers
	</title>
	<link rel="preconnect" href="https://fonts.googleapis.com" />
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap" />
	<style type="text/css">
		body {
			font-family: "Roboto", sans-serif ;
		}
		canvas {
			box-shadow: 0px 0px 0px 2px #000000 ;
		}
	</style>
</head>
<body>

	<h1>
		Canvas "alphabetic" textBaseline Is Consistent Across Browsers
	</h1>

	<canvas id="demo" width="400" height="400"></canvas>

	<script type="text/javascript">

		var canvas = document.querySelector( "#demo" );
		var canvasWidth = 400;
		var canvasHeight = 400;
		var context = canvas.getContext( "2d" );

		// We have to give the FONT time to load so that we can use it on the canvas.
		window.addEventListener(
			"load",
			() => {

				drawGridLines();
				drawText( "Nobody puts baby in a corner!" );

			}
		);

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		/**
		* I draw the sample text to canvas at various sizes.
		*/
		function drawText( textValue ) {

			var pairings = [
				{ fontSize: 10, y: 50 },
				{ fontSize: 20, y: 100 },
				{ fontSize: 30, y: 150 },
				{ fontSize: 40, y: 200 },
				{ fontSize: 50, y: 250 },
				{ fontSize: 60, y: 300 },
				{ fontSize: 70, y: 350 }
			];

			context.fillStyle = "#000000";
			context.textBaseline = "alphabetic";

			for ( var pairing of pairings ) {

				context.font = `500 ${ pairing.fontSize }px Roboto`;
				context.fillText( textValue, 50, pairing.y );

			}

		}


		/**
		* I draw the horizontal and vertical grid lines on the canvas so that we can more
		* easily see where the text is aligned on different browsers.
		*/
		function drawGridLines() {

			var step = 10;
			var jump = 50;

			context.lineWidth = 1;
			context.strokeStyle = "#cccccc";

			// Draw GREY horizontal grid lines.
			for ( var i = step ; i < canvasHeight ; i += step ) {

				context.beginPath();
				context.moveTo( 0, ( i - 0.5 ) );
				context.lineTo( canvasWidth, ( i - 0.5 ) );
				context.stroke();

			}
			// Draw GREY vertical grid lines.
			for ( var i = step ; i < canvasWidth ; i += step ) {

				context.beginPath();
				context.moveTo( ( i - 0.5 ), 0 );
				context.lineTo( ( i - 0.5 ), canvasHeight );
				context.stroke();

			}

			context.strokeStyle = "#ff3333";

			// Draw RED horizontal grid lines.
			for ( var i = jump ; i < canvasHeight ; i += jump ) {

				context.beginPath();
				context.moveTo( 0, ( i - 0.5 ) );
				context.lineTo( canvasWidth, ( i - 0.5 ) );
				context.stroke();

			}
			// Draw RED vertical grid lines.
			for ( var i = jump ; i < canvasWidth ; i += jump ) {

				context.beginPath();
				context.moveTo( ( i - 0.5 ), 0 );
				context.lineTo( ( i - 0.5 ), canvasHeight );
				context.stroke();

			}

		}

	</script>

</body>
</html>

And, when run this is Chrome, Firefox, and Safari, we get the following output:

As you can see, using a textBaseline of alphabetic looks more-or-less exactly the same in each browser. But, the big difference, rendering-wise, between this demo and the previous demo is that the text is being rendered above the red lines as opposed to below the red lines. In order to account for that, we'd have to adjust the location of the y coordinate in the .fillText() invocation - but that's a post for another day.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel