🤨

SvelteKitで画面サイズごとにコンポーネントを出し分ける

2023/09/10に公開

概要

SvelteKitでSSR時できるだけチラつきなしに画面ごとにコンポーネントを出し分ける

注意

SSR時のみ考慮します。SSG(SG)時やキャッシュのことは考えません。

結論

最初に書いてしまうのもアレですが、CSSで切り替えた方が多分いいと思います。

ソース

https://www.sveltelab.dev/zh7wfbnxvwljv08

なぜ作ろうと思ったか

  • デバイスごとに表示するコンポーネントを分ける場合、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