ChakraUIのCardをリスト表示したら遅かったので改善してみた
はじめに
こんにちは。
個人開発でWebマンガ一覧をChakraUIのCardコンポーネントを使って表示したら思った以上に遅かったので、色々と改善を行いました。
今回はその振り返りを行ってみようと思います。
個人開発で作ったマンガのキュレーションサイト、よかったら見てください!
今回検証で利用する環境について
現在は該当箇所を修正してしまっており、元に戻すのが手間だったので今回は当時の再現用デモサイトを用意しました。
こちらを使って振り返りをしていきます。
どんな問題だったか?
記事タイトルにある通り、初期表示時やマンガ一覧の更新時といったCardのリスト表示がとてもモッサリで操作感が最悪でした。
該当の画面
当時のパフォーマンス
Scripting: 1450ms
とjavascriptの処理に多くの時間が掛かっています。
原因について
試しにChakraUIのCardコンポーネントをdivタグのテキストに置き換えたところ、上記のScriptingが大幅に解消したことを確認できたので、ChakraUIが原因であることを絞れました。
いざ改善!
原因の大枠については把握できたので、対策を考えて改善していきます。
改善その①
ChakraUIが悪いのであれば、使わなければ良いんじゃない?
ということで、ChakraUIのCardコンポーネントをプレーンなHTML/CSS
に置き換えてみました。
デモサイトでのBefore/After
プレーンなHTML/CSS
とtailwind
バージョンを用意して比較してみましたが、結局やってることは同じなので、同様のパフォーマンスが出ました。
-
改善前のデモサイト
Scripting: 960ms
- ソースコード
-
改善後のデモサイト(プレーンなHTML/CSS)
Scripting: 90ms
- ソースコード
-
改善後のデモサイト(tailwind)
Scripting: 90ms
- ソースコード
実際のサイトに反映させた結果
Scripting: 640ms
と改善前に比べると800ms
ほど改善されています。
ただ実は個人開発のサイトの方ではChakraUIの依存を100%引き剥がすことはできなかったため、パフォーマンスの課題があります。
改善その②
考えてみると現状は画面に映っていないカードについてもレンダリングしていました。
レンダリングするコンポーネントの量を必要最低限にすれば、それだけコストが無くなるのでは?
ということで、ユーザーが見えている部分だけをレンダリングにするぞ。
方法
IntersectionObserverを使って、要素がユーザーが見ている画面内に入っているかを検知します。
下記のようなHooksを作成しました。hasBeenInView
は一度画面内に入ったかどうかを判定し、ピューポートから外れても再描画しないよう制御するための戻り値です。
import { type RefObject, useEffect, useState } from "react";
export const useInView = (ref: RefObject<HTMLElement>, rootMargin = "0px") => {
const [inView, setInView] = useState(false);
const [hasBeenInView, setHasBeenInView] = useState(false);
useEffect(() => {
if (!ref.current) {
return;
}
const observer = new IntersectionObserver(
(entries) => {
const isIntersecting = entries.some((entry) => entry.isIntersecting);
setInView(isIntersecting);
if (isIntersecting && !hasBeenInView) {
setHasBeenInView(true);
}
},
{ rootMargin },
);
observer.observe(ref.current);
return () => {
observer.disconnect();
};
}, [ref, rootMargin, hasBeenInView]);
return { inView, hasBeenInView };
};
今回は自作していますが、もっとリッチなuseInViewもあるので、そちらを使っても良いかもしれません。
デモサイトでのBefore/After
改善①よりは効果は低いですが、それでも大きく改善されていることが分かります。
-
改善前のデモサイト
Scripting: 960ms
- ソースコード
-
改善後のデモサイト(ビューポートに入った要素だけレンダリング)
Scripting: 270ms
- ソースコード
改善その③
ChakraUIのバージョンアップです!
この記事を書いている途中で気付いたのですが、どうやら僕が当時使っていたChakraUIのバージョンv2.9.3
がパフォーマンス劣化が酷く、問題になっていました。
issuesを読むと、無事に次のバージョンでパフォーマンスのデグレが解消されていました。
デモサイトの動作確認している時に、改善されたバージョンを使っていたため、パフォーマンスが想定していたより悪くならず、調べ直してやっと気づけました。
デモサイトでのBefore/After
改善①に比べると弱いですが、改善②と同様の効果がありました。
-
デモサイト
- 改善前のバージョン:
"@chakra-ui/react": "2.9.3"
Scripting: 960ms
- 改善後のバージョン:
"@chakra-ui/react": "2.10.3"
Scripting: 270ms
- 改善前のバージョン:
すべての改善を反映した結果
改善その②とその③について、当時は個別の効果を測定できていませんでした。
なので、すべてをサイトに反映した現在のパフォーマンスを最後に載せます。
最初のScripting: 1450ms
に比べるとScripting: 330ms
と大幅に改善できたので、満足の行くパフォーマンス改善ができました。
最後に
最初は「ChakraUIはこんなにパフォーマンスが悪いのか。。。」と絶望していたのですが、結局運悪くハズレのバージョンを引いていたので、そこまでChakraUI悪くなかったです。
最近v3もリリースされてパフォーマンスも更に良くなったらしいので、いつかチャレンジしてみたいです。
今回の経験のおかげでChakraUI関係なく汎用性があるパフォーマンス改善のアイデアについて引き出しが増えたので良い経験になりました。
最後に、tailwind強い!
Discussion