実現したいレイアウト
下の例のように、テキスト主体のページの右下隅に画像を配置したいことがある。
Fig.1 サンプルページ
例えていえば、上下逆L字型の回り込みです。この場合、ポイントはテキストの最終行と画像の底辺を一致させることです。上のサンプルでいうと「作者 鴨長明」の行と楓の葉の底辺が一致することです。もちろん「一致」の中身は「管理する」ことであって、意図的に一定のサイズ上下させることを含みます。
通常、「テキストの回り込み」というと、画像が左上か左中段、または右上か右側中段にあって、テキストが上から下に回り込むことを言うようで、上のサンプルのような形は、「結果」としてテキストが下に回り込む手前で止まるように画像の位置を CSS で調整することによって実現されます。固定されたレイアウトであれば、その方法でほとんどの場合は問題ないのですが、レスポンシブ前提のレイアウトではそうはいきません。
どのような方法で実現するか
HTML と CSSだけで上のサンプルの様なレイアウトを実現できないかと試行してみたのですが、すぐに無理だということがわかりました。その最も大きな理由は行末の「禁則処理」、「句読点のぶら下げ」、「英単語の途中改行禁止」などにあります。画面幅が変化する環境ではリフローの前に行数を把握することができないからです。レンダリングエンジンの処理内容が把握できれば事前にシュミレーションすることも理論的には可能なのでしょうが、それは全く非現実的な発想です。結局、javascript で動的に合わせ込むという現実的な方法を採用することとしました。
テキストはスタイル情報から line-height を取得すれば、一行の高さはわかります。従って、行数がわかればテキストの BOX 高もわかるわけで、そうなると。画像の上下位置を動的に調整して下辺を合わせるという方針が見えてきます。
そこで、「行数」なのですが、改行の不確実性に阻まれて簡単にはいきません。強引にやるとすれば、BOX の高さを監視しながらテキストを1文字ずつ書き込んで、BOX の高さが変化した数をカウントして行数を把握するという方法があります。この方法であれば、等幅フォントでもプロポーショナルフォントでも対応可能です。しかし、1文字書き込むごとにリフローが発生します。警告必至で非効率極まりない手法といえます。
ならば fragmentNode を使ってオフラインでやるかという方法もあります。しかし、強引に加えて"それはやりすぎ"という声が心の中から聞こえてきます。実現させたいレイアウトに比して仕組みが大きすぎると...
もっとスマートな方法はないかと、1ヶ月ほど思案した結果、何とかまとまった方法は、少しアナログ的な方法で、おおよその検討を付けてレイアウトし、リフローの結果を受けて微調整するというものです。最終的に、改行の1個の有無まで追い込んだ後は、画像の上下パディングと画像そのものの高さを微調整して合わせ込むという考え方です。
調整の量は、レイアウトにもよりますが、上のサンプルの例でいうと、line-height は 26px なので、画像の上下にそれぞれに 26px の 1/4 ずつのパディング、画像そのものの高さに 1/2 を配分します。それによって画像のアスペクト比が変わりますが、サンプルの楓の画像の場合は、ほとんど気づかない程度です。
レイアウト
単純化すると次のようなブロック構造になります。
Fig.2 ブロック図
主なブロックは3つ、「string」、「image」、「essay」です。レイアウトは次のようになります。
・「string」を「float:right」
・一旦「clear:both」で float 解除したあと、「image」を「float:right」
・「essay」を「float:none」で左上から流し込む
「image」を上から「string」という紐で吊り下げた形に例えると分かり易いと思います。この状態で、紐の長さを変えて「essay」の下辺と「image」の下辺を合わせるという考え方です。「string」は、その目的からして、幅1px、ボーダー無し、背景色なしとし、「essay」の右 padding の値を左より 1px 少なくすることで占めるスペースを見えなくしています。
微調整
CSS の初期値としては「string」は十分に短い値に設定しておきます。
HTML/CSS がローディングされた状態で、「essay」の BOX 高を取得し、その値から「image」の BOX 高を引いた値を「string」の高さとして設定します。ここでリフローが発生し、おおよそ1行以内の誤差で「essay」と「image」の底辺が揃います。
ここでもし、「essay」の底辺と「image」の底辺のレベル差が1行分以上合った場合は、「string」の高さを再調整します。この原因としては、「image 」の位置が変わることにより、長めの英単語がまとまった単位で折り返しされるなどのケースが考えられます。
調整の結果、「image」の底辺の方が「essay」の底辺よりも低かった場合は、これから先の微調整ができないため、「image」の位置を1行分上に上げます(「string」の高さを減らします)。
紐の長さの調整では、最終的に「essay」の1行分の高さが調整できない範囲になることがあります。「essay」の高さは1行の高さを最小単位として増減します。その調整は次のようになります。
・「image」の底辺よりも「essay」の底辺が下に来た時に、その補正のため、「image」を下に下げます。すると「image」の上に広がったスペースに「essay」のテキストが流れ込んで、結果として「essay」が1行減る場合があります。この"行ったり来たり"の状態が調整の限界となります。
・このような場合、上記のように「image」の底辺よりも「essay」の底辺が下に来ている状態で、「essay」の行数に変化が無いように「image」を下げます。つまり、「image」の BOX 枠上辺の位置が変化しないように「image」の位置を下に下げ、結果として「image」の下辺と「essay」の下辺を、ほぼ一致するラインにそろえるわけです。具体的には、
-「image」の padding-top を 1/4 行分増加させる。サンプルの場合は 26/4px になります。
-「image」の padding-bottom を 1/4 行分増加させる。サンプルの場合は 26/4px になります。
-「image」BOX の中に収容する画像の高さを「img」で 1/2 行分増加させます。
・このような処理によって、見た目の違和感が少ないように隙間を埋めます。画像のアスペクト比が変わるため、ものによっては違和感を感ずる場合があるかもしれません。その場合は、画像の高さを変えずに padding の配分のみで調整すると良いでしょう。
サンプルページ
サンプルページ (別ページで開きます)
ウインドウの幅を狭めてレスポンシブ動作を確認できます。
その後の見直し(2023年6月)
この技術は、まだアイデアの領域にとどまっており、実用的にはまだまだ解決すべき課題が残っています。それは次の2点、
(1) 最終調整段階で image の margin-top と margin-bottom に lineHeight の 1/4 ずつ配分し、残りの 1/2 は image の height を増加させることで対応しています。しかし、画像のアスペクト比を変えることはやはり避けるべきで、もっと工夫が必要な点です。テキストに英単語が入った場合、折り返しの発生が予測不可能なことから、最後の1行の折り返し発生ポイントまで範囲を狭め、最終的には padding-top によって image の位置を下げることによって底辺を合わせるという手法の方が合理的であるように思います。
(2) この仕組みを現実に使う段になると、標準化・普遍化ができていないことに気づきます。タグ名のルール化や、パッケージ内外での利用の競合関係の整理、そして、使い方の整理です。簡潔な形で埋め込むようにしなければ使い勝手が良くなりません。同じページの中で複数個所に摘要したい場合など、javascriptのモジュールの再帰性を意識しながら、呼び出しのルール化が必要になります。
これらの課題を解決して、元画像に忠実で、より汎用的な使い勝手の良いツールとなるようブラッシュアップを続けています。