Chrome position: sticky
復活記念
先日リリースされた Chrome 56 で position: sticky
が戻ってきた のを記念して、position: sticky
を活用した小技です。
2017/02/16: 前稿におけるネガティブマージンの指定に問題があったため、記事を修正しました。
やりたいこと
overflow: auto
や overflow: scroll
などで スクロール可能にしたコンテナ要素の中で、position: fixed
のように自由に固定配置をしたい。
例として、以下のような縦横にスクロール可能なコンテナ要素を用意します。
<div class="scrollable-container">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
<p>...</p>
<p>...</p>
</div>
.scrollable-container {
overflow: scroll;
border: 1px solid silver;
width: 10em;
height: 10em;
p { width: 20em; }
}
『このコンテナのど真ん中に、固定配置で "" を置く』 のをゴールにしてみます。
失敗例
position: fixed
position: fixed
はビューポート(≒<body>
)に対する固定配置になるので、コンテナ要素の中で使っても、外に追い出されてしまいます。
position: absolute
だからといって position: absolute
は、スクロールにくっついてしまうので、固定配置とは言えません。ちょっと右に動かしたらご覧の通り。
そこで position: sticky
の出番です。
position: sticky
とは
普段はそのまま表示されますが、要素の位置がスクロールなどによって top
left
などで指定したしきい値を超えると、その位置で絶対配置で固定されるようになる要素です。
詳しい説明が MDN にありますが、正直、言葉じゃちんぷんかんなので、デモページなどを見ると感じが掴めるのではないかと思います。
もし iOS ユーザーなら、連絡帳アプリなどで、スクロールしても頭文字がずっとヘッダに固定される挙動を見たことがあると思いますが、アレが CSS で実現できると思ってください。(雑)
対応状況
Can I use で見てみてください。 Firefox はとうの昔から対応していましたが、Chrome は experimental な実装のまま、一度ドロップした経緯があります。52 で再度 experimental で実装され、先日の 56 のリリースで晴れて正式な実装となりました。(参考: Chrome 56 に position: sticky; が戻ってきた)
WebKit では 6.1 (iOS 6.1) から使えていますが、接頭辞 -webkit-sticky
が必要です。IE, Edge は目を瞑りましょう。
アプローチ
基本的な要領は、position: sticky
でよく見るヘッダ固定と同じなんですが、
-
position: sticky
で固定する要素の大きさを、コンテナ領域全体 に拡大 - その要素の中で
position: absolute
を使って絶対配置する
といった具合です。
position: fixed
は "固定されたビューポート" に対する絶対配置だったので、そのような役割の要素を position: sticky
で作ろうというのが、基本的な考え方です。
position: sticky
でコンテナ要素全体に固定する要素を作る
分かりやすく position: sticky
な要素を緑色にすると、以下のような感じになります。上下左右にスクロールして、固定したコンテナが動かなければ問題ナシ。
<div class="scrollable-container">
<!-- コンテナ要素全体に固定する要素 -->
<div class="sticky-container">
Sticky container
</div>
<div class="scrollable-body">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
<p>...</p>
<p>...</p>
</div>
</div>
.scrollable-container {
overflow: scroll;
position: relative;
}
.sticky-container {
position: -webkit-sticky;
position: sticky;
left: 0;
top: 0;
z-index: 1;
width: 100%;
height: 100%
}
.scrollable-body {
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
ポイントは <div class="sticky-container">
の位置で、position: sticky
ナシで普通に配置した時に、スクロールするコンテナの左上 に来るようにしておきます。
そうしないと、CSS で設定したしきい値を超えずに、fixed
にならないスクロール位置(いわば「遊び」)ができてしまうことになります。
また、後続の要素もしっかり height: 100%
の影響を受けるので、ネガティブマージン margin-bottom: -100%
を使って配置のズレを打ち消しています。
2017/02/16: 追記
前稿では height: 100%
の影響を打ち消すため、ネガティブマージンを使っていましたが、margin-bottom: 100%
の 100%
は、高さではなく、横幅に対する相対指定 でしたので、この指定は誤りでした
そのため、後続の要素については、絶対指定・かつ左上固定の <div class="scrollable-body">
コンテナの中に入れるように修正しています。
固定配置したい要素を置く
コンテナが固定できたら、後は position: absolute
と同じ世界になるので、この中に絶対配置で要素を置いていけば OK です。というわけで、 を置きましょう。
<div class="scrollable-container">
<div class="sticky-container">
<div class="star">★</div>
</div>
<div class="scrollable-body">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
<p>...</p>
<p>...</p>
</div>
</div>
.star {
position: absolute;
width: 32px;
height: 32px;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
font-size: 32px;
line-height: 32px;
color: #f80;
}
無事、スクローラブルなコンテナ内で fixed
っぽい固定配置ができました。
下の要素にアクセスできるようにする
しかし、このままだと『position: sticky
の要素がコンテナ全体を覆ってしまうため、下にある要素がクリックできない』という問題が生じます。
これを回避するためには、固定された要素に pointer-events: none
を指定して、クリックイベントを透過させると良いでしょう。
.sticky-container {
position: -webkit-sticky;
position: sticky;
left: 0;
top: 0;
width: 100%;
height: 100%;
/* クリックイベントを透過 */
pointer-events: none;
}
.sticky-container > * {
pointer-events: auto;
}
.sticky-container > *
を使用し、子要素に対して pointer-events: auto
を適用すれば、固定した要素もちゃんとクリックに反応してくれます。
なので、例えば Android の UI のように、ボタンを右下に固定配置する、なんてこともできたりします。
サンプル
実際のサンプルは でどうぞ。Firefox / Safari / Chrome 56 以降でご覧ください。
https://jsfiddle.net/a3k4z409/6/
Chrome における position: sticky
サポートが進んだことで、そろそろモダンブラウザでは気兼ねなく使える土壌が整ったのではないでしょうか。Edge の対応が待たれます。