8192.jp

「position:absolute」と「transform:translate()」を使った中央寄せの落とし穴

Web制作 2022/07/21 2022/07/21

気にするほどではないけれど、こういうのは一応知っておいたほうがいいです。

目次

    まず、要素を中央寄せするときによく使われる手法をリストアップしてみましょうか。

    • text-alignやline-heightを使う
    • margin: 0 auto;で左右のマージンを自動調整させる
    • FlexやGridを使う
    • positionとtransformを組み合わせる

    ※最近だと「width: max-content」を使った新しい中央寄せの手法なども話題になっていますが、それはまた今度記事にします。

    さて、今回注目するのは「positionとtransformを組み合わせた中央寄せ」についてです。

    まずは挙動を見てみる

    See the Pen Untitled by Leo_8192 (@Leo_8192) on CodePen.

    <div class="container">
    	<div class="item"></div>
    </div>
    .container{
    	position: relative;
    	width: 400px;
    	height: 300px;
    	background: #333;
    }
    .item{
    	position: absolute;
    	top: 50%;
    	left: 50%;
    	transform: translate(-50%,-50%);
    	width: 100px;
    	height: 100px;
    	background: #f55;
    }

    これはもう言うまでもない、Web制作者なら誰もが書いたことのある記述ですよね。

    親要素である.item_outerを基準に、.item_innerを左から50%、上から50%の位置に配置しつつ、transform.item_innerの大きさの50%分だけ左上にずらすことによって、コンテナ内で上下左右中央寄せを行っています。

    一見何も問題無く動作しているように見えますが…

    中央に配置されてるけどされてない(?)

    まずはこのコードを見てください。

    <div class="container">
    	<div class="item_outer item_1"><div class="item_inner"></div></div>
    	<div class="item_outer item_2"><div class="item_inner"></div></div>
    </div>
    .container{
    	background: #000;
    }
    
    .item_outer{
    	margin: 0 30px;
    	width: 150px;
    	height: 150px;
    	background: #fff;
    }
    .item_outer.item_1{
    	position: relative;
    }
    .item_outer.item_2{
    	display: flex;
    	justify-content: center;
    	align-items: center;
    }
    
    .item_inner{
    	width: 100px;
    	height: 100px;
    	background: #222;
    }
    .item_1 .item_inner{
    	position: absolute;
    	top: 50%;
    	left: 50%;
    	transform: translate(-50%,-50%);
    }

    150x150pxのコンテナ内に100x100pxのアイテムを配置しています。んで、.item_1positionを使った中央寄せで、.item_2はFlexを使った中央寄せです。これを見比べてみると…

    See the Pen Untitled by Leo_8192 (@Leo_8192) on CodePen.

    まぁ、どっちも同じに見えますよね。用いている手法は違いますが、どちらも「中央に配置する」という着地になるのは間違いないはずなので、この2つの配置位置が異なるわけない…

    そう思っていた時期が僕にもありました。

    分かりやすい例を出すために、親要素(.item_outer)の大きさを子要素と同じ100x100pxにして比べてみましょう。

    See the Pen position+transform_2 by Leo_8192 (@Leo_8192) on CodePen.

    さて、あなたにはどう見えていますか?

    これはブラウザやスケーリングの状況よって表示結果が異なる挙動を示すので、筆者の環境のSSを貼っておきます。

    ブラウザはChrome、4Kモニターでスケーリングを125%に設定した状態で見ているんですが、positionを使ったほうの要素は明らかに親要素の背景色(白)が見えていますよね。一方、Flexを使って中央寄せしたほうの要素はどんなに拡大・縮小しても親要素は全く見えません。

    どっちも同じに見えているという人も、ページを拡大・縮小してみると同じ現象が確認できるかも。

    一部のプロパティを使うとこうなる

    筆者もそこまで詳しくないので断言は出来ないんですが、多分MDNのこのドキュメントが参考になると思います。

    要はtransform等、一部のプロパティを使うとその要素のレイアウトの法則(モード?)が変わるっちゅーことです。実際、perspectiveを付けたりしてもこの現象は起こりました。

    レイアウトの法則が変わるということは、拡大縮小したときに生じる小数点以下のピクセルの扱い方とか、その裏にある要素の扱い方とか、ブラウザによってレンダリングの法則が変わりそうっていうのは何となくイメージ出来ますよね。

    こんなん誤差だから大丈夫じゃない?

    そう思っていた時期が僕にもありました。

    これ筆者が実際に経験したんですが、デザインに対するこだわりが強いクライアントさんに「なんかここ、よく見ると1px?ボヤけて気がするんですけど…」って言われて頭抱えました。開発者ツールで調べても変にサイズが変わったりもしてないし、配置に関する記述にもミスは無し。でも確かに拡大・縮小するとなんか変。

    結局その時は色々とこねくり回して何とかしたんですが、「positionとtransformの組み合わせなんて大昔から使われてる手法なんだから、問題があるわけがない!」という謎の固定観念に囚われていたのでマジで苦労しました。

    対策は…

    対策は2つ。

    1つは「別の配置方法を検討する」ことです。タグ組みの兼ね合いなどでpositionしか使えないような状況ならどうしようもないですが、そうじゃないならさっさとFlexとかGridとかに切り替えましょう。これはCSSに限った話ではないですが、壁に当たった時に変に考え込んで時間を浪費するよりも、アプローチの仕方自体をガラッと変えてしまったほうが手っ取り早く問題は解決します。

    もちろん色々と試行錯誤して知識を付けるのは大切ですが、納期の決まっている仕事としてWebサイトを作っているなら、まずは目的を達成させるのを優先させましょう。お勉強は仕事を片付けてからでも遅くはないです。

    そしてもう1つの対策ですが、それは「誤魔化す」という荒業。これも状況が限られますが、さっきのサンプルで言うと後ろの白背景を透過させちまえば見た目上は何も問題のない綺麗な中央寄せになりますよね。というか親要素と同じサイズの子要素なんてtop:0;left:0;にしてtransformは使わなくていいですし。いやそもそも中央寄せの必要すらありません(これはサンプルが悪かった)。

    こういうゴリ押しで乗り切るのはあまり良くない…というかかなり良くないんですが、一応選択肢には入れておきましょう。

    まとめ

    こんなシチュエーションになるのは稀なのでそこまで気にする必要はないとは思いますが、こういうことも起こりうる!というのを頭の片隅に入れておけば、同じ現象に遭遇した時の対処が早くなります。

    以上。