タイトルが少し分かりにくいですが、display:flexの中でposition:absoluteが指定された要素の初期位置がどこになるか、という話です。

経緯

ポップアップメニューのコンポーネントを実装していて、フレックスボックスと絶対位置指定(position:absolute)を利用してポップアップメニューを中央揃えさせようとしましたが、SafariとIE11では期待通りの表示になりませんでした。

試してみた方法は、ポップアップのメニューとボタンを包むラッパーをフレックスボックスにして、justify-content:centerで中央寄せし、メニューの高さ方向だけ絶対位置指定する方法。

HTML
<div class="wrapper">
  <button type="button">Button</button>
  <div class="menu">hoge</div>
</div>
CSS
.wrapper {
  display: inline-flex;
  justify-content: center;
}

.menu {
  position: absolute;
}
期待される表示
メニューとボタンが中央揃えされている図

HTMLとCSSは必要な箇所以外を省略しています。

ただし前述の通り、SafariとIEでは中央揃えにならなかったので、そもそもフレックスボックス内で絶対位置指定された要素の初期位置(静的位置)って仕様ではどうなってるんだろうと調べました。

現在の仕様について

まずは現在の仕様から確認していきます。

CSS Flexible Box Layout Module Level 1 #4.1. Absolutely-Positioned Flex Childrenでは、次のように定められています。続く日本語訳は私のなんとなくの訳です。

As it is out-of-flow, an absolutely-positioned child of a flex container does not participate in flex layout.

The static position of an absolutely-positioned child of a flex container is determined such that the child is positioned as if it were the sole flex item in the flex container, assuming both the child and the flex container were fixed-size boxes of their used size. For this purpose, a value of align-self: auto is treated identically to start.

CSS Flexible Box Layout Module Level 1 #4.1. Absolutely-Positioned Flex Children

フレックスコンテナ内の絶対位置指定された子(要素)はフローから外れているのでフレックスレイアウトに参加しません。

フレックスコンテナ内の絶対位置指定された子(要素)の静的位置は、その子とフレックスコンテナが使用されているサイズの固定サイズボックスであると仮定し、さらにその子(要素)がフレックスコンテナの唯一のフレックスアイテムであった場合の位置となります。この目的のために、align-self:autoの値はstartと同じように扱われます。


大事なのは静的位置は(中略)子要素がフレックスコンテナの唯一のフレックスアイテムであった場合の位置の箇所のようですね。

実装してみてブラウザの挙動を確認

ということで、乱暴に言うと絶対位置指定された(けどtopleftなどが指定されてない)時の位置 = その子要素がフレックスコンテナの唯一のフレックスアイテムであった場合の位置となるはずですので、それぞれ実装して確認してみます。

See the Pen Absolutely-Positioned Flex Children by masuP9 (@masuP9) on CodePen.

ChromeやFirefox、Edge(の2017/06/05時点での最新版)では、仕様どおりに中央に表示されていますが... おや、Safari と IE の様子が...

Chromeでの表示例
Absolutely-Positionedの要素が、中央に表示されている
Safari バージョン10.1.1 (12603.2.4)での表示例
Absolutely-Positionedの要素はフレックスアイテムの一つ目のすぐ後ろ、3つめの要素に重なって、少し左寄りに表示されている
IE11での表示例
Safariと同じような表示で、少し左寄りに表示されている

フレックスボックスの過去の仕様を遡ってみると

Safariについては webkit.orgにてバグ報告されているみたいでした。156798 - Abspos flex item static position

バグ報告の中身を見ていると、仕様がアップデートされたが実装がアップデートされていない。という文言がありましたので、古い仕様を遡ってみてみました。すると2012年9月18日に出た仕様では、次に続く同一フレックスライン上のフレックスアイテムがあれば、そのアイテムの外側のメインスタートエッジが静的位置だとされているので、まさにIEやSafariの挙動は古い仕様における正しい位置でした。

IEにはもう期待しないとして、Safariには新しい仕様に対応して欲しいですね...

2017/06/07 追記: Safari Technology Preview Release 31 (Safari 10.2, WebKit 12604.1.23.0.1)では、現在の仕様に対応しているようです。

まとめ

まとめるとフレックスコンテナ内で絶対位置指定された要素の静的位置は、現在の仕様ではそれが唯一のフレックスアイテムだとしたときの位置となるはずですが、SafariとIEは古い仕様を元にした実装のままとなっているのか、期待した動作にならないことが分かりました。

当初の目的であった可変サイズのボタンとポップアップメニューの中央揃え、どなたかいい方法があれば教えてください。

参考までにYoutubeやFacebookのポップアップの実装を見てみると、コンポーネントとしてまとめるのではなく、ポップアップで出てくるメニューやツールチップは別のDOMとして生成しておいて、実行されたボタンの位置にJavaScriptでゴリゴリ合わせる、というような実装になってるぽく、うーん、そこまでやるのもどうかだなあと考えているところです。

参考