スクロールに連動して要素が画面上部に固定されてついてくる「追従ヘッダー」や「追従サイドバー」。
Webサイトでもよく見かけるデザインですが、みなさんはどのように実装してますか?
JavaScriptを使う場合もありますが、実はCSSのpositionプロパティ「sticky」を使うだけで実装できちゃいます!
- スクロール追従するヘッダーやサイドバーはどうやって作るの?
- position: stickyがうまく効かないんだけど原因は…?
- ブラウザ対応状況的に、実務で導入できるCSSなの?
当記事ではposition: sticky の使い方や仕組みについて網羅的に解説します。上のような疑問をお持ちの方はぜひ読み進めてください!
position: sticky とは?
position: sticky は、スクロールで指定位置まできた要素を固定させるCSSのプロパティと値です。
stickyは「粘着」といった意味なので、sticky要素は付箋のように指定場所に貼り付くイメージです。
<body>
<div class="section"></div>
<div class="sticky">sticky</div>
</body>
.section {
background-color: #e0e4eb;
height: 80vh;
}
.sticky {
position: sticky;
top: 0;
}
画像のような動きを実現させている、最低限必要なコードです。
使い方は簡単で、対象要素にposition: stickyを指定するだけ。ただ、topやbottomなどで位置を指定しないと固定されない点には注意しておきましょう。
JavaScriptを使わず、CSS2行だけで実現できるなんてカンタンですね!
実装例1:追従ヘッダー
<div class="kw"></div>
<header class="sticky-header">
<nav>
<ul class="nav-list">
<li>nav</li>
<li>nav</li>
<li>nav</li>
<li>nav</li>
<li>nav</li>
</ul>
</nav>
</header>
<div class="section">
<!-- メインコンテンツエリア -->
</div>
.kw {
height: 70vh;
}
.sticky-header {
position: sticky;
top: 0;
}
kv(キービジュアル)があって、その下にヘッダーがあるデザインです。
ヘッダー要素に position: sticky を指定し、 top: 0 に指定することで要素が画面上部までスクロールされるとそこで固定されます。
実装例2:追従サイドバー
<header class="header">
<!-- ヘッダー -->
</header>
<div class="kv">
<!-- キービジュアルエリア -->
</div>
<div class="container">
<div class="main"></div>
<aside class="sticky-aside">aside</aside>
</div>
.container {
display: flex;
}
.sticky-aside {
flex-basis: 20vw;
flex-shrink: 0;
height: 60vh;
position: sticky;
top: 0;
}
上の例では、コンテナクラスに display: flex を指定し、子要素を左右に振り分け、片方(sticky-asideクラス)にのみposition: sticky を指定することで追従サイドバーを実現しています。
ブログ型サイトに取り入れられることの多いデザインで、ナビゲーションエリアだけでなく、バナーなどのCTAを追従させるケースもよく見かけますね。
実装例3:固定フッター
<header class="header">
<!-- ヘッダー -->
</header>
<div class="kv">
<!-- キービジュアルエリア -->
</div>
<div class="container">
<!-- コンテンツエリア -->
</div>
<footer class="sticky-footer">footer</footer>
.footer {
position: sticky;
bottom: 0;
}
position: sticky でbottom: 0 を指定すると画面下部に固定されます。
フローティングフッターとも呼ばれますが、こちらもCTAとして採用されることの多いデザインです。
positionプロパティの値 fixed でも同じような実装はできますが、CSSの仕様が若干異なります。(のちほど解説します)
実装例4:パララックス効果
<body>
<div class="parallax area-1">area-1</div>
<div class="parallax area-2">area-2</div>
<div class="parallax area-3">area-3</div>
<div class="parallax area-4">area-4</div>
<div class="parallax area-5">area-5</div>
</body>
.parallax {
width: 100%;
height: 100vh;
position: sticky;
top: 0;
}
.area-1 {
background-color: #e0e4eb;
z-index: 100;
}
.area-2 {
background-color: #aab5ca;
z-index: 200;
}
.area-3 {
background-color: #7383a1;
z-index: 300;
}
.area-4 {
background-color: #354d7b;
z-index: 400;
}
.area-5 {
background-color: #111934;
z-index: 500;
}
パララックスとは視差のことで、Webではスクロールに連動して背景や要素に遠近感を持たせる装飾効果として採用されることがあります。
精密な動きを加える場合はJavaScriptが必要になりますが、簡易的なものであればposition: sticky だけで実現できます。
上の例では各要素共通で position: sticky を指定し、 top: 0 まできたら固定するようにしています。それぞれの要素は上に被さるよう、z-indexで高さに差をつけています。
CodePenのように、box-shadow や filter: drop-shadow なんかで影をつけると、高低差がよりリアルに感じられるのではないでしょうか。
ブラウザ対応状況(IE)について
現在ほとんどのモダンブラウザで対応していますが、IEのみ非対応です。
しかし、IEはサポート終了予定のため、今後採用される機会は増えていきそうです。
PolyfillでIE対応も可
何かしらの事情で、IE対応した状態で position: sticky を使いたい場合、stickyfillというPolyfillを使うことで対応させることが可能です。
<script src="https://cdnjs.cloudflare.com/ajax/libs/stickyfill/2.1.0/stickyfill.min.js" integrity="sha512-dmLpQXesGDP0ZM/s8zGKQU3Xlbix57QfwFNFh+BY5Ad/afObQ/lBo200mHWuHu8LOoI7tlK09yP3L/4DjdQ5Xw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
CDNとして使うScriptタグは上の通りです。bodyの閉じタグ前に設置しておきましょう。
上のサイトからも確認することが可能です。
<script>
var elements = document.querySelectorAll(".stickyさせたいクラス名");
Stickyfill.add(elements);
</script>
// jQueryを使っている場合はこちら
<script>
var elements = $('.stickyさせたいクラス名');
Stickyfill.add(elements);
</script>
実行コードは上のいずれかを記述すればOKです。position: sticky させたい要素のクラス名は各自書き換えましょう。
<body>
<div class="sticky"></div>
<!-- 以下、stickyfillのCDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stickyfill/2.1.0/stickyfill.min.js" integrity="sha512-dmLpQXesGDP0ZM/s8zGKQU3Xlbix57QfwFNFh+BY5Ad/afObQ/lBo200mHWuHu8LOoI7tlK09yP3L/4DjdQ5Xw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- 以下stickyfillの読み込み -->
<script>
var elements = document.querySelectorAll(".sticky");
Stickyfill.add(elements);
</script>
</body>
実際に使用する場合は上のように、CDN→実行コードの順番で記述しましょう。
position: sticky の仕組み
ここからはより理解を深めるため、position: sticky の仕組みについて解説します。
想定通りにコーディングできない場合、仕組みから理解することで解決策が見つかることもあります。
スティッキーアイテムとスティッキーコンテナ
position: sticky を指定すると、対象の要素はスティッキーアイテム、スティッキーアイテムの親要素は自動的にスティッキーコンテナになります。
sticky を指定しても、HTML内の高さ(height)が消えることはありません。
固定されるtopやleftなどの位置は、スティッキーコンテナを基準に適用されます。
また、スクロール中にstickyが固定されるのは、スティッキーコンテナの範囲内に限られますが、固定される条件は、window全体(ビューポート)が基準です。
「スクロールで指定位置まで到達した」という条件を満たすと、要素が浮き上がって固定されます。
動画のように、赤枠のスティッキーコンテナ範囲をスクロールが通過すると、固定されていたスティッキーアイテムも一緒にスクロールされます。
下から上にスクロールする場合も、スティッキーアイテムが指定位置まで戻ると再度固定され、コンテナ要素が指定位置以上に戻ると固定が解除されます。
positionプロパティの比較
他のpositionプロパティと共通している点・相違している点についてまとめています。
値 | 配置の基準位置 | position指定時の挙動 | 指定時の高さ( height ) |
---|---|---|---|
static | – (※配置自体できない) | 変化なし | 変化なし(高さを保つ) |
relative | 自分自身 | 変化なし | 変化なし(高さを保つ) |
absolute | relative要素か祖先要素 | ルールから外れ移動する | 保たない(その場から浮き上がる) |
fixed | window全体(ビューポート) | ルールから外れ移動する | 保たない(その場から浮き上がる) |
sticky | スティッキーコンテナ | 変化なし | 変化なし(高さを保つ) |
staticとの違い
staticはpositionプロパティの初期値で、特別な効果を与えない値です。
何も指定していない他要素と変わりないため、topやleftなどの位置指定や、z-indexなどの高さを指定しても効きません。
relativeとの違い
relativeは、通常配置(HTMLの並び)通りに表示され、HTML内で高さ(height)も保ちます。
top・right・bottom・leftの値を指定すると、元々配置されていた自分の場所を基準に、相対的に配置されます。
stickyの値とは指定した際に高さを保つ点など同様ですが、相対配置される基準点がコンテナ要素である点などに違いがあります。
absoluteとの違い
absoluteの基準位置は、直近の祖先要素か、 relative を指定した親要素です。
top・right・bottom・leftの値を指定することで基準位置から相対配置されるか、指定がなければ基準要素の左上に配置されます。
このときHTMLの配置ルールからは外れ、要素が浮き上がる状態になるため、高さ(height)も消えます。
相対配置された時に要素が浮き上がる点ではstickyと同じですが、指定した段階で要素が移動する点、高さが確保されない点が違いです。
fixedとの違い
fixedの基準位置はwindow全体(ビューポート)です。
top・right・bottom・leftの値を指定することでウィンドウを基準に相対配置されるか、指定がなければ左上に配置されます。
このときHTMLの配置ルールからは外れ、要素が浮き上がる状態になるため、高さ(height)も消えます。
固定される基準がwindow全体(ビューポート)になるのは同じですが、スティッキーコンテナの中で幅や位置が決まる点がstickyとの違いです。
さきほど「実装例3:固定フッター」で例にあげたようなコードも、コンテナとなる親要素によって、スティッキーアイテムの大きさが変わります。
fixedはビューポート基準で相対配置されるため、幅(width)を指定していない場合、動画のように表示自体が消えることもあります。
stickyは要素自体の大きさは保たれるため、HTML要素がブロックレベルであれば、スティッキーコンテナの中でwidth: 100%が適用されたままとなります。
position: sticky のパターン別挙動
次に position: sticky の挙動について、ケース別に見ていきましょう。
/* スティッキーコンテナ */
.sticky-container {
background-color: #e0e4eb;
border: 2px solid red;
}
/* スティッキーアイテム */
.sticky-item {
border: 2px dashed darkblue;
height: 100px;
position: sticky;
top: 0;
}
スティッキーコンテナ内にスティッキーアイテムがひとつの場合
上で解説している通り、指定段階では要素位置に変化はなく、指定位置までスクロールされたら(条件を満たしたら)要素が浮き上がり固定されます。
スティッキーコンテナの配置と連動するため、コンテナエリアが画面上から消えると、アイテムの固定も解除されて一緒にスクロールされます。
スティッキーコンテナ内にスティッキーアイテムが複数ある場合
/* スティッキーアイテム1 */
.first {
background-color: #fff;
}
/* スティッキーアイテム2 */
.second {
background-color: #8997ab;
}
/* スティッキーアイテム3 */
.third {
background-color: #ab9289;
}
同一の階層下でスティッキーアイテムがふたつ以上ある場合、指定位置でアイテムが上に重なるように表示されます。
スティッキーコンテナが複数ある場合
スティッキーコンテナが複数ある場合(兄弟要素どうしがコンテナとなっている)、下のコンテナが上のコンテナを押し出すように表示されます。
スティッキーコンテナ内にスティッキーアイテム以外の要素がない場合
スティッキーコンテナ内にスティッキーアイテム以外の要素がない場合、position: sticky は効きません。
スティッキーアイテムが浮き上がって固定される範囲は、スティッキーコンテナの範囲内です。つまりコンテナにスクロールできる余白(範囲)がない場合固定されないというわけです。
position: sticky が効かないときのチェックポイント
position: sticky の基本的な使い方はわかったかと思いますが、仕様にもとづくルールも理解しておく必要があります。
意外に知らない人もいるため、ここで内容を見ておきましょう。
top や left プロパティなどで位置を指定する
/* NG */
.sticky-item {
position: sticky;
}
/* OK */
.sticky-item {
position: sticky;
top: 50px;
}
position: sticky でスクロールによる位置固定をするためには、位置指定のプロパティを指定しないと効きません。位置指定のプロパティが固定の条件となっているからです。
absolute や fixed の場合、top や left などの位置指定がなくても効果は適用するため(要素が浮かび上がり、HTMLのルールから外れる)、同じ感覚で使わないように覚えておきましょう。
スティッキーコンテナ内に高さを持たせる
少々わかりづらいかもしれませんが、サイドバー内の子要素にもスティッキーアイテムを設定している例です。
コンテンツ量も多く、サイドバー自体の高さがある場合、目標地点までスクロールできるため固定されています。
しかし、コンテンツ量が減り、サイドバー自体の高さがなくなると、スクロール量が足りずに固定されません。
top などで指定した位置と、スクロールできる高さの関係性も意識する必要があります。
先祖要素にoverflowプロパティが指定されていないか確認する
先祖要素とは、親要素を含めた上階層にある要素すべてを指します。
position: stickyを指定したスティッキーアイテムの先祖要素に、overflowプロパティを指定した要素がある場合、固定の指定が効きません。
<div class="outer">
<div class="container">
<div class="kv">key-Visual</div>
<header class="sticky-header">
<nav>
<ul class="nav-list">
<li>nav</li>
<li>nav</li>
<li>nav</li>
<li>nav</li>
<li>nav</li>
</ul>
</nav>
</header>
<!-- コンテンツエリア -->
</div>
</div>
sticky-headerクラスに position: sticky を指定した場合、outerクラスとcontainerクラスが先祖要素に該当します。
.container {
overflow: hidden;
}
.sticky-header {
position: sticky;
top: 0;
}
上のように、先祖要素にoverflowプロパティが指定されている場合、stickyの位置固定が効かなくなります。※値がvisible の場合は問題ありません。
横スクロール防止など、全体のコンテナ要素などにoverflowプロパティを指定することはよくあることなので、注意しておきましょう。
position: sticky パターン別トラブルシューティング
よく見かける「なぜか sticky が効かない」パターンと、その原因をいくつかご紹介します。
Flexboxで実装した左右のサイドバーが追従しない
上で紹介した追従サイドバーと同じようなデザインですが、CodePenで見るとアイテムが固定されていません。
<div class="container">
<div class="main"></div>
<aside class="sticky-aside"></aside>
</div>
.container {
display: flex;
}
.main {
background-color: #e0e4eb;
}
.sticky-aside {
position: sticky;
top: 0;
}
containerクラスにFlexboxをあて、片方にposition: sticky を指定。
Flexboxで横並びにすると、隣り合うアイテムの高さはコンテナいっぱいに伸びます。(align-itemsの初期値、stretchが適用する)
高さが同じになる=上の場合だとサイドバーの高さがmainクラスと同じになります。(画像ではsticky-asideクラスに青色のoutlineを適用)
隣り合う要素(mainクラス)の高さが同じということは、スクロールしても常に同じ長さが表示されます。
position: sticky を指定していないmainクラスが常に隣り合っている状態とも言えるため、片側の要素だけ固定されることはありません。
そのため、position: sticky を適用させる要素には高さを指定し、差をつける必要があります。
.sticky-aside {
position: sticky;
top: 0;
height: 1px;
}
高さは隣り合う要素と差が出ればいいため、1pxでも指定すれば解消されます。
ページ内リンクのスクロール位置がズレる
追従ヘッダーでページ内スクロールする際、ヘッダーの高さが考慮されないため、リンク先とずれた位置に移動してしまいます。(リンク先と被ってしまう)
こちらの現象は、リンク先にCSS2行指定することで対処できます。
h2 {
padding-top: 50px;
margin-top: -50px;
}
padding-topで要素に対して余白を設けますが、margin-topにマイナス指定することで要素間の余白をなくします。
組み合わせることで、スクロール位置の調整を行えます。
JavaScript(jQuery)など使えばこのあたりも一括設定できたり、スクロールに細かいアニメーションもつけられます。
tableスクロール時に固定列(行)の枠線が消えてしまう
tableタグで列や行の数が多い場合、コンテナ幅におさめず大きさを保ったままスクロールで表示させることもあるかと思います。
上のCodePenでは、左側のstickyと記載された列に position: sticky とleft: 0 を指定することで、その列だけスクロールされず固定されています。
※先祖要素のtable-containerクラスにoverflow-x: scrollが指定されていますが、仕様的にここではstickyの妨げになりません。仮に上位置にsticky指定して、overflow-yとした場合はうまく固定されません。
しかし、固定時に左右のボーダー(罫線)が消えています。
これはセルの境界線を調整するborder-collapse プロパティで、collapse(セルの境界線を空けないで表示)の値を指定していることが原因です。
<div class="container">
<div class="table-container">
<table>
<tr class="table-row">
<th class="sticly">sticky</th>
<th>テーブルヘッド</th>
<th>テーブルヘッド</th>
</tr>
<tr class="table-row">
<th class="sticly">sticky</th>
<td>ダミー</td>
<td>ダミー</td>
<td>ダミー</td>
</tr>
<tr class="table-row">
<th class="sticly">sticky</th>
<td>ダミー</td>
<td>ダミー</td>
<td>ダミー</td>
</tr>
<tr class="table-row">
<th class="sticly">sticky</th>
<td>ダミー</td>
<td>ダミー</td>
<td>ダミー</td>
</tr>
</table>
</div>
</div>
/* コンテナ幅 */
.container {
width: 400px;
}
/* テーブル全体 */
.table-container {
overflow-x: scroll;
}
/* テーブル全体の装飾 */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* 固定列 */
.sticly {
background-color: #e0e4eb;
position: sticky;
left: 0;
}
構成に必要な箇所のみ抜粋しました。tableタグにあるborder-collapse: collapse を削除した状態が下のCodePenです。
今度はスクロール時も、固定列のボーダー(罫線)が消えずに表示されています。
JavaScript(jQuery)を使った方がいい場合ってある?
上でも触れたように、ブラウザ対応のためにはPolyfillが必要です。
もしくは、position: sticky を使わずにJavaScriptのプラグインで対応する方法もあります。
- sticky-sidebar(プラグイン)
- ・sticky-sidebar (GitHub)
- Sticky-kit(プラグイン)
- ・Sticky-Kit | jQuery plugin for sticky elements
ブラウザの仕様でうまく効かない場合、JavaScriptを使ったほうが安定するでしょう。
また、固定させるだけでなく、要素のデザインに変化を加えたい場合などもJavaScriptが必要です。
シンプルにスティッキー効果だけを得たい場合は position: sticky を使い、それ以上の装飾が必要な場合はJavaScript(jQuery)を使用するといいでしょう。
まとめ
今後使用頻度が高まる可能性のある「position: sticky」について解説しました。
HTMLやCSSのコード状況次第ではうまく効かないパターンなどもあるため、ある理解仕様の理解は必要ですが、やはりCSSだけで実装できる手軽さが魅力です。
IEのサポート終了にともない、新しいCSSプロパティを導入するチャンスですので、ぜひ当記事を参考に取り入れてみてください。
PENGIN BLOGでは、今後も新しいCSSプロパティについても解説していくので、よかったらチェックしてみてください!