フロートした画像の横に下揃えでテキストを配置する方法

CSSで左側に画像、右側にテキストのブロックをフロートで横並びに配置して、画像のキャプションのテキストを右下に下揃えで表示したい(下図）。簡単にできそうですが、意外な落とし穴があったので解決策のメモです。
![fig_01_w600.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/fd6a073d-9357-8683-66a1-7b579286cc9e.png)

画像出典: [Lorem Picsum](https://picsum.photos/) Images from [Unsplash](https://unsplash.com/)

## ダメな例
まずは失敗例から。画像、右側の本文およびキャプションの３つをそれぞれブロック要素で囲んで`div.box`に入れてみました。キャプションの要素`p.caption`を`position`使って、`div.box`を基準に`bottom`/`left`で絶対位置指定してやれば実現できそうです（下図）。

![fig_sample-ng_w700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/6d2b6a8a-0d4a-4242-7f3a-1a48f759f518.png)
<details><summary>HTML（要点のみ）</summary><div>

```html
<div class="content">
  <div class="box">
    <div class="pic">
      <img src="https://picsum.photos/id/1059/300/400" alt="">
    </div>
    <div class="col-txt">
      <p>情に棹させば流される。...（略）</p>
    </div>
    <p class="caption">Lorem ipsum dolor sit amet, ...（略）</p>
  </div>
</div>
```
</div></details>
<details><summary>CSS（要点のみ）</summary><div>

```css
.content {
  width: 800px;
  margin: 20px auto 0;
}

.box {
  overflow: hidden;
  position: relative;
}

.pic {
  float: left;
  width: 28%;
}

.pic img {
  width: 100%;
  height: auto;
  vertical-align: bottom;
}

.col-txt {
  float: right;
  width: 70%
}

.caption {
  font-size: 0.8rem;
  line-height: 1.2;
  position: absolute;
  bottom: 0;
  left: 30%;
}
```
</div></details>

なんだ簡単！？　では、この画面でブラウザの文字サイズを大きくしていったら何が起きるでしょうか？ 画面のズームではないです。**文字だけ**の拡大です。Firefoxの場合、メニューの「表示／ズーム／**文字サイズの変更**」を有効にすれば文字だけ拡大できます。
![fig_02_w400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/c8e77a32-5722-7dc3-9c14-6f79fd4e1da1.png)
Command+「+」を打ちながら文字を300%まで拡大してみると…
![video_sample-ng_trim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/0ffe0b26-cec3-84b1-4bc3-f5296b61fe5a.gif)
ご覧のように、文字サイズを170%以上にすると、上側の本文テキストが“侵食”してきてキャプションの表示に重なってしまいました。これは、`position: absolute`を使って配置した要素は通常のフローから外れるため、本文テキスト側はこの要素が存在しないかのように配置されるので発生します。

ちなみに、ウェブアクセシビリティに関するJIS-X8341-3:2010「7.1.4.4 テキストのサイズ変更に関する達成基準 (等級AA)」[^1]によると、テキスト拡大200%までは他のコンテンツと重ならないようにサイズ変更できることを要求しています。
[^1]: 解説 https://www.fujitsu.com/jp/about/businesspolicy/tech/design/ud/jis-sc/sc7144.html

## 解決策
`position: absolute`を使わない方法を考えてみましょう。

まず、要素の構成を変えます。`p.caption`を`div.box`の外側に出して兄弟要素にします（サンプルでは共通の親は`div.content`）。そして、`margin-left`で画像の幅だけ右に寄せて、`margin-top`にキャプションの行数分の高さのネガティブマージンを指定してテキストの位置を引き上げることで、画像と下揃えにできます（下図）。
![fig_sample-1_w700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/7ad56cc7-4798-331d-2572-48757cabffcc.png)
<details><summary>HTML（要点のみ）</summary><div>

```html
<div class="content">
  <div class="box">
    <div class="pic"><img src="（略）" alt=""></div>
    <div class="col-txt">
      <p>情に棹させば流される。智に働けば角が立つ。...（略）</p>
    </div>
  </div>
  <!-- div.boxの外側にp.captionを移動 -->
  <p class="caption">Lorem ipsum dolor sit amet, ...（略）</p>
</div>
```
</div></details>
<details><summary>CSS（要点のみ）</summary><div>

```css
.content { /* 変更なし */ }

.box {
	overflow: hidden;
}

.pic { /* 変更なし */ }
.pic img { /* 変更なし */ }

.col-txt { /* 変更なし */ }
.col-txt p {
  padding-bottom: calc(3 * 1.2 * 0.8rem); /* 追加 */
}

.caption {
	font-size: 0.8rem;
	line-height: 1.2;
	margin-left: 30%;  /* 追加 */
	margin-top: calc(-2 * 1.2 * 0.8rem);  /* 追加 */
}

```
</div></details>

ネガティブマージンの量は、計算式
（キャプションの行数）×（line-height）×（font-size）
で見積もれます。本サンプルでは、キャプションのテキストが２行（ブラウザで文字サイズを変更していない場合）になるという前提で、以下のプロパティを設定しています。

```css
.caption {
  margin-top: calc(-2 * 1.2 * 0.8rem);
}
```
下図は`p.caption`のコンテント（背景色 ライトブルー）に関して、元の位置から最終的な表示位置までの動きを示したアニメーションです。ラッパー`div.content`の境界を赤い線で示しました。
![video_demo_1_trim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/d83780b3-9ccc-ba5c-b133-f0fa3ae522ec.gif)
`p.caption`を通常フローに置いたことにより、文字拡大時の振る舞いは次のようになります。

1. キャプションのコンテンツボックスは、ネガティブマージンにより２行分`div.box`に**食い込んだ状態**でページの下側に伸びていく
2. 本文テキストの領域（背景色 黄色）が膨らんで`div.box`の高さが増えると、`p.caption`のボックスもページの下側に流れていく
3. `p.caption`の領域が本文テキストと重なる可能性が残るのは、`div.box`に食い込んだ２行分の高さ

残る課題は上記3への対応ですが、`position: absolute`のときと違って、**食い込みの高さが２行分**に抑えられることがポイントです。そこで、本文テキストの`p`要素の下側にpaddingを少なくとも3行分入れてやれば、それが**のりしろ**となって、`p.caption`から食い込んだ２行を吸収できます。つまり、paddingの量は余白１行を考慮すると、計算式
（キャプションの行数 + 1）×（line-height）×（font-size）
で見積もれます。本サンプルでは以下のように設定しました（上図で背景色が黄色い領域）。

```css
.col-txt p { /* 本文テキスト */
  padding-bottom: calc(3 * 1.2 * 0.8rem);
}
```
本文テキストが複数の`p`要素から成る場合は、(1) 最後の`p`要素にpaddingを設定する、もしくは(2) `div.box`の右側float要素に擬似要素で３行分の高さを埋め込む、などの方法で対応できます。

```css:(1)
.col-txt p:last-child { /* 最後のp要素にpadding */
  padding-bottom: calc(3 * 1.2 * 0.8rem);
}
```

```css:(2)
.col-txt:after { /* float要素の後ろに空のブロックを追加 */
  display: block;
  content: "";
  height: calc(3 * 1.2 * 0.8rem);
}
```
以上の対策を施した上で、ブラウザで文字サイズを300%まで拡大してみましょう（下図）。
![video_sample-1_trim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/e46a980a-39b8-4d95-4a8f-dab1cb3a6497.gif)
文字サイズ170%の所で、上側から下りてきた黄色の**のりしろ**がライトブルーの領域に重なり、ライトグリーンで示した領域に２行分のテキストが吸収されたのがわかります。以降、テキストを拡大してもキャプションが下へ押しやられるだけで、テキストの重なりは発生しません。

本記事のサンプルコードはこちら： [https://codepen.io/kaz_hashimoto/pen/gOaOXKj](https://codepen.io/kaz_hashimoto/pen/gOaOXKj)