CSS

position: sticky入門|貼り付く要素の作り方を実例デモで解説(前編)

みなと

この記事は、CSSの position: sticky の基本と使い方を解説する前編です。「sticky が効かない」「思ったところで貼り付かない」といったハマりどころは、後編で詳しく扱います。

position: sticky は、CSSだけでスクロールに追従する要素を作れるプロパティです。JavaScriptで scroll イベントを使うような実装と比べてシンプルで、動きも滑らかになります。

この記事では、position: sticky の基本から、よく使う使用例まで、順を追って解説していきます。

position: sticky とは?

position: sticky をひと言で表すなら、「スクロールに合わせて、要素を画面の特定の位置に貼り付ける」プロパティです。

たとえば、下の動画のように、見出しが、最初は自然な位置にあり、画面上端に達すると貼り付き、次のセクションが来ると押し出されて入れ替わる動きが、CSSだけで作れます。

要素は、ふだんはふつうの要素としてスクロールで流れていきますが、貼り付いているあいだは position: fixed のように、その場に留まり続けます。

基本構文と動作の仕組み

基本構文

position: sticky を使うときは、位置を指定するプロパティ(top / bottom / left / right)のいずれかを必ず一緒に指定します。これが指定されていないと、position: sticky は何も起こりません。

みなと
みなと

実際、top か bottom を使うことがほとんどです。left / right は、横スクロールするテーブルの最初の列を貼り付けるときなど、限られたケースでしか出てきません。

最小サンプルはこちらです。

.sticky-element {
  position: sticky;
  top: 0;
}

これで、.sticky-element はスクロールに合わせて、画面の上端から 0px の位置で貼り付くようになります。

仕組み

position: sticky の動きをコントロールするために、押さえておきたいキーワードが2つあります。

  • スクロールコンテナ(scroll container)
  • 包含ブロック(containing block)

それぞれを順に見ていきます。

スクロールコンテナ:どのスクロールに反応するか

スクロールコンテナは、sticky 要素がどのスクロールに反応するかを決める祖先要素です。

ふつうのページの場合、スクロールコンテナは画面全体(viewport)になります。ただし、overflow プロパティに visible 以外の値(hidden / scroll / auto など)が指定された祖先要素が間にある場合は、その祖先要素がスクロールコンテナになります。

包含ブロック:どこまで動けるか

包含ブロックは、sticky 要素がどこまで貼り付いていられるか(活動範囲)を決める祖先要素です。多くの場合、一つ上の親要素が包含ブロックになります。

これが、position: sticky の動きで一番大事な原則です。sticky 要素は、親要素という枠の中だけで貼り付き、枠を出ると一緒に押し出されていきます。

だからこそ、position: sticky を思い通りに動かすコツは、「sticky 要素自体」だけでなく「親要素(枠)をどう作るか」も一緒に考えることになります。

多くの場合は包含ブロック=一つ上の親要素ですが、テーブル要素は例外で、<tr><thead> ではなく <table> 自体が包含ブロックになります(「3. 使用例」のテーブル例で改めて確認します)。

動きが切り替わる2つのタイミング

スクロールコンテナと包含ブロックの関係を整理すると、sticky 要素の動きは次のように切り替わっていきます。

position: sticky の動きを7段階で示した図解。①通常状態、②スクロール中、③topに到着、④topで固定開始、⑤押し出される直前、⑥押し出される、⑦包含ブロックと一緒に去る

動きを分解すると、2つのタイミングで切り替わっていることがわかります。

  • 貼り付くタイミング(上の図の③→④):
    sticky 要素が top / bottom で指定した位置に達したとき(top: 0 ならスクロールコンテナの上端、bottom: 0 なら下端)
  • 離れるタイミング(上の図の⑤→⑥):
    包含ブロックの端がスクロールで通り過ぎたとき(top: 0 なら包含ブロックの下端、bottom: 0 なら上端)
スワン
スワン

bottom の動きは、ページを下から上にスクロールしているところをイメージすると分かりやすいです。top と上下が反転しているだけで、考え方は同じです。

動きを確認

実際の動きで確認してみましょう。

上のデモは、3つのセクションが上から順に並んだ構成になっています。

  1. 通常セクションposition: sticky の指定がない、ふつうのコンテンツ
  2. sticky の親要素:薄い青の背景。この中に青いバー(sticky 要素)が入っている
  3. 通常セクション:①と同じく、ふつうのコンテンツ

スクロールすると、

  • ①では、コンテンツが普通に流れていく
  • ②に入ると青いバーが現れ、画面の上端に達した瞬間に貼り付く
  • ②の中をスクロールしている間、バーは画面の上端に留まり続ける
  • ②の下端まで来ると、バーは押し出されて見えなくなる
  • ③では、バーは存在しない(②の外なので)

という流れが確認できます。

コード

sticky を機能させている部分だけを抜き出すと、次のようになります。

<div class="sticky-parent">
  <p>...</p>
  <div class="sticky-bar">私は sticky 要素(親要素の下端で離れます)</div>
  <p>...</p>
</div>
.sticky-parent {
  /* ... */
}

.sticky-bar {
  position: sticky;
  top: 0;
  /* ... */
}

使用例

ここからは、よく使う使用例を見ていきます。それぞれデモとコードをセットで紹介します。

なお、これから紹介する5つの使用例で、それぞれ sticky 要素の「スクロールコンテナ」と「包含ブロック」がどうなっているかを、一覧でまとめておきます。

使用例スクロールコンテナ包含ブロック
例1:セクション見出しの貼り付き画面全体<section>
例2:bottom: 0 で下に貼り付く画面全体.product
例3:サイドバーの追従画面全体.layout-inner
例4:テーブルの thead 固定.table-wrapper<table>
例5:テーブルの一番左の列の固定.table-wrapper<table>

例1:セクション見出しの貼り付き

長いコンテンツを複数のセクションに区切るとき、各セクションの見出しを position: sticky にすると、スクロールに合わせて見出しが入れ替わる動きを作れます。冒頭のデモで見ていただいたパターンです。

コード

sticky を機能させている部分だけを抜き出すと、次のようになります。

<section class="topic-section">
  <div class="topic-head">
    <h2 class="topic-title">セクション1:はじめに</h2>
  </div>
  <div class="topic-content">
    <p>...</p>
  </div>
</section>
<section class="topic-section">
  <div class="topic-head">
    <h2 class="topic-title">セクション2:背景</h2>
  </div>
  <div class="topic-content">
    <p>...</p>
  </div>
</section>
<!-- ... -->
.topic-head {
  position: sticky;
  top: 0;
  /* ... */
}

ポイントは、.topic-head の親要素が、各 <section> になっていることです。「動作の仕組み」で見たとおり、sticky 要素は親要素の中だけで貼り付くので、次の <section> に入ると前の見出しが押し出され、新しい見出しが貼り付くようになります。

例2:bottom: 0 で下に貼り付く

top ではなく bottom で位置を指定すると、要素は画面の下端に貼り付きます。ECサイトの「カートに入れる」ボタンや、コーポレートサイトや LP の「お問い合わせ」「申し込む」ボタンなど、長いページでも CTA を常に押せる状態にしておきたいときによく使うパターンです。

コード

sticky を機能させている部分だけを抜き出すと、次のようになります。

<div class="product">
  <div class="product-body">
    <!-- 商品画像、商品情報、商品説明、特徴、仕様、レビューなど -->
  </div>
  <div class="product-cta">
    <a href="#">カートに入れる</a>
  </div>
</div>
.product-cta {
  position: sticky;
  bottom: 0;
  /* ... */
}

このデモでは、CTA ボタンを商品情報の一番後ろに置いています。こうすることで、商品情報をスクロールしている間はずっと画面下端にボタンが貼り付き、最後まで到達すると本来の位置に収まります。

例3:サイドバーの追従

2カラムレイアウトで、片方のカラム(サイドバー)をスクロールに追従させる使い方です。記事ページの目次や、商品ページの「カートに入れる」エリアなどでよく見るパターンです。

コード

sticky を機能させている部分だけを抜き出すと、次のようになります。

<div class="layout">
  <div class="layout-inner">
    <div class="layout-main">
      <!-- メインコンテンツ -->
    </div>
    <aside class="layout-side">
      <!-- 目次など -->
    </aside>
  </div>
</div>
.layout-inner {
  display: flex;
  align-items: flex-start; /* sticky を効かせるためのポイント */
  /* ... */
}

.layout-side {
  position: sticky;
  top: 40px;
  /* ... */
}

flexbox レイアウトでサイドバーを sticky にする場合、align-items: flex-start(または sticky 要素側に align-self: flex-start)を指定するのがポイントです。これを忘れると、サイドバーがメインカラムと同じ高さに自動で引き伸ばされてしまい、sticky が機能しなくなります。

なお、スマホなどの狭い画面では、サイドバーを記事の上や下に配置して sticky を無効化するのが定番です。今回のデモも、767px 以下ではサイドバーが記事の上に表示されます。

例4:テーブルの thead 固定

縦に長い表で、ヘッダー行(<thead>)をスクロール中も常に表示しておきたいときに便利です。データ件数の多い管理画面や、長い比較表などでよく使うパターンです。

ここから、スクロールコンテナと包含ブロックがこれまでの例とは違うパターンが出てきます。スクロールコンテナは画面全体ではなく、縦スクロールを担当するラッパー要素(.table-wrapper)です。包含ブロックも、「2. 仕組み」のアイコンボックスで触れたとおり、テーブル要素の例外として、<table> 自体になります。これは、CSS のテーブルレイアウトでは、<tr><thead> 自体は枠(包含ブロック)の役割を持たず、<table> 全体がその役割を担うためです。

ID 名前 部署 役職 状態
001山田 太郎エンジニアリングマネージャー在籍中
002鈴木 花子デザインリーダー在籍中
003佐藤 健マーケティングシニア在籍中
004田中 美咲営業マネージャー在籍中
005高橋 拓海エンジニアリングシニア休職中
006渡辺 由美人事リーダー在籍中
007伊藤 航エンジニアリングジュニア在籍中
008中村 香織デザインシニア在籍中
009小林 俊介営業シニア在籍中
010加藤 梨花マーケティングジュニア在籍中
011吉田 竜也エンジニアリングリーダー在籍中
012山本 千尋経理マネージャー在籍中
013松本 翔エンジニアリングジュニア在籍中
014井上 彩デザインジュニア休職中
015木村 大輔営業リーダー在籍中
016林 みなみカスタマーサポートシニア在籍中
017清水 優マーケティングシニア在籍中
018山口 悠エンジニアリングシニア在籍中
019池田 真央人事ジュニア在籍中
020阿部 隆経理リーダー在籍中
021森 一郎営業ジュニア在籍中
022橋本 綾デザインマネージャー在籍中
023石川 学カスタマーサポートリーダー在籍中
024前田 玲奈マーケティングマネージャー在籍中
025藤田 淳エンジニアリングマネージャー在籍中
026岡田 理沙人事シニア在籍中
027後藤 健太エンジニアリングジュニア在籍中
028近藤 さくら営業シニア休職中
029遠藤 健一カスタマーサポートジュニア在籍中
030青木 葵デザインシニア在籍中

コード

sticky を機能させている部分だけを抜き出すと、次のようになります。

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>名前</th>
        <th>部署</th>
        <!-- ... -->
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>001</td>
        <td>山田 太郎</td>
        <td>エンジニアリング</td>
        <!-- ... -->
      </tr>
      <!-- ... -->
    </tbody>
  </table>
</div>
.table-wrapper {
  max-height: 400px;
  overflow-y: auto;
  /* ... */
}

.table-wrapper thead th {
  position: sticky;
  top: 0;
  /* ... */
}

thead 自体ではなく th(各セル)に position: sticky を指定するのが、互換性の高い書き方です。最近のブラウザは thead への指定にも対応していますが、th に指定すれば古いブラウザでも確実に動作します。

なお、スマホなどの狭い画面では、列が多いとテーブルが収まらないので、今回のデモでは、テーブル自体を viewport より広くして、横にもスクロールできるようにしています。

みなと
みなと

今回のデモは sticky 要素(thead)がほぼ「ずっと貼り付いた状態」で、自然な位置〜貼り付き〜離れまでの動きはあまり体感できません。ただ、こうした画面全体以外のスクロール領域内に貼り付く動きは position: fixed では作れない、stickyならではの強みです。

例5:テーブルの一番左の列の固定

横に長い表で、行ラベルとなる一番左の列をスクロール中も常に表示しておきたいときに使うパターンです。スプレッドシートの「行見出し固定」のような動きで、列数の多いダッシュボードや、スマホで横スクロールが必要なテーブルでよく使います。

例4と同じく、スクロールコンテナは .table-wrapper(横スクロールを担当)、包含ブロックは <table> です。

選手名 チーム 試合 打席 打数 打率 安打 本塁打 打点 盗塁 四球 三振 OPS
山田 健太巨人142612540.3281771478185875.876
佐藤 翔阪神140598518.31516332105572115.945
鈴木 大輔ソフトバンク135588521.305159852356282.815
田中 涼介DeNA138580487.29814538115285135.985
高橋 達也日本ハム132560502.2911462892852148.872
中村 拓海ヤクルト141595530.2871521878125898.815
加藤 隆志ロッテ139590489.2821381568159582.848
吉田 慎吾広島136570518.2781441058224872.755
渡辺 颯太楽天140592498.2751371772988105.830
松本 雄大オリックス138578511.2721392495660118.838

コード

sticky を機能させている部分だけを抜き出すと、次のようになります。

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th>選手名</th>
        <th>チーム</th>
        <th>試合</th>
        <!-- ... -->
      </tr>
    </thead>
    <tbody>
      <tr>
        <th>山田 健太</th>
        <td>巨人</td>
        <td>142</td>
        <!-- ... -->
      </tr>
      <!-- ... -->
    </tbody>
  </table>
</div>
.table-wrapper {
  overflow-x: auto;
  /* ... */
}

.table-wrapper thead th:first-child,
.table-wrapper tbody th {
  position: sticky;
  left: 0;
  background: #fff; /* 重要:背景色は必須 */
  /* ... */
}

この例に限らず、sticky を使うときの注意点として、sticky にした要素には背景色を指定するのが基本です。指定しないと、スクロールで後ろにある他の要素が、sticky 要素を通して透けて見えてしまいます。

まとめ

この記事では、position: sticky の基本と、よく使う使用例を解説しました。

ポイントは下記です。

  • position: sticky は、スクロール位置に応じて要素を貼り付ける CSS プロパティ
  • 動かすには top / bottom / left / right のいずれかの指定が必須
  • 動きは「スクロールコンテナ」(どのスクロールに反応するか)と「包含ブロック」(どこまで動けるか)の2つの祖先要素で決まる
  • 思い通りに動かすコツは、「sticky 要素そのもの」だけでなく「親要素(枠)をどう作るか」も一緒に考えること
  • セクション見出しの貼り付き、bottom: 0 の下貼り付き、サイドバー追従、テーブルのスクロール追従など、JavaScript なしで色々な動きが作れる

position: sticky は CSS だけで完結する手軽さが魅力ですが、実は「指定したのに効かない」「思ったところで貼り付かない」というハマりどころも多いプロパティです。

そのあたりは、後編「position: sticky が効かない原因と対処法(仮)」 で詳しく扱う予定です。あわせてお読みいただければ、position: sticky をひと通り使いこなせるようになるはずです。

押していただけると励みになります!

ABOUT ME
みなと
みなと
フロントエンドエンジニア
東京のWeb制作会社で15年以上働いている現役フロントエンドエンジニアです。これまで、いろんなプロジェクトに関わりながら、フロントエンド開発やWebデザインに取り組んできました。このブログでは、今までの経験を活かして、Web制作に役立つ情報やノウハウをシェアしていきたいと思います。初心者の方から、現場で働く方まで、誰でも参考にできる内容をお届けしますので、ぜひ覗いてみてください。
記事URLをコピーしました