polyfillを使用したContainer Queryの導入
こんにちは。株式会社スペースマーケットでフロントエンドエンジニアをしておりますwado63です。
みなさん、Container Queryを使用したことはありますでしょうか。
親要素大きさに合わせてCSSのスタイルを切り替えることができる、新しいCSSの指定の仕方です。
Media Queryのように画面の大きさ(viewport)に依存しないため、とても汎用的なコンポーネントを作成できます。
とくに複合的な要素を持つコンポーネントのレイアウトで、使えると使えないとでは実装の難易度が変わりますね。
しかしながらContainer Queryは新しい機能のため、対応しているブラウザが限られます。
ios safariの対応が16.1からとなっているので、このままでは使うことができません。
caniuse CSS Container Queries (Size)
ということでpolyfillの出番です。
Please note that this polyfill is now in maintenance mode, as of Nov, 2022. We are not planning to add more features or enhancements.
メンテナンスモードに入ったとのことですが、ios safari 16.1以降のシェアが増えてくれば外せるようになるので、このまま使います。
このpolyfillを入れることでios safari 13.4以降からContainer Queryを利用できます。
以前は:is()
を内部で使用されていたのでios safari 14.1以降がサポート対象だったのですが:where()
を使うようになったおかげで、safari 13.4以降がサポート対象となり、現実的に使える状態になりました。
導入方法
npmとCDNの2種類の方法が用意されています。
npmの方が後述のdynamic importが利用できるのでこちらを使います。
npm install --save container-query-polyfill
使い方としては以下のようにpolyfillが必要か判断してdynamic importで読み込みます。
副作用があるmoduleなのでimportだけで良いんですね。
const supportsContainerQueries = "container" in document.documentElement.style;
if (!supportsContainerQueries) {
import("container-query-polyfill");
}
"container" in document.documentElement.style;
これはContainer Queryに対応したブラウザかどうかを判別するコードなのですが、polyfill内部で使われている処理を流用したものです。
なので、CDNで直接読み込んだ場合でも同じ条件でpolyfillが適用されるかどうか判別されます。
dynamic importで読み込んだ方がpolyfillの処理が不要な場合にbundle sizeが抑えられるので効率的です。
問題点
javascriptで動いているためpolyfillが動作するような環境の場合に、初めにpolyfill動作前のスタイル、その後polyfill動作後のスタイルというようにレイアウトが一瞬動いてしまいます。
クライアントサイドのみで完結するSPAのアプリケーションであればあらかじめpolyfillを読み込ませておけば問題ないのですが、ファーストビューのHTML・CSSを返すような場合この動作を考慮する必要があります。
とはいうものの完全に避けることはできないため、現状はファーストビューに含まれるコンテンツに対してContainer Queryは使わないといった運用でカバーするしかないかなと考えております。
もしファーストビューを避けて使う場合であってもモバイルファーストでCSSを当てておくことが必要です。
モバイル向けのスタイルは基本的にレスポンシブに作られるので、Container Queryが動作する前にbodyを突き抜けて横のスクロールが発生する、といったことは少なくなります。
これらの点に注意すれば、Container Queryを使って問題ないでしょう。
スペースマーケットではios safariユーザーのうち、ios safari 16.1以降のユーザーが50%程度でしたのでpolyfillの挙動に注意しながら、気をつけて使う。ということになりそうです。
その他に気を付けることとしてpolyfillが動作した際に以下のerrorが出てきます。
ResizeObserver loop completed with undelivered notifications.
こちらに関してはpolyfillのREADMEに問題ないと記載されていますが、エラーレポート用のツールにログが載ってしまう場合はフィルタリングしておくと良いです。
おまけ Chakura UIでContainer Queryを使用する
スペースマーケットではChakra UIを使用しているため、Chakra UI上でContainer Queryを使用できないか調査しました。
Chakra UIでContainer Queryを使う場合はChakra UIが依存しているemotionのアップデートが必要となります。
今年の10月頃のissueです。
正確にはemotionが依存しているstylisというライブラリのversionが4.1.3以上であれば動作します。
emotionをアップデートしたあとはChakra UIのsx
を使用することでContainer Queryが利用できます。
<chakra.div
border="6px solid #ddd"
borderRadius="8px"
sx={{
containerType: 'inline-size',
}}
>
<chakra.img
src="https://placekitten.com/640/360"
alt="cat"
maxW="100%"
/>
<chakra.h2
fontSize="1em"
sx={{
'@container (min-width: 200px)': {
fontSize: '2em',
},
}}
>
Card title
</chakra.h2>
<chakra.p>Card content</chakra.p>
</chakra.div>
polyfillは動作した際、あらかじめに埋め込まれている<link>
, <style>
のタグ対して操作するのと、MutationObserverを使ってそれらに変更があった際に処理を行います。
emotionをProduction buildで使った際はstylesheet.insertRule(rule[, index])
という形で、stylesheetを操作するAPIを使用しstyleの変更をするので、後から追加した要素に対してpolyfillが効かないということが分かりました。
SSRで表示した要素に関しては、HTMLに静的に<style>
が埋め込まれているので動作しているのですが、CSRで表示する要素には効かないというのはなかなか気づけませんでした。
またCSRでemotionがstylesheetを操作するために、空の<style>
タグを埋め込むのですが、その埋め込んだタイミングでpolyfillが働き、CSRで一番最初に表示される要素のスタイルを削除してしまうというバグもあります。
以上のことからemotionではpolyfillを使用することが出来ませんでした。
まとめ
- polyfillを使うことでContainer Queryをほとんどのブラウザで動作させることができる。
- ただし、ios 16.1以上のユーザーがまだそれほど多くないので一瞬レイアウトが動くことを考慮しておくことが必要
- emotionでpolyfillは使用できない。
宣伝
スペースマーケットでは、一緒にサービスを成長させていくメンバーを募集中です。
開発者だけでなくビジネスサイドとも距離感が近くて、風通しがとてもよい会社ですのでとても仕事がしやすいです。
とりあえずどんなことしているのか聞いてみたいという方も大歓迎です!
ご興味ありましたら是非ご覧ください!
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion