縦書きCSSの夢：電子書籍みたいにページ単位でページ送りをする話

この記事は**[『ドワンゴ Advent Calendar 2018』](https://qiita.com/advent-calendar/2018/dwango)**14日目の記事です。

## 3行でOK
- 電子書籍みたいにページ単位でページ送りできる縦書きWebをつくりたい
- `column-width` を悪用すればいけそう
- いも

## 電子書籍みたいなページ送りって何？
![iOSのBooksアプリ](https://qiita-image-store.s3.amazonaws.com/0/25109/4cfc968c-e17f-3b78-9450-fd540fd92274.gif)

↑ こういうやつです（iOSのBooks）

アニメーション自体はさておき、一般的な電子書籍では、紙の本やPDFリーダーのように、1ページずつページが送れるようになっています。
もうちょっと細かいことを言うと、各ページには上下左右にマージン（余白）がついています。

一方で僕らのWebで縦書きCSSを行うと通常はこのようになります。

![カクヨム](https://qiita-image-store.s3.amazonaws.com/0/25109/e75cfba7-7f69-c548-d604-f4e10b2f794c.gif)

↑ 横スクロールですね（[カクヨム](https://kakuyomu.jp/)の縦書き表示画面です）

Webなのでページは当然スクロール！……といえば、それはそうなのですが、最近はWebでもネイティブアプリっぽいことできるよ、という時代なので、なんとかして電子書籍っぽいページ送りを実現できたら面白いので挑戦したいと思います。

## うまくいかなかった方法：頑張って分割する

2年前の僕「事前にページの数だけ `<div>` をつくって、横に並べよう！」

どういうことかというと、

1. 画面のサイズと同じ大きさの `<div>` をつくる
2. その中に本文を流し込む
3. 溢れたら次の `<div>` をつくり、続きをそっちに流し込む

を永遠に繰り返します。

```html:イメージ
<div>絹商人のハリスは商用がすんで南ドイツを故国へ帰る途中、この際</div>
<div>ひとつシュトラスブルクから登山鉄道に乗って、じつに三十年もの</div>
<div>歳月を経たこんにち改めて母校を訪ねてみようと、とつぜん思いつ</div>
...
```

一見、正攻法でなんとかなりそうですし、実際動くことは動いたのですが、以下のような問題が山積みになりました。。。

### そもそも処理が遅すぎる

- 溢れたかどうかを判定するためにはDOMのサイズを見るしかない
- 「少し足す→ `<div>` のサイズを見る」を永遠に繰り返す
- 都度ブラウザのレンダリングが走るのですごいことになる

### 要素の途中で分割する必要があるとつらい

- 例えば `<a href="〜">とっても長い文章</a>` みたいなとき
- `<a>` の途中でページ分けをしないといけなくなる可能性がある

```html
<div>
  〜〜<a href="〜">こんに</a>
</div>
<div>
  <a href="〜">ちはー！</a>〜〜
</div>
```

みたいなことが起こる可能性があり、実装も大変だし、何よりキモい＞＜；

<br>

なんとかならないかなーと当時考えてみたのですが、そもそもこのアプローチではこれらは回避しようがないため、諦めていました(◞‸◟)

## 思いついた：カラムが使えるんじゃね？
<img width="802" alt="カラム" src="https://qiita-image-store.s3.amazonaws.com/0/25109/94591a04-d69e-73fa-c474-3292c6bd9dcd.png">

CSSには `column-width` というプロパティがあり、これを使うと段組みレイアウトを実現できます。

```html
<div class="sample">ここに文章〜〜〜〜</div>
<style>
.sample {
  column-width: 350px;
}
</style>
```

そして、これはもちろん縦書きCSSでも使うことができます。

```html
<div class="sample">ここに文章〜〜〜〜</div>
<style>
.sample {
  /* 縦書き指定 */
  writing-mode: vertical-rl;

  column-width: 350px;
  height: 350px; /* column-width と同じ値を入れる */
}
</style>
```

すると、こんな感じになります。

<img width="352" alt="縦書き" src="https://qiita-image-store.s3.amazonaws.com/0/25109/313e1344-3193-c714-f9b4-caa64484dc42.png">

……んん？　なんか見えてきちゃいましたね！！

<img width="530" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/25109/9475fca7-a590-c3db-535f-0157fe1d8947.jpeg">

**カラムになった各区画たちを、電子書籍の1つのページ単位と見立てる**ことで、自力で `<div>` 分割をしなくてもいい感じに表示ができそうな気がします！

## 実際にやってみた

![やってみた図](https://qiita-image-store.s3.amazonaws.com/0/25109/303344ac-98ce-691c-b5fa-7bb3bc239486.gif)

- [デモページ](https://rutan.github.io/swipe-vertical-text/)
    - コード： https://github.com/rutan/swipe-vertical-text
    - Chrome or Firefox推奨です
    - PCでも動きますが、頑張ってマウスでスワイプしてください！

### どうなってんの？

![イメージ図](https://qiita-image-store.s3.amazonaws.com/0/25109/5ce69feb-1442-a381-5fdf-c3fd5f29e898.png)

まず、`次ページ用` / `現在ページ用` / `前ページ用` の3つの列を用意します。
そして、それぞれの中に縦書き ＋ `column-width` を指定した全テキスト/CSSを挿入します。

その上で `次ページ用` の要素は座標を1ページ分**上**にずらします。
また、 `前ページ用` の要素は座標を1ページ分**下**にずらします。

こうすることで、真ん中には現在ページ、左側には次ページ、右側には前ページが並んで表示されるようになります。

あとは無駄な部分は `overflow: hidden` などで見えなくしてしまい、
JavaScriptでスワイプ操作を実装してあげれば、デモページのような見た目が実現できます。

#### 高さの計算について

「1ページ分ずらします」とか気軽に言ってますが、1ページ分がどのくらいの高さなのかや、そもそも全体で何ページ分あるのかを事前に決めることはできません。ブラウザの画面サイズによってしまうため、これらは描画後にサイズを取得して計算する必要があります。

が、残念なことに `column-width` を設定して縦に伸びているように見えるDOMの高さを取得してみても、伸びていないときのサイズが返ってきます。見た目が伸びているように見えますが、ブラウザ的にははみ出しているだけなのです。。。

そのため、 `column-width` を指定した要素の中にあるもの（つまり、本文部分）の一番最後の要素の座標を取得してあげれば、全体の長さを調べることができます。

先程のデモでは、一番最後に必ず空の `<div>` を入れるようにし、その要素の座標を計算しています。
わかりやすいよう、デモでは計算用の要素に赤い色を塗っています。本文の一番最後に入っている赤い棒がサイズ計算用の要素です。

### この方法の良かった点・悪かった点

#### 良かった点 :smile:  
- 縦書き周りの処理をブラウザに任せられる
    - `<a>` の途中で改ページしたらどうしよう…とか考えなくて良い
    - 普通にきれい
- 自力分割よりは圧倒的に早い

#### 悪かった点 :cry: 
- カコンっていう引っ掛かりが見えてる
    - ページ移動した際に位置を入れ替える部分が目に見えてしまう＞＜；
    - もう一個要素を用意すればごまかせるような気もするけど…？
- そもそも、3個並べることを解決と呼べるのか…？

<br>

うーん、いい方法だと思ったのですが、完璧には至りませんでした＞＜；
でも、使い方をちゃんとしたり、チラツキのごまかしを入れたりすれば、自力分割よりは現実的そうではあります！

## まとめ

縦書きCSSで電子書籍のようなページ単位でのページ送りを実現する方法について模索してみました。
そして、`column-width` と表示領域をうまいことすることで、擬似的にページ送りのようなものが作れました。
ただし、実運用するためには、まだまだ少し頭を捻らないといけない点が多そうです。

僕は小説は絶対に縦書きで読みたい派なので、ブラウザ上で最高の縦書き体験ができるようになる日を待ち望んでいます！