テクニック

CSS画像トリミングを揃える方法|object-fitとaspect-ratioでアスペクト比を固定する実装

この記事の対象: 縦横比がバラバラな画像を一覧に並べていて、見た目が揃わずに困っている制作者
読了時間: 約12分

クライアントから受け取った画像が、横長・縦長・正方形と入り混じっている。デザインカンプでは全部同じ大きさのカードで並んでいたのに、実装したら高さがガタガタになる。CMSで運用が始まったあと、担当者がアップロードした画像でレイアウトが崩れる。制作の現場で繰り返し起きるこの問題は、object-fit と aspect-ratio の2つのプロパティで、JavaScript もライブラリも使わずに解決できます。この記事では、そのまま貼って動くコードと、どのファイルのどこに置くかまで書きます。


前提と対応環境

コードを書く前に、動く条件をはっきりさせておきます。

必要なもの

項目 内容
編集するファイル サイトのCSSファイル(style.css / theme.css など任意の1本)
HTML側の作業 画像を囲む要素にクラスを1つ足すだけ
JavaScript 不要
外部ライブラリ 不要
必要な権限 CSSファイルとテンプレートを編集できる権限

WordPress ならテーマまたは子テーマの style.css、Shopify なら assets/ 配下のCSSファイル、素のHTMLサイトなら読み込んでいるCSSファイルに追記します。ビルドツールの有無も問いません。

対応ブラウザ

  • object-fit / object-position: 主要ブラウザ(Chrome / Firefox / Safari / Edge)で長く実装されており、現行バージョンならそのまま使えます。IE は非対応なので、IE対応が要件に入っている案件では後述の代替を使ってください
  • aspect-ratio: 主要ブラウザの現行バージョンで利用できます。実装時期はいずれも比較的新しいため、極端に古い端末を対象に含む案件では対応状況を確認してから採用してください

自分が触っている案件で使えるかを確かめたいときは、ブラウザの開発者ツールでスタイルパネルを開き、プロパティを打ってみるのが一番早い方法です。対応していないプロパティは打った瞬間に打ち消し線が付きます。Can I Use などの対応状況データベースを使う場合は、シェアの条件を自分の案件のアクセス実績に合わせて設定しないと判断を誤ります。サイトのアナリティクスでブラウザ別の実数を見てから決めるのが確実です。


なぜ画像の高さが揃わないのか

まず原因を短く押さえます。ここを飛ばすと、あとで出てくる aspect-ratio の役割が理解できません。

<img> は置換要素と呼ばれ、CSSで幅だけを指定すると、高さは元画像の縦横比に従って自動で決まります。横幅300pxのカードに、3000×2000px(3:2)の画像と2000×3000px(2:3)の画像を入れると、前者は200px、後者は450pxの高さになる。カードの高さが揃わない原因はこれです。

Flexbox や Grid で並べている場合、行の高さは一番背の高いカードに引きずられます。1枚だけ縦長の画像が混ざっただけで、その行全体に余白が生まれる。CMS運用のサイトで「あとから画像を差し替えたらデザインが崩れた」という報告が来るのは、たいていこのパターンです。

画像の高さが揃わない仕組み

枠の比率を決める(狙う形)

  • 枠の縦横比をCSSで固定
  • 画像は枠に合わせて切り抜き
  • どんな画像が来ても高さが同じ

幅だけ指定(崩れる形)

  • 高さは元画像の比率まかせ
  • 縦長が混ざると行が伸びる
  • 差し替えのたびに崩れる

解決の方向は1つです。画像に合わせて枠を伸ばすのをやめ、枠の形を先に決めて、画像のほうを枠に合わせる。それをやるのが aspect-ratio と object-fit の組み合わせになります。


object-fitで画像を枠に合わせる

5つの値の違い

object-fit は、指定したサイズの枠に対して画像をどう収めるかを決めるプロパティです。値は5つありますが、実務で使うのは実質2つです。

値 挙動 使いどころ
cover 縦横比を保ったまま枠いっぱいに拡大し、はみ出した部分を切る 一覧のサムネイル、ヒーロー画像。基本これ
contain 縦横比を保ったまま枠の内側に全体を収める。余白ができる ロゴ一覧、商品の全体を必ず見せたいとき
fill 枠に合わせて縦横を引き伸ばす。比率が壊れる 使わない(初期値がこれなので明示しないと事故る)
none 原寸のまま。枠より大きければはみ出す ほぼ使わない
scale-down none と contain の小さいほうを採用 小さい画像を拡大したくないとき

初期値が fill である点は覚えておく価値があります。object-fit を書き忘れたまま width と height の両方を指定すると、画像が引き伸ばされて人の顔が横に広がる。この崩れ方をしていたら、まず object-fit の書き忘れを疑ってください。

contain を選ぶときは、余白の色が枠の背景色として見える点に注意します。ロゴ一覧のように背景が白の画像を並べる場合、枠の background-color を白にしないと、画像の周囲だけ色が変わって見えます。

最小のコード

まずは一番シンプルな形です。これをCSSファイルの末尾に貼ります。

/* 画像を枠に合わせて切り抜く基本形 */
.card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

HTML側はこう書きます。

<img class="card-thumb" src="/images/sample.jpg" alt="サンプル画像の説明" width="1200" height="800">

変更する箇所は3つです。

  1. .card-thumb — 既存のクラス名に合わせて変える。テーマ既存のクラスを使うなら、そのクラス名に置き換える
  2. aspect-ratio: 3 / 2 — 出したい枠の比率。16 / 9、1 / 1、4 / 5 など、デザインに合わせる
  3. alt の中身 — 画像ごとに内容を書く。装飾目的の画像なら alt="" にする

width と height を HTML属性として書いておく理由は、読み込み前にブラウザが領域を確保できるようにするためです。これを省くと、画像が届いた瞬間にページがガクッと動く現象(レイアウトシフト)が起きます。aspect-ratio をCSSで指定していれば領域は確保されますが、両方書いておくのが安全です。

aspect-ratioが効かないときの書き方

aspect-ratio は、幅か高さのどちらかが決まっていないと計算できません。親要素の幅が確定していないケースでは効かないことがあります。その場合は、高さを直接指定する昔ながらの書き方に切り替えます。

/* aspect-ratio が効かない環境向け。高さを直接固定する */
.card-thumb {
  display: block;
  width: 100%;
  height: 220px;
  object-fit: cover;
}

@media (max-width: 767px) {
  .card-thumb {
    height: 180px;
  }
}

変更する箇所は 220px と 180px の2つの数値です。カードの横幅から逆算して決めます。横幅360pxのカードで3:2に見せたいなら240px、16:9なら203pxが目安になります。

比率で指定できる aspect-ratio のほうが、画面幅が変わっても形が保たれるので優先してください。高さの直接指定は、狭い画面で極端に横長になったり縦長になったりします。


object-positionで切れてほしくない部分を残す

cover の切り取り位置は中央が初期値

object-fit: cover は、はみ出した部分を切ります。どこを切るかを決めるのが object-position で、初期値は 50% 50%(中央)です。

人物写真では、これが問題になります。全身が写った縦長の写真を横長の枠に cover で入れると、中央、つまり胴体のあたりが残って顔が切れる。商品写真でも、上寄りに商品が置かれた写真だと商品が切れます。

/* 人物写真: 上寄りに切り取って顔を残す */
.card-thumb--person {
  object-position: 50% 25%;
}

object-position の値は「画像のどの点を、枠のどの点に合わせるか」を示します。50% 25% なら、画像の横中央・上から25%の位置が、枠の中心に来る。顔が上のほうにある写真では、この値を小さくするほど顔が残ります。

覚えやすいのはキーワード指定です。

指定 残る位置 主な用途
object-position: top; 上端 人物の顔、建物の外観
object-position: center; 中央(初期値) 汎用
object-position: bottom; 下端 料理を手前に置いた写真
object-position: 50% 30%; やや上寄り 顔が中途半端な高さにあるとき

画像ごとに位置を変える実践的な方法

一覧に並ぶ画像の切り取り位置を、1枚ずつ調整したい場面があります。CSSに画像ごとのクラスを増やすとメンテナンス不能になるので、CSSカスタムプロパティ(変数)を使って、HTML側の1行で完結させます。

/* 切り取り位置をHTML側から指定できるようにする */
.card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
}
<!-- 通常(中央で切る): 何も書かない -->
<img class="card-thumb" src="/images/a.jpg" alt="説明" width="1200" height="800">

<!-- 顔を残したい画像だけ style で上書き -->
<img class="card-thumb" src="/images/b.jpg" alt="説明" width="800" height="1200"
     style="--focal: 50% 20%;">

<!-- 左寄りの被写体を残す -->
<img class="card-thumb" src="/images/c.jpg" alt="説明" width="1200" height="800"
     style="--focal: 25% 50%;">

var(--focal, 50% 50%) の第2引数が初期値です。--focal を書かなかった画像は自動的に中央で切られるので、調整が必要な画像だけに style を足せばよくなります。

CMSで運用する場合は、この --focal の値を管理画面のカスタムフィールドに持たせます。WordPress ならカスタムフィールド、Shopify ならメタフィールドに 50% 20% のような文字列を保存し、テンプレートで style="--focal: {{ 値 }};" として出力する。編集担当者が「顔が切れている」と気づいたときに、CSSを触らずに直せる状態になります。

このとき、入力欄の説明文に「横位置 縦位置 の順に%で入力(例: 50% 20%)」と書いておくと、値の書き方を毎回聞かれずに済みます。空欄なら中央で切られるので、必須項目にはしないでください。

編集者が触っても崩れない仕組みの作り方は、ブロックパターンの記事でも同じ考え方を扱っています。


一覧レイアウトに組み込む完成コード

一覧レイアウトに組み込む完成コード

ここまでの内容を、カード一覧として動く形にまとめます。HTMLとCSSをそれぞれコピーすれば、そのまま表示されます。

HTML

<ul class="card-grid">
  <li class="card">
    <a class="card-link" href="/works/001/">
      <div class="card-media">
        <img class="card-thumb" src="/images/work-01.jpg"
             alt="制作事例01のキービジュアル"
             width="1200" height="800" loading="lazy" decoding="async">
      </div>
      <div class="card-body">
        <p class="card-cat">コーポレートサイト</p>
        <h3 class="card-title">カードのタイトルがここに入ります</h3>
      </div>
    </a>
  </li>

  <li class="card">
    <a class="card-link" href="/works/002/">
      <div class="card-media">
        <img class="card-thumb" src="/images/work-02.jpg"
             alt="制作事例02のキービジュアル"
             width="800" height="1200" loading="lazy" decoding="async"
             style="--focal: 50% 20%;">
      </div>
      <div class="card-body">
        <p class="card-cat">ECサイト</p>
        <h3 class="card-title">縦長の画像でも高さは揃います</h3>
      </div>
    </a>
  </li>

  <li class="card">
    <a class="card-link" href="/works/003/">
      <div class="card-media">
        <img class="card-thumb" src="/images/work-03.jpg"
             alt="制作事例03のキービジュアル"
             width="1000" height="1000" loading="lazy" decoding="async">
      </div>
      <div class="card-body">
        <p class="card-cat">採用サイト</p>
        <h3 class="card-title">正方形の画像も同じ枠に収まります</h3>
      </div>
    </a>
  </li>
</ul>

CSS

/* ===== カード一覧 ===== */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 32px 24px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.card-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

/* 画像の枠。ここで形が決まる */
.card-media {
  position: relative;
  overflow: hidden;
  border-radius: 8px;
  background-color: #f2f2f2;
}

.card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
  transition: transform .4s ease;
}

.card-link:hover .card-thumb {
  transform: scale(1.04);
}

.card-body {
  padding-top: 12px;
}

.card-cat {
  margin: 0 0 4px;
  font-size: 12px;
  letter-spacing: .04em;
  color: #777;
}

.card-title {
  margin: 0;
  font-size: 16px;
  line-height: 1.6;
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .card-thumb {
    transition: none;
  }
  .card-link:hover .card-thumb {
    transform: none;
  }
}

@media (max-width: 767px) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 24px 16px;
  }
  .card-title {
    font-size: 14px;
  }
}

コピーしたあと変更する箇所をまとめます。

箇所 初期値 変える基準
minmax(280px, 1fr) 280px カードの最小幅。この値より狭くなると列数が減る
aspect-ratio: 3 / 2 3:2 デザインの比率に合わせる
gap: 32px 24px 縦32 / 横24 縦横で別々に指定できる
border-radius: 8px 8px 角丸不要なら行ごと削除
background-color: #f2f2f2 グレー 読み込み中に見える色。サイトの背景に合わせる
scale(1.04) 4%拡大 ホバー時の拡大率。不要なら transition ごと削除
repeat(2, 1fr) スマホ2列 1列にしたいなら 1fr

.card-media の overflow: hidden は、ホバー時の拡大を枠の内側に閉じ込めるために必要です。これを外すと、拡大した画像が隣のカードに重なります。

末尾に入れた prefers-reduced-motion のブロックは、OS側で「視差効果を減らす」を有効にしている利用者に対して拡大アニメーションを止める指定です。ホバーで動きを付けるときはセットで書いておくと、後からアクセシビリティの指摘を受けて直す手戻りがなくなります。

loading="lazy" は画面外の画像の読み込みを遅らせる指定で、現行の主要ブラウザで有効です。ただしファーストビューに入る画像には付けないでください。表示が遅れて、逆に体感速度が落ちます。ファーストビューの大きな画像には、代わりに fetchpriority="high" を付けると読み込みが前に出ます。

一覧まわりの実装は、Shopifyのブログ記事一覧でも同じ考え方が使えます。


画面幅で比率を変える

スマートフォンで16:9の枠を使うと、画像の面積が小さくなって何が写っているか分からなくなります。画面幅に応じて比率を変えると、どの環境でも見せたいものが見える状態になります。

/* PCは横長、スマホは正方形に近づける */
.card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
}

@media (max-width: 767px) {
  .card-thumb {
    aspect-ratio: 4 / 3;
  }
}

ヒーロー画像では、逆の使い分けをします。PCでは横長のワイドな見せ方、スマホでは縦長にして高さを稼ぐ。

/* ヒーロー画像 */
.hero-image {
  display: block;
  width: 100%;
  aspect-ratio: 21 / 9;
  object-fit: cover;
  object-position: 50% 40%;
}

@media (max-width: 767px) {
  .hero-image {
    aspect-ratio: 3 / 4;
    object-position: 50% 30%;
  }
}

変更する箇所は比率の数値と、object-position の縦位置です。スマホで縦長にするとき、切り取り位置も一緒に見直さないと、PCでは見えていた被写体が切れます。実機か開発者ツールのレスポンシブモードで、必ず両方の状態を確認してください。

コンテナクエリで親の幅に合わせる

サイドバーの中とメイン領域の両方に同じカードを出す場合、画面幅で分岐すると片方が破綻します。画面は広くてもサイドバー内のカードは細いからです。この場合は、画面幅ではなく親要素の幅で分岐させます。

/* カードの親をコンテナとして宣言する */
.card-grid {
  container-type: inline-size;
}

/* 親が420pxより狭いときは正方形に寄せる */
@container (max-width: 420px) {
  .card-thumb {
    aspect-ratio: 1 / 1;
  }
}

container-type: inline-size を付けた要素が基準になり、@container の条件はその要素の幅で判定されます。設置場所ごとにクラスを増やさずに済むので、テーマやコンポーネントとして配る前提のコードでは有効です。比較的新しい機能なので、対象ブラウザを確認したうえで、メディアクエリでの指定を残しつつ上書きする形にすると安全です。

比率の切り替えでも見せたいものが入りきらない場合は、<picture> で画像自体を出し分けます。

<picture>
  <source media="(max-width: 767px)" srcset="/images/hero-sp.jpg">
  <img class="hero-image" src="/images/hero-pc.jpg"
       alt="トップページのキービジュアル" width="1920" height="820">
</picture>

スマホ用にトリミング済みの画像を用意する方式です。CSSでの切り取りは元画像を全部ダウンロードするので、スマホでの読み込み量を減らしたい場合はこちらが有効になります。表示速度が気になるなら、この判断も含めて計測してから決めてください。


背景画像方式との使い分け

object-fit が普及する前は、div に background-image を指定し、background-size: cover で同じことを実現していました。今でも使う場面はあるので、判断基準を整理します。

img と背景画像の使い分け

img + object-fit(基本)

  • alt が書けて検索エンジンに伝わる
  • lazy loading が効く
  • srcset で解像度を出し分けられる
  • 右クリックで保存できる

背景画像(限定的)

  • alt を持てない
  • 装飾的な模様・グラデーション向き
  • 擬似要素で重ねやすい
  • コンテンツ画像には使わない

判断は単純です。その画像が内容を持つなら img、模様や飾りなら背景画像。制作事例のサムネイル、商品写真、スタッフの顔写真はすべて img を使ってください。背景画像にすると、画像検索からの流入が失われ、読み上げ環境でも内容が伝わりません。

背景画像でも background-position で切り取り位置を調整できます。書き方は object-position と同じです。

.decor-panel {
  aspect-ratio: 16 / 9;
  background-image: url("/images/texture.jpg");
  background-size: cover;
  background-position: 50% 30%;
  background-repeat: no-repeat;
}

うまくいかないとき

実装中につまずきやすい点を、症状から引ける形でまとめます。

画像が引き伸ばされて歪む

object-fit が効いていません。原因は3つのどれかです。

  • object-fit を書き忘れている(初期値の fill になっている)
  • 別のCSSで object-fit: fill が後から当たっている
  • img ではなく親の div に object-fit を書いている

object-fit は画像そのもの(img / video)に指定するプロパティです。親要素に書いても何も起きません。開発者ツールで img を選択し、スタイルパネルに object-fit: cover が有効な状態で入っているかを確認してください。

高さが揃わない

aspect-ratio が効いていない可能性が高いです。次の順で確認します。

  1. img に height が別のCSSで指定されていないか(height: auto が後勝ちしていると aspect-ratio が無視される)
  2. 親要素の幅が確定しているか(Flexアイテムで min-width: auto により潰れているケースがある)
  3. テーマやリセットCSSの img { height: auto; } と競合していないか

3つ目は非常によく起きます。多くのリセットCSSに img { max-width: 100%; height: auto; } が入っており、これが後から読み込まれると aspect-ratio で決まった高さが上書きされます。読み込み順を変えられない場合は、詳細度を上げて対処します。

/* リセットCSSに負けないように詳細度を上げる */
.card-media > .card-thumb {
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

!important を足す前に、この方法を試してください。!important は後々の改修を難しくします。

画像がぼやける

cover は枠より小さい画像も拡大します。600px幅で表示する枠に400pxの画像を入れれば、当然ぼやける。CSSでは解決できないので、元画像を差し替えてください。

高解像度ディスプレイでは、表示サイズの2倍程度の実寸があるとぼやけません。600px幅で表示するなら、元画像は1200px前後を用意します。srcset で複数サイズを渡しておけば、ブラウザが端末に応じて選ぶので、全端末に大きい画像を送らずに済みます。

CMS運用で「担当者が小さい画像をアップロードしてぼやける」問題が起きるなら、アップロード時の推奨サイズをマニュアルに明記し、管理画面の説明文にも書いておきます。技術で防ぐより、運用で防ぐほうが確実です。

ホバー拡大で画像が枠からはみ出す

親要素の overflow: hidden が抜けています。.card-media に付けてください。border-radius を付けている場合、Safari では角丸の内側で切り抜かれないことがあり、その場合は isolation: isolate; を親に追加すると改善します。

スマホで意図しない位置が切れる

object-position を画面幅ごとに設定していないのが原因です。比率を変えたら切り取り位置も見直します。

.hero-image {
  aspect-ratio: 21 / 9;
  object-fit: cover;
  object-position: 50% 40%;
}

@media (max-width: 767px) {
  .hero-image {
    aspect-ratio: 3 / 4;
    object-position: 50% 25%;  /* 縦長にしたら上寄りに */
  }
}

読み込み中にレイアウトが動く

aspect-ratio をCSSに書いていても、HTMLの width / height 属性を省くと、CSSが適用される前の一瞬だけ領域が確保されません。両方書いてください。属性値は元画像の実寸を入れます(width="1200" height="800" のように単位なしで書く)。

CMSが出力する画像タグに属性が入っていない場合は、テンプレート側で付けます。WordPress の the_post_thumbnail() や Shopify の image_tag フィルターは実寸を自動で出力するので、独自に <img> を組み立てている箇所だけを直せば済むことが多いです。

画像が読み込めなかったときに枠が潰れる

src のパスを間違えた場合や、CMSで画像が削除された場合、alt テキストだけが残って枠の形が崩れます。枠側(.card-media)に aspect-ratio と背景色を持たせておくと、画像が来なくてもレイアウトは維持されます。

.card-media {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background-color: #f2f2f2;
}

.card-thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

この形では枠が比率を持ち、画像は height: 100% で枠を埋めます。画像が無くても高さが残るので、一覧の並びが崩れません。

IEに対応する必要がある

object-fit は IE で動きません。IE対応が要件に入っている場合は、div の背景画像方式に切り替えるか、画像を絶対配置して中央に寄せる方法を使います。

/* object-fit を使わない代替。IE でも高さが揃う */
.card-media {
  position: relative;
  padding-top: 66.66%;  /* 3:2 = 2 ÷ 3 × 100 */
  overflow: hidden;
}

.card-thumb {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: auto;
  min-height: 100%;
  transform: translate(-50%, -50%);
}

padding-top の値は「高さ ÷ 幅 × 100」で計算します。16:9なら56.25%、1:1なら100%、4:5なら125%です。この方式は縦長画像の切り取り位置を細かく制御できないので、要件が許すなら object-fit を使ってください。


公開前に確認すること

公開前に確認すること

実装が終わったら、次の項目を実データで確認します。ダミー画像のまま確認して納品すると、公開後に崩れが見つかります。

確認項目 見るもの
実データでの一覧表示 横長・縦長・正方形が混在した状態で高さが揃っているか
人物・商品の切れ 顔や商品の主要部分が枠外に出ていないか
スマホ表示 比率を切り替えた側でも被写体が残っているか
画像なしの状態 画像を削除したときに枠が潰れないか
レイアウトシフト 読み込み時にページが飛ばないか(開発者ツールのパフォーマンス計測で確認)
極端に小さい画像 想定より小さい画像を1枚入れて、ぼやけ方を見ておく

CMS運用のサイトでは、最後の2つを運用担当者にも共有しておくと、公開後の問い合わせが減ります。


よくある質問

object-fitとbackground-sizeはどちらを使うべきですか

内容を持つ画像には object-fit を使ってください。img タグなら alt が書けて画像検索の対象になり、loading="lazy" や srcset も使えます。背景画像は模様やグラデーションなど、無くても内容が伝わる装飾に限定します。

aspect-ratioが効かないのですが原因は何ですか

リセットCSSやテーマの img { height: auto; } が後から当たっているケースが最も多いです。開発者ツールで画像を選択し、aspect-ratio に打ち消し線が入っていないか、height が別の値で上書きされていないかを確認してください。詳細度を上げるか、読み込み順を変えると直ります。

人の顔が切れてしまうときはどうすればいいですか

object-position で切り取り位置を上寄りにします。object-position: 50% 25% のように縦位置の値を小さくすると、上のほうが残ります。画像ごとに変えたい場合は、CSSカスタムプロパティを使って style="--focal: 50% 20%;" とHTML側で指定する方法が管理しやすくなります。

スマホとPCで画像の比率を変えられますか

メディアクエリで aspect-ratio を書き換えれば変わります。PCは16:9、スマホは4:3といった使い分けが一般的です。比率を変えたときは object-position も一緒に見直さないと、切れる位置がずれます。サイドバーとメイン領域のように、同じ画面幅でも置き場所によって幅が違う場合は、コンテナクエリで親の幅を基準に分岐させると破綻しません。

画像の縦横比がバラバラでも本当に揃いますか

揃います。aspect-ratio で枠の形を固定し、object-fit: cover で画像を枠に合わせるため、元画像が横長でも縦長でも正方形でも同じ大きさで表示されます。ただし枠より小さい画像は拡大されてぼやけるので、元画像は表示サイズ以上の解像度を用意してください。


まとめ

縦横比がバラバラな画像を揃える実装は、次の3行が核になります。

aspect-ratio: 3 / 2;                       /* 枠の形を決める */
object-fit: cover;                         /* 画像を枠に合わせて切る */
object-position: var(--focal, 50% 50%);    /* 切り取り位置を調整できるようにする */

作業の順序は次のとおりです。

実装の手順
枠の比率を決めるデザインカンプから3:2や16:9を確定する
CSSを追記aspect-ratioとobject-fitの2行を書く
切れる画像を確認実際の画像を並べて顔や商品が切れていないか見る
focalで個別調整切れている画像だけstyleで位置を指定する

3番目を飛ばして納品してしまう事故が多いので、実データを入れた状態での確認は必ず入れてください。

CMS運用のサイトでは、切り取り位置を管理画面から指定できるようにしておくと、公開後に「画像を差し替えたら顔が切れた」という連絡が来なくなります。実装コストは小さく、運用の手間が確実に減ります。

同じ「ライブラリを足さずに素のCSSで済ませる」考え方は、スライダーの実装にも応用できます。