Canvas テキスト描画で CSS の text-overflow: ellipsis を実現

Canvas にテキストを描画する際に一定のサイズを超えると “…” といった形で CSS の text-overflow: ellipsis と同様にオーバーフロー分を省略する方法を紹介します。

やりたいこと

Canvas に長いテキストを描画する際に一定のサイズを超えると超えた分を省略して “…” のようにCSS の text-overflow: ellipsis と同じように表示したいと思ったのですが既存の API で簡単にはできなかったので、自分で作ってみたいと思います。

テキスト描画幅の指定

Canvas へのテキスト描画は CanvasRenderingContext2D インスタンスの fillText() メソッドを使います。 構文は以下の通りです。

CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);

引数の text は描画するテキスト、x は描画を開始する x 座標、y は描画を開始する y 座標です。 そしてオプション引数の maxWidth はテキストを描画する範囲の最大幅です。

maxWidth を指定するとその範囲内でテキストが描画されるのはよいのですが、maxWidth を超えるテキストを描画するとフォントを水平方向に縮小したり、全体を縮小して表示されたりします。

まず maxWidth を指定せずに描画します。

<canvas></canvas>
<script>
  const ctx = document.querySelector('canvas').getContext('2d');
  ctx.font = "24px Meiryo";
  ctx.fillText('Memorandom', 10, 24);
</script>

max-Width 指定せずに描画

次に maxWidth60 を指定してみます。

<canvas></canvas>
<script>
  const ctx = document.querySelector('canvas').getContext('2d');
  ctx.font = "24px Meiryo";
  ctx.fillText('Memorandom', 10, 24, 60);
</script>

max-Width を指定して描画

水平方向に縮小されて描画されているのがわかるかと思います。

補足

描画幅とは直接関係ありませんが、2つ注意点があるので補足します。

まず fillTextxy ですが、テキストの左下の位置が基準となっていますので注意です。

fillText の座標の基準

次に font の指定ですが、font-sizefont-family が必須です。font-size のみの指定はできないので注意です。

MDN に以下の記載があります。

CanvasRenderingContext2D.font はキャンバス 2D API のプロパティで、テキストを描画するときに用いられる現在のテキストスタイルを指定します。この文字列は CSS の font の記述子と同じ構文を用います。 - CanvasRenderingContext2D.font - Web API | MDN

CSS の font 記述子と同じ構文とあるので、CSS の font 記述子を確認してみます。

次の値を含めなければなりません。

font-sizefont-family は必須で指定する必要があるとのこと。普段 CSS で font 一括指定プロパティ使わないので知りませんでした。

フォント描画幅の取得

さて .fillText()maxWidth で、テキスト描画の最大幅が指定できることはわかりましたが、テキスト幅がその最大幅を超える場合にテキストが縮小されてしまいます。描画したいテキストの幅が予めわかれば最大幅を超える分のテキストに対して編集してから描画することができそうです。

テキストの幅を描画前に取得することは measureText() で実現できます。

CanvasRenderingContext2D.measureText(text)

実際に取得してみます。

<canvas></canvas>
<script>
  const ctx = document.querySelector('canvas').getContext('2d');
  ctx.font = "24px Meiryo";
  const metrix = ctx.measureText('Memorandom');
  console.log(metrix);
</script>

measureText() の戻り値は TextMetrics - Web APIs | MDN というオブジェクトで、width が実際の幅となります。

{
  actualBoundingBoxAscent: 19
  actualBoundingBoxDescent: 0
  actualBoundingBoxLeft: -1
  actualBoundingBoxRight: 160.24609375
  fontBoundingBoxAscent: 25
  fontBoundingBoxDescent: 11
  width: 161.2734375
}

オーバーフローしたテキストを省略する

では measureText() を使って、テキストが最大幅を超える場合に省略表示する関数を作成します。

/*
 * 描画するテキストが最大幅を超える場合に省略表示しキャンバスに描画する
 * @param {CanvasRenderingContext2D} c - Canvas コンテキスト
 * @param {String} t - 描画するテキスト
 * @param {Number} x - 描画を開始する x 座標
 * @param {Number} y - 描画を開始する y 座標
 * @param {Number} w - テキストを描画する最大幅
 * @param {String} e - テキストを省略する際に表示する文字列
 */
const ellipsis = (c, t, x, y, w, e) => {
  if(c.measureText(t).width > w) {
    const we = c.measureText(e).width;
    while (c.measureText(t).width > w - we) {
      t = t.substring(0, t.length - 1);
    }
    t += e;
  }
  c.fillText(t, x, y, w);
}

実際に使ってみます。

<canvas></canvas>
<script>
  const ctx = document.querySelector('canvas').getContext('2d');
  ctx.font = "24px Meiryo"; 
  ellipsis(ctx, 'Memorandom', 10, 24, 60, "...");
</script>

無事に省略表示されて描画されました。

テキストを省略表示して描画

あとがき

ひと手間必要ですが、Canvas へのテキスト描画で CSS の text-overflow: ellipsis のような省略表示を実現してみました。例えばソシャゲのようなものでユーザー名を表示したりするようなケースとかで使えるのではないかなと思います。