LoginSignup
21
14

More than 5 years have passed since last update.

スクロールに合わせてヘッダーが積み重なっていく UI を作る

Posted at

position: sticky;display: contents; を使い、「スクロールに合わせて各コンテンツのヘッダーがページ上部に積み重なっていく UI 」を作ってみました。おまけで「ヘッダーをクリックすると、該当するコンテンツへスクロールする」機能も付けています。つまりこういうことです。
stacked-headers.gif
動くデモはこちら。
この UI 、何と呼ぶかわからないので、 Stacked Headers と勝手に呼んでいます。

Pros/Cons

先に言っておくと、使わない方が良いです(素直)。

Pros

  • あまり見かけないデザインのため、印象に残りやすい(かもしれない)
  • 通過した各コンテンツの先頭へ飛べる

Cons

  • 下へ行くほど読みにくくなる
  • ヘッダーが重なりすぎるとコンテンツがヘッダーで見えなくなってしまう
  • ブラウザサポートが心配
    • position: sticky;何とか使える状況
      • そもそもサポートされていなくても可読性に問題はないはず
    • display: contents;極めて厳しい状況 だが、 DOM を変更することで対応可能(ただし valid な構造ではなくなるはず)
  • ヘッダーをクリックしてスクロールする機能は JavaScript に頼らざるを得ない

説明

面倒なので詳細はコード読んでください(ぶん投げ)。
GitHub でも読めます。

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style>
  * {
    margin: 0;
    padding: 0;
  }
  body {
    font-family: sans-serif;
    font-size: 20px;
  }
    .page__header {
      padding: 16px;
    }
      .page__header h1 {
        text-align: center;
      }
      .content_container {

        /*
         * コンテンツヘッダーの高さ
         * WANT: JavaScript と値を共有する
         * WANT: 高さを可変にする(またまたご冗談を)
         */
        --content__header_height: 80px;

        position: relative;
      }
        .content {

          /*
           * この要素を存在しないものとして扱う
           * 結果、 .content__header の position: sticky; が、
           * .content ではなく、 .content_container に紐づく
           */
          display: contents;

        }
          .content__header {
            cursor: pointer;
            display: block;
            position: -webkit-sticky; /* Safari 用 */
            position: sticky;
          }

          /* 積み重なる位置の指定 */
          .content:nth-child(1) .content__header {
            top: 0;
          }
          .content:nth-child(2) .content__header {
            top: var(--content__header_height);
          }
          .content:nth-child(3) .content__header {
            top: calc(var(--content__header_height) * 2);
          }
          .content:nth-child(4) .content__header {
            top: calc(var(--content__header_height) * 3);
          }
          .content:nth-child(5) .content__header {
            top: calc(var(--content__header_height) * 4);
          }

            .content__header h2 {
              color: #ffffff;
              line-height: var(--content__header_height);
              padding: 0 32px;
              height: var(--content__header_height);
            }
          .content__body {
            color: #ffffff;
            padding: 0 16px 16px;
          }
          .content:nth-child(1) .content__header,
          .content:nth-child(1) .content__body {
            background-color: #c04040;
          }
          .content:nth-child(2) .content__header,
          .content:nth-child(2) .content__body {
            background-color: #c0c040;
          }
          .content:nth-child(3) .content__header,
          .content:nth-child(3) .content__body {
            background-color: #40c040;
          }
          .content:nth-child(4) .content__header,
          .content:nth-child(4) .content__body {
            background-color: #40c0c0;
          }
          .content:nth-child(5) .content__header,
          .content:nth-child(5) .content__body {
            background-color: #4040c0;
          }
    .page__footer {
      padding: 64px 0;
    }
      .page__footer small {
        display: block;
        text-align: center;
      }
</style>
<header class="page__header">
  <h1>Stacked Headers</h1>
</header>
<div class="content_container">
  <section class="content">
    <a class="content__header" onclick="scrollToContent(1)">
      <h2>Content 1</h2>
    </a>
    <p id="content__body--1" class="content__body">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </section>
  <section class="content">
    <a class="content__header" onclick="scrollToContent(2)">
      <h2>Content 2</h2>
    </a>
    <p id="content__body--2" class="content__body">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </section>
  <section class="content">
    <a class="content__header" onclick="scrollToContent(3)">
      <h2>Content 3</h2>
    </a>
    <p id="content__body--3" class="content__body">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </section>
  <section class="content">
    <a class="content__header" onclick="scrollToContent(4)">
      <h2>Content 4</h2>
    </a>
    <p id="content__body--4" class="content__body">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </section>
  <section class="content">
    <a class="content__header" onclick="scrollToContent(5)">
      <h2>Content 5</h2>
    </a>
    <p id="content__body--5" class="content__body">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </section>
</div>
<footer class="page__footer">
  <small>&copy; 2019 mimonelu</small>
</footer>
<script>
  const CONTENT_HEADER_HEIGHT = 80 // WANT: CSS と値を共有する
  const scrollToContent = (index) => {
    window.scrollBy({
      top: document.querySelector(`#content__body--${index}`).getBoundingClientRect().top - (index * CONTENT_HEADER_HEIGHT),
      behavior: 'smooth'
    })
  }
</script>

Sass とか Vue とか使えば、ギュッと圧縮できますね。

おわりに

ヘッダーをクリックしてスクロールする機能が不要だったり、 HTML が invalid でも良かったりするのであれば、よりシンプルなコードになるはずです。
さらに言うと、積み重ならなくても良いのであればもっとシンプルなコードになるし、その方が UX 的にもよろしいので、この記事は position: sticky;display: contents; の勉強程度に考えてください。

21
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
14