ブログ記事の本文などで幅100%の画像を配置する場合、縦長の画像がやたらでかくなってしまったりなど、画像のアスペクト比によってその印象が大きく違ってしまうことがある。これをアスペクト比に関わらず大きさが揃って見えるようにしたい。それには各画像の面積が同じになるように幅をコントロールできればいいのではと考え、CSSでやってみた。

まず基準になる画像として、もっとも横長な画像のアスペクト比を決める。ここでは例として16 : 9とする。この16 : 9画像の幅を100%として、それ以外のアスペクト比の画像の相対的な幅を求めていく。

HTMLでは、各img要素のstyle属性に、アスペクト比を分数形式にしてカスタムプロパティとして指定する。

<img src="cat.jpg" style="--aspect-ratio: 16/9;">
<img src="dog.jpg" style="--aspect-ratio: 1/1;">
<img src="cow.jpg" style="--aspect-ratio: 2/3;">

次にCSSで、画像の面積とアスペクト比ををもとに幅を指定する。面積がSでアスペクト比がa : bの画像の幅xを求めるとすると、

x * x/(a/b) = S

なので、すなわち

x = √(S * (a/b))

となる。つまりCSSで平方根を求める必要がある。これにはvar()calc()を使って「バビロニアン・メソッド」と呼ばれるアルゴリズムを実装する。

img {
  --w: 16;
  --h: 9;
  --s: calc(var(--w) * var(--h) * var(--aspect-ratio));
  --x0: calc(var(--s) / 2);
  --x1: calc((var(--x0) + var(--s) / var(--x0)) / 2);
  --x2: calc((var(--x1) + var(--s) / var(--x1)) / 2);
  --x3: calc((var(--x2) + var(--s) / var(--x2)) / 2);
  --x4: calc((var(--x3) + var(--s) / var(--x3)) / 2);
  --x5: calc((var(--x4) + var(--s) / var(--x4)) / 2);
  --x6: calc((var(--x5) + var(--s) / var(--x5)) / 2);
  width: calc(100% * var(--x6) / var(--w));
}

--w--hは基準の画像の相対的な幅と高さ。この面積と対象の画像のアスペクト比をかけ合わせた数値--sの平方根が、求める画像の相対的な幅になる。まず--x0でざっくりとした初期値を設定し、--x1以降の計算で精度を上げていく。だいたい5回もやればかなりの精度で求められるっぽい。ちなみに最初何も考えずに24回とかやってたらブラウザのタブが死んだ。

最後に、求められた平方根--x6は基準の16 : 9画像の幅に対する相対的な幅なので、これをパーセンテージ値にする。そしてこれが完成版のデモ

ただまあ、現実的にはこんな面倒なことはせず、利用する画像のアスペクト比をあらかじめ数パターン決めておいて、クラス名で指定するのが賢明と思われる。

img.aspect-16x9 { width: 100%; }
img.aspect-3x2 { width: 91.854% }
img.aspect-4x3 { width: 86.602%; }
img.aspect-1x1 { width: 75%; }
...

というかそもそも面積が同じなら同じ大きさに見えるかと言うとそういうことではなさそうな気もする。ほんとに印象を揃えたいなら、なんかそういう認知的なファクターみたいなものを加味するべきなのかも。