CSSで左側に画像、右側にテキストのブロックをフロートで横並びに配置して、画像のキャプションのテキストを右下に下揃えで表示したい(下図)。簡単にできそうですが、意外な落とし穴があったので解決策のメモです。
画像出典: Lorem Picsum Images from Unsplash
ダメな例
まずは失敗例から。画像、右側の本文およびキャプションの3つをそれぞれブロック要素で囲んでdiv.box
に入れてみました。キャプションの要素p.caption
をposition
使って、div.box
を基準にbottom
/left
で絶対位置指定してやれば実現できそうです(下図)。
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>
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%;
}
なんだ簡単!? では、この画面でブラウザの文字サイズを大きくしていったら何が起きるでしょうか? 画面のズームではないです。文字だけの拡大です。Firefoxの場合、メニューの「表示/ズーム/文字サイズの変更」を有効にすれば文字だけ拡大できます。
Command+「+」を打ちながら文字を300%まで拡大してみると…
ご覧のように、文字サイズを170%以上にすると、上側の本文テキストが“侵食”してきてキャプションの表示に重なってしまいました。これは、position: absolute
を使って配置した要素は通常のフローから外れるため、本文テキスト側はこの要素が存在しないかのように配置されるので発生します。
ちなみに、ウェブアクセシビリティに関するJIS-X8341-3:2010「7.1.4.4 テキストのサイズ変更に関する達成基準 (等級AA)」1によると、テキスト拡大200%までは他のコンテンツと重ならないようにサイズ変更できることを要求しています。
解決策
position: absolute
を使わない方法を考えてみましょう。
まず、要素の構成を変えます。p.caption
をdiv.box
の外側に出して兄弟要素にします(サンプルでは共通の親はdiv.content
)。そして、margin-left
で画像の幅だけ右に寄せて、margin-top
にキャプションの行数分の高さのネガティブマージンを指定してテキストの位置を引き上げることで、画像と下揃えにできます(下図)。
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>
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); /* 追加 */
}
ネガティブマージンの量は、計算式
(キャプションの行数)×(line-height)×(font-size)
で見積もれます。本サンプルでは、キャプションのテキストが2行(ブラウザで文字サイズを変更していない場合)になるという前提で、以下のプロパティを設定しています。
.caption {
margin-top: calc(-2 * 1.2 * 0.8rem);
}
下図はp.caption
のコンテント(背景色 ライトブルー)に関して、元の位置から最終的な表示位置までの動きを示したアニメーションです。ラッパーdiv.content
の境界を赤い線で示しました。
p.caption
を通常フローに置いたことにより、文字拡大時の振る舞いは次のようになります。
- キャプションのコンテンツボックスは、ネガティブマージンにより2行分
div.box
に食い込んだ状態でページの下側に伸びていく - 本文テキストの領域(背景色 黄色)が膨らんで
div.box
の高さが増えると、p.caption
のボックスもページの下側に流れていく -
p.caption
の領域が本文テキストと重なる可能性が残るのは、div.box
に食い込んだ2行分の高さ
残る課題は上記3への対応ですが、position: absolute
のときと違って、食い込みの高さが2行分に抑えられることがポイントです。そこで、本文テキストのp
要素の下側にpaddingを少なくとも3行分入れてやれば、それがのりしろとなって、p.caption
から食い込んだ2行を吸収できます。つまり、paddingの量は余白1行を考慮すると、計算式
(キャプションの行数 + 1)×(line-height)×(font-size)
で見積もれます。本サンプルでは以下のように設定しました(上図で背景色が黄色い領域)。
.col-txt p { /* 本文テキスト */
padding-bottom: calc(3 * 1.2 * 0.8rem);
}
本文テキストが複数のp
要素から成る場合は、(1) 最後のp
要素にpaddingを設定する、もしくは(2) div.box
の右側float要素に擬似要素で3行分の高さを埋め込む、などの方法で対応できます。
.col-txt p:last-child { /* 最後のp要素にpadding */
padding-bottom: calc(3 * 1.2 * 0.8rem);
}
.col-txt:after { /* float要素の後ろに空のブロックを追加 */
display: block;
content: "";
height: calc(3 * 1.2 * 0.8rem);
}
以上の対策を施した上で、ブラウザで文字サイズを300%まで拡大してみましょう(下図)。
文字サイズ170%の所で、上側から下りてきた黄色ののりしろがライトブルーの領域に重なり、ライトグリーンで示した領域に2行分のテキストが吸収されたのがわかります。以降、テキストを拡大してもキャプションが下へ押しやられるだけで、テキストの重なりは発生しません。
本記事のサンプルコードはこちら: https://codepen.io/kaz_hashimoto/pen/gOaOXKj