長い文字列を指定した文字数で切って「…」を付ける方法をまとめます。slice() で絵文字が壊れる理由、スプレッド構文で足りない場面、Intl.Segmenter を使う書き方まで確かめます。
答え
日本語と英数字だけなら、slice() で足ります。
example.js
function truncate(text, max) {
return text.length <= max ? text : text.slice(0, max) + '…';
}
console.log(truncate('こんにちは世界', 5));
console.log(truncate('短い', 5));
出力書き換えて実行できます
こんにちは…
短い
example.js
function truncate(text, max) {
const segmenter = new Intl.Segmenter('ja', { granularity: 'grapheme' });
const characters = [...segmenter.segment(text)].map((s) => s.segment);
return characters.length <= max ? text : characters.slice(0, max).join('') + '…';
}
console.log(truncate('👨👩👧と旅行に行きました', 5));
console.log(truncate('あ🙂いうえお', 3));
console.log(truncate('短い', 5));
出力書き換えて実行できます
👨👩👧と旅行に…
あ🙂い…
短い
絵文字が混ざる可能性があるなら、Intl.Segmenter を使ってください。
javascript
console.log('短い'.slice(0, 5) + '…');
短い…
example.js
function truncate(text, max) {
return text.length <= max ? text : text.slice(0, max) + '…';
}
console.log(truncate('ちょうど五文字', 7));
出力書き換えて実行できます
ちょうど五文字
長さを先に見る
切る前に長さを比べてください。 これを忘れると、短い文字列にも … が付きます。
example.js
const text = 'あ🙂いうえお';
console.log(text.length);
console.log([...text].length);
出力書き換えて実行できます
7
6
javascript
const text = 'あ🙂いうえお';
const cut = text.slice(0, 2);
console.log(cut.length);
console.log(cut === 'あ🙂');
2
false
slice() は範囲を超えてもエラーになりません。そのまま返します。
つまり「切られなかった」ことに気づけません。
example.js
const text = 'あ🙂いうえお';
console.log([...text].slice(0, 3).join(''));
出力書き換えて実行できます
あ🙂い
example.js
const text = '👨👩👧と旅行';
console.log([...text].slice(0, 2).join('') === '👨👩👧と');
出力書き換えて実行できます
false
… を足す前に、切る必要があったかどうかを判定します。
slice() では足りない場面
slice() が数えているのは符号単位で、見た目の文字数ではありません。
example.js
const text = '👨👩👧と旅行';
const segmenter = new Intl.Segmenter('ja', { granularity: 'grapheme' });
const cut = [...segmenter.segment(text)].slice(0, 2).map((s) => s.segment).join('');
console.log(cut);
console.log(cut === '👨👩👧と');
出力書き換えて実行できます
👨👩👧と
true
example.js
const segmenter = new Intl.Segmenter('en', { granularity: 'word' });
const text = 'The quick brown fox';
console.log([...segmenter.segment(text)].filter((s) => s.isWordLike).map((s) => s.segment));
出力書き換えて実行できます
[ 'The', 'quick', 'brown', 'fox' ]
見た目は6文字なのに、length は7です。絵文字が2つ分だからです。
絵文字の途中で切ると壊れます。
あ と絵文字の前半だけが取れています。
画面には読めない記号が出ます。文字数の指定は守れても、中身が壊れます。
→ 文字を正しく数える
スプレッド構文で足りるか
[...text] は符号位置ごとに分けるので、絵文字は割れません。
多くの場面ではこれで十分です。
ただし、複数の絵文字が組み合わさったものは分解されます。
家族の絵文字は5つの符号位置でできています。
slice(0, 2) では、その2つ目までしか取れません。
Intl.Segmenter なら、見た目の1文字を1つとして数えます。
どれを使うか
| 中身 |
使うもの |
| 日本語・英数字だけと分かっている |
slice() |
| 利用者が入力する文章 |
Intl.Segmenter |
| 絵文字が1つずつしか出ない |
[...text] でも足りる |
利用者が入れた文字列を切るなら Intl.Segmenter を選んでください。
入力欄に何が来るかは、こちらでは決められません。
Intl.Segmenter は毎回作らず、外に出して使い回すほうが速くなります。上の例は読みやすさのために中で作っています。
単語の途中で切りたくない
英語のように単語の区切りがある文章では、単語で区切ってから数える方法もあります。
isWordLike が単語らしいものだけを残します。空白と記号は落ちます。
日本語には単語の区切りに空白がありませんが、Intl.Segmenter は 'ja' を指定すれば分けられます。
まとめ
- 切る前に長さを比べる。
slice() は超えても落ちないので気づけない
slice() が数えるのは符号単位。絵文字の途中で割れる
[...text] は符号位置ごと。多くの場面ではこれで足りる
- 組み合わせた絵文字まで守るなら
Intl.Segmenter
- 利用者の入力を切るなら
Intl.Segmenter。 何が来るかは決められない
切り出しそのものの規則(負の数、範囲外、substring() との違い)はこちらにまとめてあります。
→ 文字列を切り出す・整える