SvelteKitで画面サイズごとにコンポーネントを出し分ける
概要
SvelteKitでSSR時できるだけチラつきなしに画面ごとにコンポーネントを出し分ける
注意
SSR時のみ考慮します。SSG(SG)時やキャッシュのことは考えません。
結論
最初に書いてしまうのもアレですが、CSSで切り替えた方が多分いいと思います。
ソース
なぜ作ろうと思ったか
- デバイスごとに表示するコンポーネントを分ける場合、CSSで切り替えてしまうと不要なHTMLが残ってしまってパフォーマンスに影響があるのではないかと考えたため
-
<svelte:window bind:innerWidth />
を使用して切り替えるとinnerWidth
がundefinedの間のチラつきがあるため気になる- PCでウィンドウを縮めてモバイルサイズで表示しているユーザー(自分)にチラつきを見せたくない
この辺がモチベーションです。
説明
コードはSvelteLabに置いているので手元でみてみてください。
レアケースへの対応
まず、CSS側はmedia queryを使用しているためそれと合わせるためにmatchMediaを使用します。
matchMediaを見てスマホの場合とタブレット以上の場合でコンポーネントを出し分けたいです。
なので基本的にはサーバー側でユーザーエージェントを見れば「大体」問題ないです。
上に書いたようにPCでウィンドウを縮めてモバイルサイズで表示しているユーザーがいるので、そのユーザー向けにスマホサイズでみているかどうかをCookieに保存します。
つまり1回目は諦めて、2回目以降コンポーネントの切り替えのチラつきを見せないようにします。
hooks.server.ts
MediaQuery.svelteにロジックを置いています。
hooks.server.tsでは、MediaQuery.svelteでexportしたsetCookieIsMobile
関数を使用してユーザーエージェントにモバイルっぽいテキストが含まれているかどうかをみて、cookieに真偽を書き込みます。
ただし、Cookieに値が書き込まれていない場合のみ書き込みます。
フロントエンド側でPCでモバイルサイズで閲覧した場合、すでにCookieにはtrueがセットされています。
この状態でユーザーエージェントをみてPCだった場合の値(false)を上書きしてしまうとCookieに値を保持している意味がないからです。
+layout.server.ts
MediaQueryコンポーネントにcookieに保存されたモバイルかどうかの値を渡すために、+layout.server.ts
からその値をexportします。
+layout.svelte
export let data;
let is_mobile = writable(data.sk_is_mobile)
setContext('is_mobile', is_mobile);
このようにして、+layout.server.ts
から受け取ったデータを初期値としてStoreを作成し、Contextを作成します。
あとはActionをsvelte:window
に渡して、matchMediaを監視するeventLisnerをくっつけてあげます。
<svelte:window use:mediaQuery={is_mobile} />
これで準備完了です。
MediaQuery.svelteのAction mediaQuery
window.matchMedia('(min-width: 768px)')
こちらを監視します。resize
イベントは重いので使いません。
handler
関数はstoreへの値の書き込みと、リロードされた時に以前みた画面サイズがスマホだったかどうかをCookieから取得できるようにするために/api/sk_is_mobile
を叩いてCookieに画面サイズがスマホかどうかを書き込んでいます。
これがあるおかげで、PCでスマホサイズで再度閲覧した時に画面がチラつきません。
(Cookieへの書き込みがまだない場合はユーザーエージェントを見ているので初回一回だけチラつきます)
MediaQuery.svelte
getContext
で+layout.svelte
で作成したContextを使用します。
ContextにはStoreを入れたので
const is_mobile = getContext<Writable<boolean>>('is_mobile');
is_mobile
はリアクティブな値になっています。($is_mobile
で呼び出せます。)
まとめ
- Cookieに以前みた画面サイズがスマホサイズかどうかを保持
- その値を初期値としてStoreを使用
- コンポーネントを出しわけ
という実装でした。
最初に書きましたが、CSSで出し分けた方が楽だしどこでも使えるので良さそうです。
ただし、CSSが読み込まれなかった場合はどっちも表示されてしまうので用途に合わせてやるのがいいかなぁと思います。
Discussion