スクロールに応じてページ要素を固定表示できるposition: stickyが便利で面白い使い方ができそうなので実験してみました。仕様がまだ草案(Working Draft)の段階で、将来、細かい部分に変更がないとは言い切れませんが、Edgeを含めた最新のブラウザで、ほぼサポートされています。
フィーチャー・クエリ(@supports) と一緒に使えば、position: stickyに対応していないブラウザにも考慮した実装が可能なので、注意は必要ですがちょっとしたエンハンスメントとして使うのに良さそうです。
まずは「こんなのが簡単にできちゃいますよ」というデモからご紹介します。
面白い使い方
基本的な使い方だけでも便利なスティッキーですが、応用するとスクロール効果のような面白い使い方ができます。以下、最新版のFirefox、Chrome、Safariあたりで見ていただけると確実に動いていると思います。
デモ1
デモ2
デモ3
デモ4
これらをJavaScriptなしで、すべてCSSだけで実装できるってすごくないですか?
テーブルとスペーサーGIFでページをレイアウトする時代にHTMLコーディングをはじめたので(いつ?昭和?笑)、現代の制作者がうらやましい限りですw。
余談はさておき、上のデモの説明の前に、スティッキーの基本的な使い方を簡単にご紹介します。
position: stickyとは
数行のCSSでスクロールに応じて要素を固定表示することができます。たとえば、ページの途中にある要素がスクロールして最上部に来た際に固定できます。
こちらもデモを作ってみたので最新版のChrome、Safari、Firefoxあたりで見てみてください。
ベーシックな実装方法
上のデモのようにベーシックな実装をする場合、CSSとHTMLはシンプルです。
たった3行のCSSで、先ほどのデモのようにh2をスクロールに合わせてページトップに固定することができます。ステッキーですよねw。
要素の位置関係による挙動の違い
そんな素敵なスティッキーですが、スティック(固定)させる要素のDOM上の位置関係によって挙動にちょっとした差が生じます。
同じブロック内にある兄弟要素の場合
下のGIF動画のように、1つ目のスティッキー要素に2つ目のスティッキー要素が覆いかぶさるように表示されます。
たとえばh2要素を固定するとして、以下のように兄弟要素の場合にこのような挙動になります。
<h2>サブタイトル1</h2>
<p>
...
...
...
</p>
<h2>サブタイトル2</h2>
<p>
...
...
...
</p>
異なるブロックにある要素の場合
下のGIF動画のように、1つ目のスティッキー要素を2つ目のスティッキー要素が押しのけるように表示されます。
以下のように固定する要素が異なるブロック内にある場合はこのような挙動になります。
<div>
<h2>サブタイトル1</h2>
<p>
...
...
...
</p>
</div>
<div>
<h2>サブタイトル1</h2>
<p>
...
...
...
</p>
</div>
この辺の挙動の違いとz-indexを上手く利用すると、先ほどのデモのような、ちょっと気の利いたスクロール効果を簡単に実装できます。ということで、ここからは最初に紹介したデモのHTMLとCSSがどのように組まれているのかご説明します。
デモ1の説明
デモ1はこちら。
HTMLはいたってシンプルで、divで分けられた5つのブロックがあって、その中に段落が入っています。基本的には、デモ1〜4までほぼ同じHTMLです。
<body>
<div class="block block-one">
<p>One</p>
</div>
<div class="block block-two">
<p>Two</p>
</div>
<div class="block block-three">
<p>Three</p>
</div>
<div class="block block-four">
<p>Four</p>
</div>
<div class="block block-five">
<p>Five</p>
</div>
</body>
CSSは、まず各ブロックがウィンドウの全てを覆うようにwidth: 100%、height: 100vhを指定しています。そして、Flexboxでalign-itemsとjustify-contentを使って中のp要素を中央に表示させています。さらに、position: sticky、top: 0を指定して、各ブロックがスクロールに応じてページ上部に固定されるようにしています。
.block {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: -webkit-sticky;
position: sticky;
top: 0;
}
p {
display: inline-block;
font-size: 60px;
font-family: 'Great Vibes', cursive;
padding: 0;
margin: 0;
}
以下のCSSでそれぞれのブロックにz-indexを付与して後のブロックが前のブロックの上に重なるようにしています。
.block-one {
background: #212E32;
color: #fff;
z-index: 100;
}
.block-two {
background: #85937A;
z-index: 200;
}
...
デモ2の説明
デモ2では兄弟要素と異なるブロックにあるスティッキーの挙動の差を利用して表示に変化を与えています。CSSはブロックの高さをheight: 50vhに変えただけで残りは同じです。HTMLはdivでセクションを2つに分けています。
<body>
<div>
<div class="block block-one">
<p>One</p>
</div>
<div class="block block-two">
<p>Two</p>
</div>
<div class="block block-three">
<p>Three</p>
</div>
</div>
<div>
<div class="block block-four">
<p>Four</p>
</div>
<div class="block block-five">
<p>Five</p>
</div>
</div>
<div class="block">
<p>Some more content</p>
</div>
</body>
デモ3の説明
デモ3では、3つ目のブロックに以下のCSSを追加して変化を加えています。HTMLはデモ2と同じです。
フィーチャー・クエリ(@supports)を使ってposition: stickyがサポートされる場合に背景色をnoneで上書きするように指定しています。フィーチャー・クエリを使わずに.block-threeにbackground: noneを指定してしまうと、stickyがサポートされない場合に意図しない表示になってしまいます。
あと、@supportsでposition: -webkit-stickyを入れないと、フィーチャー・クエリがSafariで思ったように動いてくれないので要注意です。
.block-three {
background: #586C5D;
color: #212E32;
z-index: 300;
}
@supports (position: sticky) or (position: -webkit-sticky) {
.block-three {
background: none;
}
}
また、.block-threeだけ、p要素のスタイルを変えて違った効果を与えています。
.block-three p {
display: flex;
justify-content: center;
align-items: center;
width: 50%;
height: 50%;
background: #fff;
color: #212E32;
box-shadow: 0 1px 10px rgba(0,0,0,0.2);
}
デモ4の説明
デモ4では、ブロックに階層を設けて、各ブロックを上部に固定させるだけでなく、各セクションのタイトルも固定するようにしています。HTMLは、以下のようにdivでセクションを作って、それぞれにh1を追加しただけです。
<h1>Section One</h1>
<div>
<div class="block block-one">
<p>One</p>
</div>
<div class="block block-two">
<p>Two</p>
</div>
...
</div>
<h1>Section Two</h1>
<div>
...
</div>
あとはh1要素にスタイルを追加して上部にスティックさせるだけです。
h1 {
background: #FF6015;
color: #fff;
z-index: 1000;
margin: 6px;
padding: 10px;
position: -webkit-sticky;
position: sticky;
top: 6px;
left: 6px;
}
以上がデモの説明でした。
ブラウザ・サポート
ブラウザ・サポートを簡単にまとめると以下の通りです。詳しくはCan I use…を見てください。
- 最新のFirefox、Chrome(注1)、Safari(注2)、Edgeでサポートされています
- 注1: Chromeではtheadとtr要素でstickyが使えないそうです
- 注2: Safariではプレフィックスが必要です(-webkit-sticky)
- IEはサポートされてません
position: stickyを使う時の注意点
position: stickyを使ってみて、いくつかハマったところや注意点をメモっておきます。
先祖要素にoverflowが指定されていると動かない
親要素にoverflow: hiddenを指定してfloatを解除している場合、position: stickyが使えません。これは、直近の親要素だけじゃなくて、先祖要素のいずれかにoverflowが指定されているとダメみたいです。
floatを使ったグリッドシステムではposition: stickyを使えない可能性が高いので注意が必要です。
- overflow: hidden、overflow: autoだとstickyが効かない
- overflow: scrollで高さの指定がないとstickyが効かない
- overflow: scrollで高さの指定があると、そのブロック内でstickyが効く。この場合、先祖要素にoverflow: hiddenが指定されていても関係ありません
- overflow: visibleだとstickyが効く
inline-block要素がSafariでスティックしない
スティックさせたい要素にdisplay: inline-blockを指定するとSafariでスティッキーが効かなくなります。Firefox、Chrome、Edgeでは問題なくスティックするんですが、Safari(iOS Safariも)だけスティックしなくなります。
下のデモではオレンジに表示されているh1要素にスティッキーが指定されてます。
サポートのないブラウザへの対応
position: stickyがサポートされていないブラウザでは、プロパティが無視されるだけなので大きな問題はないと思うんですが、調整が必要な場合は@supportsとかModernizrを使って調整すると良さそうです。
ちなみに、@supportsを使う場合は以下のようにプレフィックスが入った方(position: -webkit-sticky)も条件に入れないとSafariで認識されないのでご注意ください。
.block-three {
background: #586C5D;
color: #212E32;
z-index: 300;
}
@supports (position: sticky) or (position: -webkit-sticky) {
.block-three {
background: none;
}
}
ちなみに、上のCSSでは、まずposition: stickyをサポートしないブラウザ向けのスタイルを記述して、@supportsを利用して対応ブラウザ向けのスタイルで上書きしています。
さいごに
ユーザーが縦スクロールに飽きてるなんていう考察もあるようですし、こういった効果を簡単に実装できるのはありがたいですね。
※この記事は「position: stickyの面白い使い方と使用時の注意点」の転載です。
タイトルおよびリード文はWPJ編集部によるものです。
Copyright ©️ 2018. Ryo Watanabe.