CSS
JavaScript
UX
CSS3
UI

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

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; の勉強程度に考えてください。