BeAct Co., Ltd.

BLOG
社員ブログ

{position: absolute;} の、甘く危険な誘惑

こんにちは! 今日もコーディングを楽しんでいますか?

皆さんの中には、HTMLの要素を特定の位置に配置しようとして、CSSに{position: absolute;}を指定したことがある方がいらっしゃると思います。ところが、どうしても狙った位置に配置できなかった事はありませんか? たいていの場合、{z-index: 10001;}などといった、「他のz-indexと絶対に被らないであろう数値」を指定し、ページの左上を原点とした座標を指定するという実装例が多いのですが、このような「z-indexの数値に範囲を設け、管理する」やり方では、ページに手を入れ続けていくうちに、いつか破綻してしまうことでしょう。

こんかいは、「より理解しやすい{position: absolute;}の使い方」とともに、それにまつわる問題をいくつか取り上げていこうと思います。例として、架空の飲食店のWebアプリ製造の案件がやってきて、あなたが実装担当者としてアサインされた……とします。


「ポイントカードを、スタイルシートで書いてほしいんだ」

要件定義時に、こんな依頼がディレクターから上がってきました。利用金額によってポイントが溜まっていき、それによってお店から特典を得られる……という、ありがちなものです。スタイルシートでの描画を希望する理由は、利用者のニックネームや会員番号、現在のポイントなどの値をサーバから取得し、動的に描画できるようにしたいから、と考えてください。

「なんでそんなにabsoluteが嫌いなんですか?」

その後あなたが、アプリ全体の設計でてんてこ舞いになったため、ポイントカードの画面実装については、急きょ別チームのWebデザイナーさんがヘルプに入る事になりました。HTMLもCSSもできるという方だったので、HTML+SCSSによるモック(ロジックを入れていない、見た目だけの実装)作成をお願いしたところ、以下のようなコードが提出されました。

See the Pen abusoluteの甘い誘惑1 by isobeact (@rmzpacpm-the-builder) on CodePen.

これはあくまでも例なので、見栄えの貧相さには目をつぶっていただきたいのですが、スタイルシートの内容にご注目ください。

以下は、このスタイルシートを見たあなたと、デザイナーさんの回答との、一問一答です。


  • header{position: absolute;}なのは、ハンバーガーメニューのスライドのためなのはわかるのですが、{z-index: 1000;}とはなんの意図ですか?
    • メニューは、コンテンツとは独立した、ユーザーインターフェースだからです。もっとケタを大きくしたほうがいいですかね?
  • (おや? 数値のケタ数に、何らかの意味をもたせようとしている?)……その前に、もし、これがなかったら、どうなりますか?
    • メニューが、mainの下に隠れてしまいます。
  • (まさか……あーやっぱり)mainの子孫の、{position: absolute;}が当たっている要素の座標(top, left)の原点は、どこですか?
    • 「ページ」の左上隅です。
  • そうなると、もし「カードの位置を変更してほしい」という要望が来たら、これら全部の座標の数値を全部書き替えることになりますよね。それって大変じゃないですか?
    • {position: absolute}ですから、それは仕方ないです。
  • うーん、こういう事が起きやすいから、absoluteはむやみに使いたくはないんですよねぇ。
    • え? なんでそんなにabsoluteが嫌いなんですか?

いや、好き嫌いの問題ではないんですよねぇ……。

位置指定コンテキスト(positioning context)

別の記事で「重ね合わせコンテキスト」についてお話しました。それと同様に、「{position: absolute}のときに、その要素のtopleftなどが、どこを原点とするのか」をブラウザが判断する手がかりを、位置指定コンテキストと呼びます。

位置指定コンテキストには、以下のいくつかの要素が絡み合っています。

包含ブロック

座標の原点を規定する要素です。この要素の子孫に{position: absolute}が含まれた場合、topleftは包含ブロックの原点からの距離を意味するようになります。

特に指定しない場合は文書ルート、すなわち<html>要素が包含ブロックとなります(重ね合わせコンテキストと似ていますね!)。<body>ではないことに注意してください!

重ね合わせコンテキスト

ここで再登場です! じつは位置指定コンテキストが一定の条件を満たすと、意図せずともその要素は重ね合わせコンテキストにもなってしまうのです! これを知らないと「重ね合わせが壊れた(=重ね合わせコンテキストの影響範囲が、突然変わった)!」とパニックになってしまいます。

では、その注意すべき一定の条件を、以下に記しましょう。

  • positionが、absoluteまたはrelativeで、かつ、z-indexauto以外の値のとき。
  • positionが、fixedまたはstickyのとき。
  • transform, filter, will-changeが、設定されているとき。

このとき、その要素は位置指定コンテキストと重ね合わせコンテキストの両方になります。知った上で行うのならともかく、知らないと大変なことになりますね。

位置指定コンテキストの「正しい」作り方

「正しい」と書きましたが、そんなに身構えなくても大丈夫です。順番にいきましょう。

位置指定コンテキストができる条件を確認しよう!

先ほど紹介した条件は「重ね合わせコンテキストの発生とかぶる条件」でした。なので改めて、正しい条件を整理しましょう。

  • positionが、absolute, relative, fixed, stickyのいずれかのとき。
  • transform, filter, will-changeが、設定されているとき。

これらのうち、いずれかが成り立てば、それは位置指定コンテキストになります。

ここで、さきほど挙げた一定の条件をもう一度確認してみてください。位置指定コンテキストにする、かつ、重ね合わせコンテキストにしない(あと周りへ影響を及ぼさない)組み合わせがありそうですね!

新しく原点を作りたい要素に{position: relative}を設定しよう!

はい、まずは原点を設定したい要素に{position: relative}を設定することからスタートしましょう! 最初のコード例で言えば、ポイントカード部分になりますね。すると、この要素が位置指定コンテキストになり、この要素の子孫にとっての原点となります!

意図しない重ね合わせコンテキストを作らないように、ここでz-indexを設定しないようにしてくださいね!

※ハンバーガーメニュー部分の原点は、特定の要素ではなく「ビューポート」なので、次の項目で説明します。

位置指定コンテキストからの座標で、{position: absolute}しよう!

新しく作った位置指定コンテキストを原点にして、ポイントカードの各部品を配置しましょう。これで、「ポイントカードの位置を変えてほしい」という要望があっても、各部品の座標を変えずに済むようになります!

他の部品の{position: absolute}を解除しよう!

ポイントカードを作りたいがために、その他の部分で、不要な絶対配置指定を行っている箇所があります。これをとりあえず解除しましょう。

ハンバーガーメニューのz-indexは1でOK!

これで、重ね合わせコンテキストと位置指定コンテキストとの衝突を起こさず、なおかつポイントカード部分だけに位置指定コンテキストを設定することができました。

そして、「ハンバーガーメニューが<main>の下に隠れてしまう」という問題は……思い出していただけましたか? そう、重ね合わせコンテキストの問題ですね。さきほど、

意図しない重ね合わせコンテキストを作らないように、ここでz-indexを設定しないようにしてくださいね!

と説明しましたが、この作業によって、重ね合わせコンテキストを暗黙のコンテキスト(文書ルート)だけにすることができました。

同じ重ね合わせコンテキスト内での重ね順は、デフォルトではHTMLで先に記述された要素が奥に、あとに記述されたほうが手前になります。ハンバーガーメニューが<main>の下に隠れてしまう原因は、メニューのマークアップを先に記述しているからです。

この順番を反転させるには、ハンバーガーメニューのマークアップを後に記述することでも可能ですが、文書のセマンティクス的には好ましくありません。この場合は、先に記述したハンバーガーメニュー要素にz-index:1を指定するだけで十分です。重ね合わせコンテキストをうっかり生み出したりしないようにすれば、こんなに簡単に制御できるというわけです!

See the Pen 位置指定コンテキスト2 by isobeact (@rmzpacpm-the-builder) on CodePen.

次回予告:「現在ポイントの文字の縦の位置を『揃えて』くれないかな」

さて、今度はディレクターからこんな依頼が来ました。これに関しては、位置指定コンテキストは関係ない部分です。しかし……デザイナーさんとディレクターが何度やり取りしても、「なんかちょっと違うんだよね」と合意に至りません。そしてデザイナーさんはポイントの数字の部分に{position: absolute}を……と、それは禁じ手なのはもうおわかりですね? 実は、ディレクターの言う『揃えて』は、特に日本人はふだん意識することのない意味だったのです。

さてさて、この『揃えて』の意味とは? そして、あなたが施した実装とは? 次回の「プログラミング」ブログの更新をお楽しみに!

こんかいのお話は、以上です。それでは、よきコーディングライフを!