context.measureText() vs method (v5)

Revision 5 of this benchmark created on


Preparation HTML

<div id="benchmark-container">
  <canvas id="benchmark-canvas" width="800" height="150"></canvas>
</div>

Setup

const canvas = document.getElementById('benchmark-canvas');
const context = canvas.getContext('2d');
context.font = font = '16px Arial';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

function generateString(length) {
  let result = '';
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * characters.length));
  }
  return result;
}

const testStrings = {
  tiny: generateString(10),
  small: generateString(25),
  medium: generateString(50),
  large: generateString(100),
  huge: generateString(1000)
};

const canvasGetTextWidth = (text) => {
	const width = context.measureText(text).width
}
const allCharWidths = new Map();
// Prepopulate cache
allCharWidths.set(font, new Map());
const charWidths = allCharWidths.get(font)
for (let i = 0; i < characters.length; i+=1) {
	const char = characters[i]
	charWidths.set(char, context.measureText(char).width)
}

const getTextWidth = (text) => {
    if (text.length === 0) return 0;

    if (!allCharWidths.has(font)) {
      allCharWidths.set(font, new Map());
    }
    const charWidths = allCharWidths.get(font);
    context.font = font;

    let result = 0;
    for (let i = 0; i < text.length; i += 1) {
      const char = text[i];
      const nextChar = i < text.length - 1 ? text[i + 1] : undefined;

      if (!charWidths.has(char)) {
        charWidths.set(char, context.measureText(char).width);
      }

      if (nextChar !== undefined) {
        if (!charWidths.has(nextChar)) {
          charWidths.set(nextChar, context.measureText(nextChar).width);
        }

        const pair = char + nextChar;
        const isFirstPair = i === 0;

        let add = 0;
        if (charWidths.has(pair)) {
          add = isFirstPair
            ? charWidths.get(pair)
            : charWidths.get(pair) - charWidths.get(char);
        } else {
          const textMetrics = context.measureText(pair);
          const { width } = textMetrics;
          charWidths.set(pair, width);
          add = isFirstPair ? width : width - charWidths.get(char);
        }
        result += add;
      } else if (result === 0) {
        result += charWidths.get(char);
      }
    }
}

Test runner

Ready to run.

Testing in
TestOps/sec
len: 10 canvas
canvasGetTextWidth(testStrings.tiny)
ready
len: 10 custom
getTextWidth(testStrings.tiny)
ready
len: 25 canvas
canvasGetTextWidth(testStrings.small)
ready
len: 25 custom
getTextWidth(testStrings.small)
ready
len: 50 canvas
canvasGetTextWidth(testStrings.medium)
ready
len: 50 custom
getTextWidth(testStrings.medium)
ready
len: 100 canvas
canvasGetTextWidth(testStrings.large)
ready
len: 100 custom
getTextWidth(testStrings.large)
ready
len: 1000 canvas
canvasGetTextWidth(testStrings.huge)
ready
len: 1000 custom
getTextWidth(testStrings.huge)
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.