作成するもの
- トップに戻るボタン
- クリックするとページのトップに戻る。
- ページトップでは表示されない。
- 画面右下に固定表示される。
- 画面内に少しでもフッターが表示されると固定表示が解除され、
ボタンはフッターの上に表示される。
前提
- TypeScript のバージョンが 4.1.0 以上であること。(理由については こちら を参照)
- React がインストールされていること。
- Chakra UI がインストールされていること。
- Chakra UI icons がインストールされていること。
(ボタンのアイコンに自分で用意した画像を使用する場合はインストール不要)
成果物
import { Box } from "@chakra-ui/layout";
import { Fade, IconButton, ResponsiveValue } from "@chakra-ui/react";
import { ArrowLeftIcon } from "@chakra-ui/icons";
import React, { useState, useLayoutEffect, useRef } from "react";
interface ScrollToTopButtonProps {
footerHeight : number
position : ResponsiveValue<any>
}
const ScrollToTopButton = (props: ScrollToTopButtonProps) => {
const handleClick = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
<IconButton
aria-label ="Scroll to top"
icon ={<ArrowLeftIcon transform="rotate(90deg)" w="4" h="4"/>}
position ={props.position}
bottom ={props.position === "fixed" ? "8" : props.footerHeight + 20}
right ="8"
size ="lg"
borderRadius="full"
color ="white"
opacity ="30%"
bg ="gray.600"
_hover ={{ opacity: "70%", bg: "gray.500" }}
onClick ={handleClick}
/>
);
};
const Footer = () => {
const footerRef = useRef<HTMLDivElement>(null);
const [pageHeight , setPageHeight] = useState<number>(0);
const [windowInnerHeight, setWindowInnerHeight] = useState<number>(0);
const [scrollY , setScrollY] = useState<number>(0);
const [footerHeight , setFooterHeight] = useState<number>(0);
const [position , setPosition] = useState<ResponsiveValue<any> | undefined>("fixed");
const determineButtonPosition = () => {
setPageHeight(document.body.offsetHeight);
setWindowInnerHeight(window.innerHeight);
setScrollY(window.scrollY);
setFooterHeight(footerRef.current?.clientHeight ?? 0);
if (pageHeight - windowInnerHeight - scrollY <= footerHeight) {
setPosition("absolute");
} else {
setPosition("fixed");
}
};
useLayoutEffect(() => {
window.addEventListener("resize", determineButtonPosition);
window.addEventListener("scroll", determineButtonPosition);
return () => {
window.removeEventListener("resize", determineButtonPosition);
window.removeEventListener("scroll", determineButtonPosition);
};
});
return (
<Box as="footer" h= "30dvh" bg="red.50" fontSize="36px" ref={footerRef}>
height = 30dvh のフッター
<Fade in={scrollY > 0}>
<ScrollToTopButton footerHeight ={footerHeight}
position ={position}
/>
</Fade>
</Box>
);
};
export default Footer;
以下、コードの解説
0. ボタンの位置を制御するための方針
- ボタンの表示パターンは3つ
ボタンの表示パターン | 条件 |
---|---|
表示なし | 縦方向のスクロール量 = 0 |
画面右下に表示 | 縦方向のスクロール量 > 0 かつ 画面内にフッターが全く表示されていない |
フッターの上に表示 | 縦方向のスクロール量 > 0 かつ 画面内にフッターが少しでも表示されている |
- 上記パターンの判定に必要なパラメータ
変数名 | 説明 | |
---|---|---|
① | pageHeight | ページ全体の高さ |
② | windowInnerHeight | ブラウザに表示されているページの高さ |
③ | scrollY | 縦方向のスクロール量 |
④ | footerHeight | フッターの高さ |
- 画面内にフッターが表示されているかどうかの判定
-
➀ - ➁ - ➂
≦➃
⇒画面内にフッターが 表示されている -
➀ - ➁ - ➂
>➃
⇒画面内にフッターが 表示されていない
-
※ページ中間までスクロールした例
(赤矢印は ➀ - ➁ - ➂
であり、➀
は点線部の高さを含まない。)
1. ボタンのコンポーネント(ScrollToTopButton)
1.1 ScrollToTopButtonのprops
前述の footerHeight
に加え、ボタンの表示位置を制御する position
を持つ。
interface ScrollToTopButtonProps {
footerHeight : number
position : ResponsiveValue<any>
}
1.2 ScrollToTopButtonの実装
ボタンクリック時に handleClick
関数が呼び出され、ページトップまで滑らかに遷移する。
ボタンの表示位置は props
の position
の設定値に応じて、以下の通り変化させる。
-
position
が"fixed"
⇒ボタンは画面右下に固定表示 -
position
が"fixed"
以外
⇒ボタンはフッターよりも少し上に表示
const ScrollToTopButton = (props: ScrollToTopButtonProps) => {
const handleClick = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
<IconButton
aria-label ="Scroll to top"
icon ={<ArrowLeftIcon transform="rotate(90deg)" w="4" h="4"/>}
position ={props.position}
bottom ={props.position === "fixed" ? "8" : props.footerHeight + 20}
right ="8"
size ="lg"
borderRadius="full"
color ="white"
opacity ="30%"
bg ="gray.600"
_hover ={{ opacity: "70%", bg: "gray.500" }}
onClick ={handleClick}
/>
);
};
2.フッターのコンポーネント(Footer)
2.1 フックの定義
Reactのフックを使用して、フッターの情報を格納する変数と
ボタンの表示パターンの判定に必要な変数を定義する。
Reactのフックについては公式のリファレンスを参照。
const footerRef = useRef<HTMLDivElement>(null);
const [pageHeight , setPageHeight] = useState<number>(0);
const [windowInnerHeight, setWindowInnerHeight] = useState<number>(0);
const [scrollY , setScrollY] = useState<number>(0);
const [footerHeight , setFooterHeight] = useState<number>(0);
const [position , setPosition] = useState<ResponsiveValue<any> | undefined>("fixed");
2.2 ボタンの表示位置を決定する
ウィンドウのサイズ変更 または スクロールが発生する度に determineButtonPosition
を呼び出す。
ボタンの表示パターンの判定に必要な各パラメータを取得し、ボタンの表示位置を決定する。
const determineButtonPosition = () => {
setPageHeight(document.body.offsetHeight);
setWindowInnerHeight(window.innerHeight);
setScrollY(window.scrollY);
setFooterHeight(footerRef.current?.clientHeight ?? 0);
if (pageHeight - windowInnerHeight - scrollY <= footerHeight) {
setPosition("absolute");
} else {
setPosition("fixed");
}
};
useLayoutEffect(() => {
window.addEventListener("resize", determineButtonPosition);
window.addEventListener("scroll", determineButtonPosition);
return () => {
window.removeEventListener("resize", determineButtonPosition);
window.removeEventListener("scroll", determineButtonPosition);
};
});
2.3 Footerコンポーネントの定義
determineButtonPosition
関数がフッターの情報を取得できるよう、propsにref
を設定する。
また、ページトップでボタンが表示されないよう、ScrollToTopButton
コンポーネントを
<Fade>
の子要素として配置する。
return (
<Box as="footer" h= "30dvh" bg="red.50" fontSize="36px" ref={footerRef}>
height = 30dvh のフッター
<Fade in={scrollY > 0}>
<ScrollToTopButton footerHeight ={footerHeight}
position ={position}
/>
</Fade>
</Box>
);
Chakra UIの <Fade>
については公式のリファレンスを参照。
参考サイト
- Chakra UI 公式ドキュメント
- React公式ドキュメント
- React Hooks入門ガイド
- JavaScriptでHTML要素の高さを取得する方法