👒
TS/CSSでスクロール時にヘッダーをしまう
はじめに
ページを上にスクロールすると上に完全にしまわれ、下にスクロールすると上から完全に出てくるヘッダーを作りました。
実装
ヘッダーコンポーネントに対し、スクロールが発生したら hide
のスタイルを付けたり外したりしています。
layout.tsx
...
const Layout = ({ children }: { children: React.ReactNode }) => {
const mainRef = useRef<HTMLElement>(null);
// スクロール方向を判定するために直前のスクロール位置を保存
// ヘッダーが縦68pxなので、初期位置は上から68px
const [prevPosition, setPrevPosition] = useState<number>(68);
const [isHiddenHeader, setIsHiddenHeader] = useState<boolean>(false);
const onScroll = useCallback(() => {
if (!mainRef?.current) return;
const top = mainRef?.current.getBoundingClientRect().top;
// 直前の位置より上にいる、かつ-30pxより上の位置にいたらヘッダーをしまう
// 初期位置からすぐにしまうと、メインコンテンツの上部にたくさん空白ができてしまうので、0px〜-30pxの位置ではしまわないように
if (top < prevPosition && top < -30) {
setIsHiddenHeader(true);
} else {
setIsHiddenHeader(false);
}
setPrevPosition(top);
}, [prevPosition]);
// スクロールイベントを取得
useEffect(() => {
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, [onScroll]);
return (
<>
<Header isHidden={isHiddenHeader} />
<main className={styles.main} ref={mainRef}>
{children}
</main>
<Footer />
</>
);
};
Header.tsx
...
type Props = {
isHidden?: boolean;
};
export const Header = ({ isHidden = false }: Props) => {
return (
// isHiddenが付与された時だけ隠す用のスタイルを追加
<div className={`${styles.component} ${isHidden && styles.hide}`}>
<Link href="/">
<Image
className={styles.logo}
src="/logowithtitle.png"
alt="logo image"
width={253}
height={32}
priority
/>
</Link>
</div>
);
};
Header.module.css
.component {
width: 100%;
height: 68px;
position: fixed;
top: 0px;
// topが変化したらアニメーションさせる
transition: top 0.3s;
z-index: 999;
}
.hide {
// ヘッダーの高さ分上に移動させる
top: -68px;
}
おわりに
記事の内容とは関係ありませんが、bool値の変数名にいつも迷います。
今回も「headerを隠すかどうか」を変数にしていますが、「ヘッダーを隠す」と英語にすると hideHeader
になって関数名っぽくなってしまい、「ヘッダーが隠れている」だと headerIsHidden
で is
が中間に来ていることが違和感・・・。
結局 isHeaderHidden
にして、英語の語順的には変な気がするけど is
を先頭に来させるという統一感でやりました。
逆にきれいな変数名にならないということは、変数名を作る基準が違っているのかもしれない。
今回は「ヘッダーを隠すかどうか」ではなく「上スクロールかどうか」で isScrolledUp
、もしくはbool値ではなく scrollDirection: 'UP' | 'DOWN' | 'NONE'
みたいに定数を返すものにしたらよかったか?
Discussion