【JS】文字幅を取得する

こんにちは〜

今日はjavascriptでごく稀に使う、文字列の幅を取得する方法についてメモです。

|あああああ|←この||の距離の事です。

javascriptにはデフォルトで計測するものを持っていないので、自力で測るしかありません。

調べてみると、2種類の方法があったので、そちらを使ってみます。

方法2つ

参考サイト:[Javascript] 任意の文字列で、レンダリングに必要な横幅を計算する

1.実際にタグを置いて計算

2.canvasに描いて計算

まぁどちらも参考サイト先に書いてるので、コードは省略します。

じゃあこの記事要らんやん。まぁそう焦らずに。

今回はこの二つのどちらがいいのか、検証したいと思います。

検証1.速度

やはり使うにあたって速度はある程度重要になります。特に何回も呼び出す場合は。

今回はこのようなコードで速度を比較してみました。

//js
var start = performance.now();

for(var i = 0; i < 10000; i++){
	var span = document.createElement('span');
	span.style.position = 'absolute';
	span.style.top = '-1000px';
	span.style.left = '-1000px';
	span.style.whiteSpace = 'nowrap';
	span.innerHTML = 'あいうえお';
	span.style.fontSize = '16px';
	span.style.fontFamily = '\'Noto Sans\', sans-serif';
	document.body.appendChild(span);
	var width = span.clientWidth; // (1)
	span.parentElement.removeChild(span);
}
console.log( 'span / 実行時間 = ' + (performance.now() - start) + 'ミリ秒' );

var start = performance.now();
for(var i = 0; i < 10000; i++){
	var canvas = document.createElement('canvas');
	var context = canvas.getContext('2d');
	context.font = '16px \'Noto Sans\', sans-serif';
	var metrics = context.measureText('あいうえお');
	var width = metrics.width; // (2)
}
console.log( 'canvas / 実行時間 = ' + (performance.now() - start) + 'ミリ秒' );

詳しいコード内容は参考サイトに、styleはそれぞれのサイトに合わせて記述してください。

では、実行。(※環境はelectronです)

結果:

span / 実行時間 = 1409.0950000099838ミリ秒
canvas / 実行時間 = 190.0600001681596ミリ秒​

圧倒的な差!

これはもう言うこと無しですね。

一応、キャッシュを使ってみますか。

//js
var start = performance.now();

var span = document.createElement('span');
span.style.position = 'absolute';
span.style.top = '-1000px';
span.style.left = '-1000px';
span.style.whiteSpace = 'nowrap';
span.style.fontSize = '16px';
span.style.fontFamily = '\'Noto Sans\', sans-serif';

for(var i = 0; i < 10000; i++){
	span.innerHTML = 'あいうえお';

	document.body.appendChild(span);
	var width = span.clientWidth; // (1)
	span.parentElement.removeChild(span);
}
console.log( 'span / 実行時間 = ' + (performance.now() - start) + 'ミリ秒' );

var start = performance.now();
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = '16px \'Noto Sans\', sans-serif';
for(var i = 0; i < 10000; i++){
	var metrics = context.measureText('あいうえお');
	var width = metrics.width; // (2)
}
console.log( 'canvas / 実行時間 = ' + (performance.now() - start) + 'ミリ秒' );

表示以外は外に出して、再利用してみます。(もしキャッシュ方法がミスってたらすいません。)

結果:

span / 実行時間 = 1189.5700001623482ミリ秒
canvas / 実行時間 = 26.450000004842877ミリ秒

逆に差が広くなってしまいました(笑)

速度の面ではcanvasが圧倒的に良いですね。

検証2.性能

続いて性能面です。いくら早くても正確な値ではなければ意味がありませんので。

と言うことで、先ほどのコードの(1) (2)の後の行に、それぞれ

console.log('width:', width);

を追記します。(あと、回転数は1万も要らないので、それも減らして・・・)

では文字列「あいうえお」で実行。

span / width: 80
canvas / width: 80

一致しました!

では次。「abcde」で実行してみましょう。

span / width: 49
canvas / width: 48.52796936035156

おや、canvasが少しずれました。

(ちなみにspanは100%正確と仮定します。html上に直接書いてますので。)

では、「あいうえお 」(最後に半角スペース)で実行です。

span / width: 80
canvas / width: 85.32798767089844

spanもしかして前後の半角スペース取り除くの・・・?

では真ん中に入れてみます。「あ い う え お」

span / width: 101
canvas / width: 101.31195068359375

やはり多少ズレますね。

ちなみに書きませんが、全角スペースはズレません。半角・特殊文字のみずれるようです。

ちなみに

canvasがずれた、と書きましたが、実際はspanもずれています。

Element.clientWidth(MDNのサイト)に記載されていますが、

メモ: このプロパティは値を整数値に丸めます。小数値が必要であれば、 element.getBoundingClientRect() を使用してください。

とのことで、綺麗な数値になっているのはただの偶然ではなく、丸め処理をしているからです。

最後の「あ い う え お」を上記関数で実行すると、「101.3125」になります。(でもcanvasとは一致しないですよね。どっちが正しいのか)

結論

特にこだわりがない限りはcanvasでいいでしょう。

一つだけ注意点とすれば、canvasタグはIE8以前では使えないので、もし幅広く対応するサイトの場合はspanのみの選択肢になるかと思います。

それでは、またね〜

- 2020/06/05 追記 -

canvasタグで弱点がありました。

&nbsp;などの特殊文字(文字実体参照っていうのかな?)や、htmlタグには対応してません。

なので、"<span>&nbsp;</span>"を実行すると、spanは5.328125を返すのに対し、canvasは172.17584228515625を返します。

そのため、こういう特殊文字を含む可能性がある場合は、特殊文字を元に戻したり、タグを消したり、諦めてspanを使ったりする必要がありそうです。

逆にspanの場合は、<script>タグなどを埋め込まれると実行してしまう可能性があるので、注意が必要かもしれません。(ちなみに<script>alert("a")</script>を入れてもalertは呼び出されなかったが、念のため。もちろんサイズは0になりました。)

もしよろしければ、サポートをしていただけると今後の励みになります!!