カクヨムでの縦組み表示の実装と、縦書きWebの将来に向けて (builderscon tokyo 2018)

こんにちは、Webアプリケーションエンジニアのid:nanto_viです。先日開催されたbuilderscon tokyo 2018において「カクヨムでの縦組み表示の実装と、縦書きWebの将来に向けて」という発表を行いました。Webブラウザ上で、小説を縦組みで読むという機能に関するものです。その発表資料に補足と質疑応答の内容を加えて公開します。

なお、当日は資料を単一のHTMLファイルに切り出して、それをFirefoxで開き200%にズームした画面を映しながらプレゼンテーションしました。


カクヨムでの縦組み表示の実装と、縦書きWebの将来に向けて

自己紹介

  • nanto_vi (TOYAMA Nao)
  • 株式会社はてな
    • Webアプリケーションエンジニア
  • Perl, TypeScript

カクヨム

  • KADOKAWA × はてな による小説投稿サイト
  • 2016年2月正式オープン


縦組み表示



補足

カクヨムの小説ビューワは初期状態で横組みですが、表示設定ダイアログから「組み方向」を「縦組み」に変更すると縦組み表示になります。
縦組みと横組みの切り替えは瞬時に行われ、本文だけでなくヘッダ、フッタ、サイドバーなどビューワ全体が縦組みになります。

デザイン

縦組みを実現しましょうとなったときに、本文のみを単純に縦にすれば良いのかというと、そうではないと思いました。
(略)
そこで、UIを含めた本文以外の要素もすべて縦にするのはどうか、というのがカクヨムの小説ビューワーにおける提案でした。

縦組みとCSS

html.writingDirection-vertical {
  writing-mode: vertical-rl;
}

  • 縦中横にする部分はJSでspan要素を挿入
.tatechuyoko1,
.tetechuyoko2 {
  text-combine-upright: all;
}
<span class="tatechuyoko1">W</span><span class="tatechuyoko1">e</span><span class="tatechuyoko1">b</span><span class="tatechuyoko2">27</span>周年
World Wide Web
補足

日本語組版処理の要件の「縦組の和欧文混植に用いる文字」によれば、英数字など横組みを前提する文字を縦組みの中で使うには、三つの方法があります。

  1. 1文字ずつ縦に積む(図中の「Webは27周年」の「Web」の部分)
  2. 縦中横にする(図中の「27」の部分)
  3. 横に寝かせる(図中の「World Wide Web」の部分)

ブラウザの表示そのままだと横に寝ての表示になるので、縦に積むまたは縦中横にする部分にはCSSのtext-combine-uprightプロパティを指定する必要があります。

縦中横の規則

  • 前提: 「東アジアの文字」と「東アジア以外の文字」に分ける
  • 原則: 「東アジア以外の文字」の並びが
    • 4文字以下なら1文字ずつ縦に積む
    • 5文字以上ならそのまま(横に寝る)
  • 例外:
    • 3文字以下の数字の並びは縦中横
    • 3文字以下の「!」「?」の並びは縦中横
    • 「(」「)」「→」など縦に積むと不自然になる文字はそのまま
    • 縦組みでも正立になる文字はそのまま
補足

平仮名、片仮名、漢字、いわゆる全角文字は「東アジアの文字」とみなします。
英数字、記号、スペースは「東アジア以外の文字」とみなします。
ある文字が縦組みにおいて正立になる(縦に積む)か横転する(横に寝る)かは、Unicode標準の一部であるUAX #50にて定義されています。
さらに、全角の疑問符や感嘆符が並ぶ場合や、数字の前後に特定の記号が来る場合の例外もあります。

縦組みWebの歴史

補足

縦組みにするためのCSSのプロパティと値は、1999年の草案ではlayout-flow: vertical-ideographicでしたが、2001年の草案ではwriting-mode: tb-rl、2010年の草案ではwriting-mode: vertical-rlとなっています。
IE 5.5はwriting-mode: tb-rlをサポートしていますが、実は(その後IE 11まで)layout-flow: vertical-ideographicという指定も受け付けます。
2018年9月現在、CSS Writing Modes Level 3の勧告候補およびCSS Writing Modes Level 4の勧告候補が公開されています。

日時表示が横組みになる (IE)

  • IE、Edgeでは未対応の要素が縦組みにならない
    • IEではmaintime要素に未対応
対策
  • <main><div role="main">
  • time要素の内容にspan要素を入れ、そこで縦組みを指定
<div role="main">
  <time datetime="..."><span>2018年4月31日</span></time>
</div>

縦に積んだ文字の幅が広がる (IE, Edge)

.tatechuyoko1, 
.tatechuyoko2 {
  text-combine-upright: all;
}
<span class="tatechuyoko1">W</span><span class="tatechuyoko1">e</span><span class="tatechuyoko1">b</span><span class="tatechuyoko2">27</span>周年
  • 欧文フォントでのみ発生?
対策
  • IE、Edgeではwriting-mode: horizontal-tbを指定
    • CSSハック!
_:-ms-lang(x),
.tatechuyoko1 {
  text-combine-upright: none;
  writing-mode: horizontal-tb;
  line-height: 1;
}
補足

2018年にもなってCSSハックを使うことになるというのは複雑な心境です。

JSでのスクロール位置の取得 (IE, Edge)

  • 通常は左にスクロールするとscrollXが負数になる

  • IE、Edgeでは左にスクロールするとscrollYが正数になる

対策
let scrollOffset = (scrollX < 0) ? -scrollX : scrollY;

writing-modeを指定する要素 (Firefox, Chrome)

  • body要素に指定すると
    • Firefoxでは内容が短いときに左側に寄ってしまう
    • Chromeではウィンドウサイズの変更に内容が追従しない
対策
  • html要素に指定する

Flexboxが崩れる (Firefox)

  • Firefox 59では縦組みでflexboxを使うと異様に横に長くなる
  • Firefox 60(2018年5月10日リリース)は修正済み
対策
  • Firefox 60のリリース日まで待つ
補足

縦組み表示機能はベータ版という位置づけなので、対応ブラウザは各ブラウザの最新版のみに絞り込みました。
対応外のブラウザで縦組みに切り替えようとして表示が崩れてしまった場合は、ヘルプページから横組みに戻すことができます。

固定配置要素の表示が乱れる (Chrome)

  • Chrome 65ではサイドバー内でスクロールすると表示が乱れる
  • Chrome 66(2018年4月18日リリース)では発生しない

対策
  • Chrome 66のリリース日まで待つ

固定配置要素の内容が消える (Chrome)

  • Chrome 66ではサイドバーの内容が表示されないことがある

対策
  • 一部の要素にtransform: translateZ(0)を指定
    • ハードウェアアクセラレーションを引き起こす

補足

なぜかはわかりませんが、図中の赤線で囲った要素ふたつにtransformプロパティを指定すると、それ以外の要素も含めてサイドバーの内容がすべて表示されます。

横組みにしたときに崩れる (Safari)

  • 縦組みから横組みにすると表示が崩れる
対策
html.writingDirection-vertical {
  writing-mode: vertical-rl;
}
html:not(.writingDirection-vertical) {
  writing-mode: horizontal-tb;
}
補足

writing-modeプロパティの初期値はhorizontal-tbなので、本来ならhtml:not(.writingDirection-vertical)に対する指定は不要なはずです。しかし、Safariではこれがないと、縦組みから横組みに切り替えたときに縦組みの効果が残って表示が崩れてしまいます。

固定配置要素の内容をスクロールできない (iOS Safari)

対策
  • 絶対配置にしてスクロールに応じてJSで内容の位置調整
補足

iOSとAndroidで見比べてみると、サイドバーの挙動の違いがわかるかと思います。

マウスホイールでスクロールできない (IE、Chrome、Safari、Firefox)

  • Edgeではホイールスクロールが横スクロールに読み替えられる
    • 他のブラウザでは縦スクロールのまま
対策
  • Edge以外では、JSでホイール操作を検知し横スクロールさせる

リリース

  • めでたしめでたし

ちょっと待って!

  • このハックまみれ、いつまで続けるの?

未来のために

ブラウザが、自身のバグを見つけ修正できるようにしてやるのは、すべてのWeb開発者にとっての責任です。

John Resig - A Web Developer’s Responsibility
  • ブラウザ間の挙動の違いも含む

ブラウザベンダにバグを報告する


バグ報告の内容

  • 新たに報告する
    • 簡潔な再現コード
    • 期待する挙動
    • 実際の挙動

  • 既存の報告に付け加える
    • ☆、Me Too
    • 実在のWebサイトでの症状
補足

上に挙げたバグ報告の場はすべて英語でのやり取りとなりますが、再現コードがあれば簡単な英語でも十分通じます。
私は再現コードをJSFiddleで書くことが多いですが、再現コードをHTMLファイルにしてバグ報告に添付することもできます。
バグ報告においても、新たなWeb標準仕様の提案においても、実例というのは非常に価値の高いものです。実例があると議論が進みやすくなります。

web-platform-tests

ページ内リンクのスクロール位置

補足

HTML仕様もweb-platform-testsもGitHub上で管理されており、GitHubのissueやpull requestを通じて貢献できます。
テストを書くにあたってはweb-platfrom-testsのドキュメントや既存のテストファイルを参考にしました。

まとめ

補足

未知の要素が縦組みにならないと、Web Componentsのカスタム要素を縦組みで使うことが難しくなるので困ります。
文字幅が引き伸ばされると読みづらくなるので困ります。
スクロール位置の座標やスクロール領域の寸法の取得方法がブラウザ間で異なっていると、JSで表示を制御する際に困ります。
日本語の場合は行の右側に下線(傍線)が出るのが自然なので、左側に下線が出てしまうと不自然な表示となり困ります。

質疑応答

  • 質問: 縦組みを実装するにあたって、canvasでの描画などの方法は検討したか?
    • 回答: まずwriting-modeを使うプロトタイプが作られ、これで行けそうだということで開発が始まったので、writing-modeありきで開発を進めた。
  • 質問: 句読点のぶら下げ(句読点を行末からはみ出させるような処理)には対応しているか?
    • 回答: 対応していない。ぶら下げを制御するためにCSS Text Moduleでhanging-punctuationプロパティが検討されており、Safariはこれに一部対応している。なお、禁則処理は各ブラウザの実装に任せている。
  • 質問: ルビはどうしているか?
    • 回答: HTMLのruby要素を使うことで、各ブラウザとも縦組み中で問題なくルビを表示することができる。
  • 質問: JSで縦中横の範囲を算出しているとのことだが、CSSで指定することはできないか?
    • 回答: text-combine-upright: digits 3で3桁までの数字を縦中横にするといった指定はできるが、各ブラウザの対応状況が十分でなく、また疑問符や感嘆符の並びには対応していないため、JSで処理している。4文字以下の英字を縦に積むといった細かな規則も考えると、CSS側ですべて対応するのは難しいのではないか。
  • 質問: 新聞や雑誌のように段組みを使うことはできるか?
    • 回答: 段組み自体はCSS Multi-Column Layout Moduleで実現可能だが、縦組みにおいて「一段の高さと幅を固定し、文章量に応じて段の数を自動的に増減させる」ような表示には直接対応していない。そのような表示を実現するためにはJSの補助が必要となるだろう。

buildersconに参加したのは初めてですが非常に熱量のあるイベントで、普段聞けない話も多く驚きに満ちた場でした。運営者、参加者の皆さんありがとうございました。