CSSのみで実装する指向性ヘッダーアニメーション
JavaScriptによるスクロール位置の監視が必須であった複雑なヘッダーアニメーションも、Scroll State Queriesを使えばCSSのみで実装することが出来る。
実装例
html { container-type: scroll-state;}header { transition: all 0.3s; @container scroll-state(scrollable: top) { position: sticky; inset: 0 0 auto; z-index: 1; } @container scroll-state(scrolled: bottom) { translate: 0 -100%; } @container scroll-state(scrolled: top) { translate: 0 0; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } @container not scroll-state(scrollable: top) { translate: 0 0; box-shadow: none; }}CSS Scroll State Queries
実装にはCSS Scroll State Queriesを使用する。詳しくはUsing container scroll-state queries - CSS | MDNを参照。
ブラウザのサポート状況は次の通り。
実装例の解説
まず、祖先要素にcontainer-type: scroll-state;を指定することで、ページ全体のスクロール状態をクエリできるようにする。例では、ルート要素に適用しているが、ヘッダーのラッパー要素に適用しても良い。
html { container-type: scroll-state;}下方向にスクロールしたときのアニメーションを@container scroll-state(scrolled: bottom)を使用して設定する。例では、スティッキーヘッダーに切り替わりつつ上にスライドアウトするようにしている。
header { transition: all 0.3s; @container scroll-state(scrolled: bottom) { position: sticky; inset: 0 0 auto; z-index: 1; translate: 0 -100%; }}上方向にスクロールしたときのアニメーションを@container scroll-state(scrolled: top)を使用して設定する。例では、影を付けてスライドインするようにしている。
また、@container not scroll-state(scrolled: none)を使用してスクロール発生後共通のスタイルを設定している。
header { transition: all 0.3s; @container not scroll-state(scrolled: none) { position: sticky; inset: 0 0 auto; z-index: 1; } @container scroll-state(scrolled: bottom) { translate: 0 -100%; } @container scroll-state(scrolled: top) { translate: 0 0; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }}これで指向性のあるヘッダーアニメーションを実装出来たが、この状態では次の問題がある。
- スクロールバーによる操作でscrolledが発火しないため、ヘッダーがスライドアウトした状態のままになり消失してしまう場合がある。
- 一度スクロールが発生すると、最上部に戻ってきてもヘッダーのスタイルが変わったままになる。例では影が付いたままになる。
これを制御するには次のようにscroll-state(scrollable: top)を利用して、上方向にスクロール可能かどうかを見れば良い。
まず、スクロールしたかどうかで共通のスタイルを設定するのは適切ではないため、scroll-state(scrollable: top)を利用して、上方向にスクロール可能状態かどうかで共通スタイルを適用するようにする。
また、not scroll-state(scrollable: top)を使用してスクロール可能状態ではない場合に、ヘッダーを通常のスタイルに戻すようにする。
これにより、ヘッダーの状態が操作方法に依らず復元可能になる。
header { transition: all 0.3s; @container scroll-state(scrollable: top) { position: sticky; inset: 0 0 auto; z-index: 1; } /* ... */ @container not scroll-state(scrollable: top) { translate: 0 0; box-shadow: none; }}