インフラエンジニアの中野(etsxxx)です。今回はWebのフロントの話です。稚拙な部分はご容赦を。
はじめに
2020年12月、リブセンスにQ by Livesenseという広報ブログが誕生しました。このブログは明朝体と縦書きと長文にアイデンティティがあります。
Q by Livesenseは縦書きということで、漢数字を使っていたり、写真やイラストを使わずに純粋な文体で記事を書いていたりと、書籍のような日本語らしさが求められるデザインとなっています。
縦書きはWebエンジニアにとっても珍しい実装ですが、読者にとっても慣れないUIです。長文だからこそ読みやすさにはかなり気を遣って調整を繰り返していますし、レスポンシブという難しさもありますが、その話は本記事では触れません。
さて。CSSを扱う人ならばきっとご存知だと思いますが、Webにおいて日本語には悩みどころがあります。「禁則処理」と「両端揃え」そして「折り返し制御」です。
英語等の欧米の言語と比べると、日本語はわかち書きしない(スペースがない)構造のため、スペースの長さで見栄えの良いフォーマットに整えることができず、どうしてもガチャガチャした印象となってしまいます。
固定幅フォントで揃える原稿用紙のような方法もありますが、特に英数字が混じる文章ではなんだか間の抜けた見栄えとなります。
・・・と言いつつも、本記事のように1行あたりの文字数が多い箇所では、CSSで両端揃えを設定するだけで、ブラウザはほぼ見栄え良くフォーマットしてくれることでしょう。稀に英数字を入れることで崩壊することはありますが、稀です。
整形の問題が生じるのは、見出しのように大きなフォントを使う箇所、1行あたりの文字数が少ない場所です。例えばこのように。
「女/性」と分割されている見出しの折り返し位置に違和感を抱きませんか?システマティックに考えれば何の問題もない折返し位置なのですが、日本語としては勿体ないと感じます。なんとかしたい、日本語を綺麗に見せたい、以下のように折り返したいのです。
長い前置きとなりましたが、本記事は、見出しを少しでも見栄え良くするために行った「分節区切りでの折り返し」の自由研究の紹介です。
前提知識
段組みを綺麗に見せようとする時、欧米の言語ではCSSに text-align: justify
を指定しておけばだいたい綺麗に仕上がります。しかし、日本語の場合は、それだけでは単語の途中でもお構いなしに折り返しが発生します。
任意の場所に改行文字を入れるという解決方法もあります。しかし、Q by Livesenseは縦方向にもレスポンシブなサイトであり、特定のviewportサイズを期待した作りは簡単に破綻してしまいます。
CSSに詳しい方には釈迦に説法かと思いますが、日本語においては、途中で分割したくない単位を display: inline-block
を付与した span
タグで囲んでいくと、折り返し位置を制御することができます。
簡単なサンプル
<div style="width: 8rem; border: solid 1px;"> あいうえおかきくけこさしすせそ </div> -- <div style="width: 8rem; border: solid 1px;"> <span style="display: inline-block;">あいうえお</span><span style="display: inline-block;">かきくけこ</span><span style="display: inline-block;">さしすせそ</span> </div>
出力結果
つまり、文節でspanタグを付けることができれば、2021年現在のブラウザ実装においても自然な改行位置で折り返しが行われるということになります。
解決案の候補
解決案1: 手でspanタグをつける
間違いなく確実に、文節区切りすることができるでしょう。記事を書いた人がタグを付ければ、誤読や誤解も起きません。
欠点は言うまでもなく、そのようにコンテンツを作るのが面倒だということです。ましてや、推敲が多いケース、非エンジニアが記事を制作するときなどには、面倒が勝ってしまいます。
また、Q by LivesenseのようにCMSを使って運用する場合、タイトル欄などでHTML要素が使えないケースも多いはずです。それも制約・欠点と言えるでしょう。
解決案2: サーバーサイドの処理
例えばGoogle社はこの問題の解決ツールとしてBudouを公開しています。
CMS側でコンテンツを出力、あるいは保存する際に、Budouで処理すればGoogleパワーで文節区切りすることができそうです。
また、MeCab等の形態素解析エンジンでも同様なことはできそうです。辞書さえしっかりしていれば、かなり強力に分解できることでしょう。
難点はCMSと連動させるのがそれなりに面倒だということです。出力時の処理であればレスポンスタイムへの影響を考慮する必要が、保存時の処理であればCMSのデータストアに変更が必要となるでしょう。
解決案3: クライアントサイドの処理
今回採用したのはこの方式ですので、次の節で詳しく紹介します。Q by Livesenseでは、JavaScriptによりクライアントサイドで文節区切りを行います。
紹介前にあらかじめお伝えしておきますが、この方法が最善だという気は全くありません。実際に後で紹介しますが、実装後に眺めている限り、ミスもかなり多いです。
この方式を選んだ最大の理由は、CMS側への変更が最小限で済むことでした。JavaScriptを読み込めば有効、読み込まなければ無効に切り替えられるので、結果が芳しくなければやめることが簡単です。工数との兼ね合いでちょうど良かったという話ですね。
クライアントサイドで文節区切り
利用しているライブラリはMeCabで有名な工藤拓さん作のTinySegmenterです。
このライブラリにより、クライアントサイドで日本語の形態素解析が実現できます。というか、このライブラリを知ったことで解決案3を思いつきました。
このライブラリはどこかと通信することなくクライアントサイドで完結して処理するため、セキュアで高速です。感謝しかありません。サイズも25KBと非常に軽量なので、サイトのパフォーマンスに悪影響なく利用できたのもありがたいところです。
実装
以下のようなjQueryのコードで、ページがロードされたときにH1, H2, H3... タグ内の文字列を自動的に形態素解析して文節区切りに変換します。もう少しきれいに書けるかも知れませんが、jQuery素人の実装なのでご容赦ください。
言うまでもありませんが、予めTinySegmenterのJSを読み込んでおきましょう。
// add span tags $(document).ready(function() { var segmenter = new TinySegmenter(); var reg = new RegExp(/^[!"%')\*\+\-\.,\/:;>?@\\\]^_`|}~?!。、」』】はがのをにへとで]$/); $("h1, h2, h3, h4").each(function() { var spans = []; var segs = segmenter.segment($(this).text()); var len = segs.length; for (var i=0; i<len; i++) { if ( spans.length == 0 ) { spans.push(segs[i]); } else if ( reg.test(segs[i]) ) { spans[spans.length - 1] = spans[spans.length - 1] + segs[i]; } else { spans.push(segs[i]); } } $(this).html("<span>" + spans.join("</span><span>") + "</span>"); }); });
形態素解析の結果を配列に押し込む際に、文末記号や閉じ括弧、”てにをは”のような一文字ではないかを正規表現でチェックして、マッチした場合は前のトークンに結合するということで分節区切りおよび禁則処理をしています。
この結合処理を行わなかった場合は形態素解析そのままなので、以下のような区切りで出力されてしまいます。
エンジニア/に/男性/、/CS/に/女性/が/多い/の/は/なぜ/?
「/ブラック/企業/」/と/黒人/差別/を/考える
これでは折り返し後の先頭(行頭)に『に』や『と』、あるいは『、』や『」』といった記号が現れてしまいます。
結合処理を行うことで、以下のような禁則処理付きの文節区切りとなるということです。
エンジニアに/男性、/CSに/女性が/多いのは/なぜ?
「/ブラック/企業」と/黒人/差別を/考える
ただ、変則パターンがとにかく多いのが日本語。 結構な確率でミスはします。 それでもやらないよりマシというところを期待しています。
結果と考察
これまでの記事のタイトルの処理結果をみてみましょう。
「/ブラック/企業」と/黒人/差別を/考える エンジニアに/男性、/CSに/女性が/多いのは/なぜ? 利き手/という/ダイバーシティ #/今年/買っ/て/よかっ/た/もの/ と/ /#/買わ/なく/て/も/よかっ/た/もの 今企業は/何を/書く/べきか?/オウンドメディアの/現在/地点 職場に/おける/呼称と/無意/識の/偏見 外国語では/たらく。/日本語/ノンネイティブ/社員の/目線/から 企業が/差別と/戦う/という/こと。/男性が/性別に/向き合う/という/こと。 差別と/向き合い/始め/たわ/た/し/たち。/ジェンダー/問題を/学ん/だ/社員の/率直/な/声 「/正解が/ない」を/逃げ場に/し/ない。/ワークショップの/つくり/手に/聞く
結果についてどう思いましたか?
私は実装者ということで、新しい記事が上がるたびにこの処理結果を眺めてきました。3記事目くらいまではガッツポーズをするような見事な結果に思っていましたが、4記事目にしてハッシュタグとか半角スペースが入ってきたのに泣かされました。あと、『わたしたち』とか『はたらく』とか、漢字にしがちなところをひらがなで綴るのには、日本語の美しさを学ぶとともに、私もTinySegmenterも苦汁を嘗めさせられています。
最新のタイトルの縦幅を変更したときの折返し位置を見てみましょう。 このタイトルでも文節が少しおかしい処理結果になっているのですが、それっぽく折り返していることが見て取れるでしょうか。
100%の精度は到底期待できないのですが、わずか25KBのコードで外部APIにも頼らずにこの精度で分解できるのはTinySegmenterの凄いところだとつくづく思いました。
さいごに
本記事はTinySegmenterを開発・公開している工藤拓さんに感謝の意を示し、学んだ知識を世の中に還元することを目的に書いたものです。
Q by Livesenseは、記事の内容もさることながら、システム面で関わっている私も(タイトルにハッシュタグを埋め込まれたり、タイトルが長すぎてデザインがぶっ壊れたりと)新たな『Q』を得ることばかりです。記事の更新と合わせて読みやすくするための改善は今後も続けていきますので、この実装もすぐに使われなくなるかも知れません。
日本語の綺麗なフォーマット、ましてや縦書きの綺麗なフォーマットは、まだまだ技術が追いついていない印象です。CSSで1行書き加えるだけで綺麗な日本語の折り返しが実現される未来は、いつかは来るのでしょう。この記事がその時までの助けとなれば幸いです。
2024/05/16追記
この記事を書いてからおよそ2.5年後、Google社がBudouXを発表しました。
BudouXはJavaScript版も提供しており、この記事で書いているような処理を簡単に実現可能となりました。
その後リリースされたChrome v119以降では、CSSでword-break: auto-phrase
と指定するだけで、分かち書きのポイントで改行が行われるようになりました。これによりJavaScriptの読み込みすら不要となりました。まさに「さいごに」に書いたことが現実になったように思います。
ただ、この記事を執筆している現在では、word-break: auto-phrase
に対応しているブラウザは限られています。特に、iPhoneの標準ブラウザとして日本で利用者が多いSafariが非対応であるため、JavaScriptでの処理は今でもまだ必要であると考えています。
2024年5月にQ by LivesenseはHugoで再実装されました。
その際に、この記事で書いたTinySegmenterで行っていた処理を、BudouXに置き換えました。CSSとJavaScriptで2重で処理している形式です。ちょっとだけカスタムしたいケースがあり、JavaScriptで処理の補正をしています。
2重に設定していることは無駄のように見えるかもしれませんが、ChromeではCSSで指定するだけで適用できる処理のため、JavaScriptに比べると適用が速いのがメリットです。実際、LighthouseのFirst Contentful PaintやCumulative Layout Shiftといった、Performanceのスコアを落とす要因を減らすことができました。体感としてもガタつくことが少なくなったため、とてもソリッドなサイトに感じられるようになりました。