position:fixedと包含ブロック (containing block)

あまり機会がなく、すぐ忘れてしまうので備忘録として。

[最終形はこちら](#最終形)


## うまくいかないケース
下記のようなHTMLにおいて、CSSで`.container`に`overflow:scroll`、（`.fix`）に`position:fixed`を設定した時に、思った結果にならない。

```html
<div class="container">
    <nav class="fix">ナビゲーションなど</nav>
    <p>長い文章〜（略）〜長い文章。</p>
</div>
```

```css
body {
  padding: 100px 0;/* わかりやすいように隙間を開ける */
}

.container {
    border: 1px solid #ccc;
    width: 30em;
    height: 5em;
    overflow: hidden scroll;
}

nav.fix {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #eee;
}
```

[サンプル（jsfiddle）](https://jsfiddle.net/wxt15abg/2/)

### 期待
- `.container`の上部に`nav.fix`を固定したい
- `.container`をスクロールしても`position:sticky`のように、そのままの位置に固定したい

### 実際
- `nav.fix`が画面（ビューポート）の上部に固定されてしまう
- `.container`を`position:relative`としてもうまくいかない（`position:fixed`な子孫要素の包含ブロックにはならない）



`.container`を包含ブロックとして機能させられればいいのだが、そのためにはいくつかある条件の１つを満たす必要がある。





## `position:sticky`を使う
たいていの場合は`position:fixed`でなく、`position:sticky`で対応すればいい。

<details><summary>stickyを使ったサンプル</summary>

```html
<div class="container">
    <nav class="fix">ナビゲーションなど</nav>
    <p>長い文章〜（略）〜長い文章。</p>
</div>
```

```diff_css
body {
  padding: 100px 0;/* わかりやすいように隙間を開ける */
}

.container {
    border: 1px solid #ccc;
    width: 30em;
    height: 5em;
    overflow: hidden scroll;
}

nav.fix {
-   position: fixed;
+   position: sticky;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #eee;
}
```

[サンプル（jsfiddle）](https://jsfiddle.net/wxt15abg/4/)

</details>


## 何らかの理由で`position:fixed`でなければならない時
特に`position:sticky`のような、粘着する動作を実現したい時には、HTMLの構造を変えるなどの工夫が必要。

まず、スクロールする要素を`.content`、fixedする要素を`.fix`、その2つを包含する要素を`.container`とする。

```diff_html
<div class="container">
    <nav class="fix">ナビゲーションなど</nav>
+   <div class="content">
        <p>長い文章〜（略）〜長い文章。</p>
+   </div>
</div>
```

```diff_css
body {
  padding: 100px 0;/* わかりやすいように隙間を開ける */
}

.container {
    border: 1px solid #ccc;
    width: 30em;
    height: 5em;
-   overflow: hidden scroll;
}

+ .content {
+    width: 100%;
+    height: 100%;
+    overflow: hidden scroll;
+ }

nav.fix {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #eee;
}
```

[サンプル（jsfiddle）](https://jsfiddle.net/wxt15abg/1/)

（ここで、`.container`自体をスクロールできるようにすると、次項の処理を行なっても`position:sticky`のように粘着する動作にならず失敗する。）




## 最終形

これだけだと、`.fix`はビューポートを基準に配置されてしまい、ページの最上部に固定されてしまうので、`.container`が包含ブロック[^1]となるように`.container`のスタイルに`transform:scale(1)`（など）を追加する。

[^1]:[MDN - レイアウトと包含ブロック - 包含ブロックの識別](https://developer.mozilla.org/ja/docs/Web/CSS/Containing_block#%E5%8C%85%E5%90%AB%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E8%AD%98%E5%88%A5)

```html
<div class="container">
    <nav class="fix">ナビゲーションなど</nav>
    <div class="content">
        <p>長い文章〜（略）〜長い文章。</p>
    </div>
</div>
```

```diff_css
body {
  padding: 100px 0;
}

.container {
    border: 1px solid #ccc;
    width: 30em;
    height: 5em;
+   transform:scale(1);
}

.content {
    width: 100%;
    height: 100%;
    overflow: hidden scroll;
}

nav.fix {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #eee;
}
```

[サンプル（jsfiddle）](https://jsfiddle.net/wxt15abg/)



## 思いがけず包含ブロックになってしまうケース
別の事例で、思いがけず包含ブロックになってしまい、思いがけない結果になるケース。

```html
<div class="content">
    <p>文章〜（略）〜文章</p>
    <nav>
        <button>Item 1</button>
        <button>Item 2</button>
        <div class="close">×</div>
    </nav>
</div>
```

```css
.content {
  position: relative;
  border: 1px solid #ccc;
}

nav {
  background-color: #eee;
  filter: drop-shadow(3px 3px 3px rgba(0, 0, 0, 0.5));
}

.close {
  position: absolute;
  top: 4px;
  right: 4px;
}
```

[サンプル（jsfiddle）](https://jsfiddle.net/6f12yj9a/)


`.close`を`.content`の右上に配置するつもり。

しかし、`nav`に`fitler`プロパティが設定され、値が`none`以外であるため、`nav`が包含ブロックになってしまう。

結果、`.close`は`nav`の右上に表示される。