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>
次に maxWidth
に 60
を指定してみます。
<canvas></canvas>
<script>
const ctx = document.querySelector('canvas').getContext('2d');
ctx.font = "24px Meiryo";
ctx.fillText('Memorandom', 10, 24, 60);
</script>
水平方向に縮小されて描画されているのがわかるかと思います。
補足
描画幅とは直接関係ありませんが、2つ注意点があるので補足します。
まず fillText
の x
、y
ですが、テキストの左下の位置が基準となっていますので注意です。
次に font
の指定ですが、font-size
、font-family
が必須です。font-size
のみの指定はできないので注意です。
MDN に以下の記載があります。
CanvasRenderingContext2D.font
はキャンバス 2D API のプロパティで、テキストを描画するときに用いられる現在のテキストスタイルを指定します。この文字列は CSS の font の記述子と同じ構文を用います。 - CanvasRenderingContext2D.font - Web API | MDN
CSS の font 記述子と同じ構文とあるので、CSS の font 記述子を確認してみます。
次の値を含めなければなりません。
<font-size>
<font-family>
- font - CSS: カスケーディングスタイルシート | MDN
font-size
と font-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
のような省略表示を実現してみました。例えばソシャゲのようなものでユーザー名を表示したりするようなケースとかで使えるのではないかなと思います。