1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

すりガラスの sticky ヘッダを作る — backdrop-filter と畳み方

1
Posted at

こんにちは。小学生向けのニュースサイト、こどもニュースをつくっています。

このサイトのヘッダは上部に固定されていて、下段のナビゲーション行がすりガラスになっています。スクロールするとその行だけが畳まれます。

作るときに引っかかった点をまとめます。

backdrop-filter は背景が透けていないと効かない

すりガラスの指定自体は 2 行です。

backdropFilter: 'blur(8px) saturate(135%)',
WebkitBackdropFilter: 'blur(8px) saturate(135%)',

blur だけだと、ぼかした結果が眠い色になります。saturate を足して彩度を上げると、下の色が透けている感じが出ます。数値は見ながら決めました。

引っかかったのは、背景色の扱いでした。

bgcolor: 'rgba(255, 255, 255, 0.00001)',

ほぼ透明な白を指定しています。完全に透明(transparent)だと、環境によって backdrop-filter が効かないことがあります。背景のレイヤーが作られないためです。

わずかでも背景色があると、合成のレイヤーができて効きます。見た目には影響しない値を入れておく、という回避です。

-webkit- 付きも書いています。この指定は接頭辞が必要な環境がまだあります。

上段は不透明にする

このサイトのヘッダは 2 段構成です。上段はサイト名を出す色付きのバーで、こちらは不透明にしています。

すりガラスは、下に何かがあるときにきれいに見えます。ページの最上部では、下にあるのはページの地色だけなので、ぼかす対象がありません。

上段は常に見えているので、不透明の色を置いたほうが締まります。下段はスクロールしたときに本文の上に重なるので、そこですりガラスが効きます。

「全部すりガラス」にすると、効く場所と効かない場所が混ざって統一感が出ませんでした。効果が生きる場所だけに使う、という判断です。

畳み方: 高さを 0 にする

スクロールしたときは、下段を畳みます。

minHeight: hidden ? 0 : { xs: , sm:  },
maxHeight: hidden ? 0 : { xs: , sm:  },
opacity: hidden ? 0 : 1,
overflow: 'hidden',
visibility: hidden ? 'hidden' : 'visible',
transition: 'max-height 180ms ease, min-height 180ms ease, opacity 140ms ease, visibility 180ms ease',

高さを 0 にして、overflow: hidden で中身を隠します。display: none にすると遷移が効かないので、高さのアニメーションで畳みます。

visibility も切り替えているのは、畳んだ状態でも中身がフォーカスできてしまうのを防ぐためです。高さが 0 でも、要素は存在します。タブ移動で「見えないリンク」にフォーカスが移ると、どこにいるのか分からなくなります。

aria-hidden も同時に付けています。

aria-hidden={hidden}

読み上げソフトからも隠す、という指定です。視覚的に隠すのと、支援技術から隠すのは別の指定になります。

遷移の時間を分ける

transition で、プロパティごとに時間を変えています。

max-height 180ms, min-height 180ms, opacity 140ms, visibility 180ms

透明度を少し速くしています。畳むときは先に薄くなってから縮むので、動きが自然に見えます。

全部を同じ時間にすると、縮みながら薄くなって、最後にかくっと消える感じになります。数十ミリ秒の差ですが、印象が変わりました。

何をもって「隠す」か

スクロールの向きで畳むかどうかを決めますが、素朴に実装すると細かく反応しすぎます。少し下げただけで隠れ、少し上げただけで出てきます。

判定には「折り返した量」を使いました。

/** 切り替えに必要な移動量(px)。畳む対象より大きいこと。 */
export const HIDE_DEADBAND = 72;
/** 先頭付近では常に出す(ここより上では隠さない)。 */
export const HIDE_SHOW_AT_TOP = 80;

ある程度の量を動かして初めて切り替わります。この量は、畳む対象の高さより大きくしています。小さいと、隠れた瞬間にレイアウトが動いて、その動きがまた判定を引き起こす、というループが起きます。

ページ先頭の付近では常に表示します。一番上まで戻ったのにナビが隠れている、という状態を避けるためです。

判定の部分は純粋関数に切り出して、テストしています。

export function nextHideState(input: {
  y: number; anchor: number; hidden: boolean;
  deadband?: number; showAtTop?: number;
}): { hidden: boolean; anchor: number } {  }

スクロールの状態遷移は、実際に動かして確認するのが難しいものです。入力と出力の関数にしておくと、境界の挙動をテストで固定できます。

スクロールの監視は間引く

scroll イベントは大量に発火します。そのたびに状態を計算すると重くなります。

requestAnimationFrame で間引くフックを作って、ヘッダと目次で共有しています。フレームごとに 1 回だけ計算する形です。

同じ間引きが複数箇所で要るなら、フックにまとめておくと実装が揃います。

まとめ

  • backdrop-filterblursaturate を足すと色が生きる
  • 完全に透明な背景だと効かないことがある。わずかな背景色を入れて合成レイヤーを作る
  • すりガラスは下に何かがある場所でだけ使う。全部に掛けると統一感が出ない
  • 畳むときは高さを 0 にする。visibilityaria-hidden も同時に切り替える
  • 遷移の時間はプロパティごとに変えると自然に見える
  • スクロールでの切り替えは、畳む対象より大きい移動量を閾値にする。小さいとループする
  • 判定は純粋関数に切り出してテストする
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?