概要
reactで無限スクロールするなら、ライブラリがたくさんある
https://github.com/danbovey/react-infinite-scroller
ただ、今回preactで開発していて、どうせならreactライブラリ使わずに簡単にできないかを考えた。
実装
下記の実装を作成。
import { h } from "preact";
import style from "./style.module.css";
/**
*
* @param loadMore scroll時に実施するmethod
* @param children スクロール対象の要素
* @param height スクロール範囲の高さ
* @param margin 下から何pxの部分でロードをかけるかの値。default:20
* @param hasMore 追加で読み込むかの判別flag。fefault:true
* @param pageStart 追加ロードで読み込むpage。default:0
* @returns
*/
const InfiniteScroll = ({
loadMore,
children,
height,
margin = 20,
hasMore = true,
pageStart = 0,
}: {
loadMore: (page: number) => void;
children: h.JSX.Element;
height: number;
margin?: number;
hasMore?: boolean;
pageStart?: number;
}) => {
const onScrollHandler: h.JSX.UIEventHandler<HTMLDivElement> = (e) => {
if (!hasMore) return;
if (
e.currentTarget.scrollHeight -
e.currentTarget.offsetHeight -
e.currentTarget.scrollTop <
margin
) {
loadMore(pageStart + 1);
}
};
return (
<div
class={style["scroll-component"]}
style={{ height: height }}
onScroll={onScrollHandler}
>
{children}
</div>
);
};
export default InfiniteScroll;
cssは下記の通り。
.scroll-component {
overflow-y: scroll;
}
loadMore
にはpageを受け取って追加読み込みする関数を渡す
children
はInfiniteScroll
の子要素としてセットし、無限読み込みしたいlistなどを配置する
height
はスクロールする要素の高さ
margin
は下から何pxの部分で追加読み込みするか決める値
hasMore
は追加読み込みするかどうかの条件
pageStart
は読み込むページの初期値。追加読み込みするときは、pageStart+1
のページを読み込む。
サンプル
下までスクロールされたら追加読み込みしてlistを増やすサンプルを示す。
サンプルコードなので、handlerやテストデータの作り方は雑です。
import h from "preact";
import { useState } from "preact/hooks";
import InfiniteScroll from "./infinite-scroll";
export const Sample = () => {
const initTestData = [
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
{ key: "TestKey", value: "TestValue" },
];
const [list, setList] =
useState<{ key: string; value: string }[]>(initTestData);
const [hasMore, setHasMore] = useState<boolean>(true);
const [currentPage, setCurrentPage] = useState<number>(0);
const loadMore = (page: number) => {
const testData = initTestData.map((data) => {
return { key: data.key + page, value: data.value + page };
});
setList([...list, ...testData]);
setCurrentPage(page);
};
const reset = () => {
setCurrentPage(0);
setList(initTestData);
};
return (
<div>
<button onClick={reset}>リセット</button>
<button
onClick={() => {
setHasMore(false);
}}
>
読み込み停止
</button>
<InfiniteScroll
loadMore={loadMore}
hasMore={hasMore}
margin={5}
pageStart={currentPage}
height={300}
>
<div style={{ border: "solid 1px" }}>
<ul>
{list.map((l) => (
<li>
{l.key}:{l.value}
</li>
))}
</ul>
</div>
</InfiniteScroll>
</div>
);
};
- 読み込み停止(hasMore=false)すると下まで行っても追加読み込みしない
まとめ
やってみたらすごい少ない行数で無限スクロールが実現できました。
また、ほぼreactと同じ使い方できるので、reactのライブラリを使わず自作でカスタムしたい人にも参考になるかもです。
ライブラリに頼りすぎるとコーディングスキル落ちそうだから簡単な実装程度なら自力で描いてかないとダメだね。