SvelteKit で CSSのコンテナクエリを試してみた

今回は2023年の2月に全てのブラウザに実装されたCSSのコンテナクエリをSvelteKitで試していきたいと思います
2023.03.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

西田@CX事業本部です

今回は2023年の2月に全てのブラウザに実装されたCSSのコンテナクエリを SvelteKit で試していきたいと思います

コンテナクエリとは?

任意の要素の幅を基準にしてCSSを切り替えられる仕組みです。同じような用途ではメディアクエリが使われています

メディアクエリが画面の表示領域のサイズに応じてCSSを切り替えるのに対して、コンテナクエリは、親要素や、祖先要素を基準にCSSを切り替えることができます

役立つシチュエーション

レスポンシブで切り替わるコンポーネントを親の要素の幅に応じて切り替えることができます

例えば、同じコンポーネントを1カラムと2カラムそれぞれに当てはめた場合のレスポンシブが容易に記述できるようになります

以下の画像(Gif)は同じコンポーネントを1カラムと2カラムでそれぞれ当てはめた例です。横幅が900px付近で2カラムの方でCSSが切り替わり、450付近で1カラムの要素が切り替わっています。このようなケースのCSSを簡潔に書くことができます

試してみる

今回は SvelteKit を使ってコンポーネントを作成し、一つのコンポーネントが、親の幅に合わせてレスポンシブするようにしていきます

1カラムと2カラムで同じコンポーネントを使用し、親の要素の幅で切り替わるようにします

SvelteKit プロジェクトを作成します

npm create svelte@latest container

今回は以下を選択しました

  • スケルトンプロジェクト
  • TypeScript
  • ESLint + Prettier
┌  Welcome to SvelteKit!
│
◇  Which Svelte app template?
│  Skeleton project
│
◇  Add type checking with TypeScript?
│  Yes, using TypeScript syntax
│
◇  Select additional options (use arrow keys/space bar)
│  Add ESLint for code linting, Add Prettier for code formatting

※ 執筆時点(2023年3月末)では Svelte はコンテナクエリがサポートされていなかったので、今回は有志の方が公開している fork 版のパッケージを使用します。 PR を出されており、これが本体に取り込まれれば以下の変更は必要ありません

package.json の overrides で有志の方が公開しているパッケージを使うように指定します

package.json

"devDependencies": {
  //... 省略
},
"overrides": {
	"svelte": "npm:@typhonjs-svelte/svelte@3.55.1-cq"
},

参考:

https://github.com/sveltejs/svelte/pull/8275

https://github.com/sveltejs/svelte/issues/6969#issuecomment-1467183195

ライブラリをインストールします

npm install

コンポーネント構成

今回は 1カラムと2カラムのレイアウトを用意して、同じ Card コンポーネントを置く構成で作成します

親コンポーネント

親になるコンポーネントです

1カラムと、2カラムのそれぞれのレイアウトに import した Card コンポーネントを、1カラムの方に一つ、2カラムの方にそれぞれ3つづつ置いてます

src/routes/+page.svelte

<script lang="ts">
  import Card from "./card.svelte";
</script>

<div class="one_col">
  <Card />
</div>
<div class="two_col">
  <div>
    <Card />
    <Card />
    <Card />
  </div>
  <div>
    <Card />
    <Card />
    <Card />
  </div>
</div>

<style>
  .two_col {
    display: flex;
  }

  .two_col div {
    width: 100%;
  }
</style>

カードコンポーネント

親コンポーネントからインポートされてるカートのコンポーネントです

svelteでは、コンポーネントに書かれたCSSがコンポーネント内に閉じるので、要素に対するセレクタを使ってCSSを記述してます

src/routes/card.svelte

<div class="container">
  <div class="wrapper">
    <img class="image" src="https://source.unsplash.com/random/640x400"  />
    <div>
      <dl>
        <dt>長いラベル</dt>
        <dd>投稿しました</dd>
      </dl>
      <dl>
        <dt>長いラベル</dt>
        <dd>¥100,000</dd>
      </dl>
    </div>  
  </div>
</div>

<style>
  .container{
    container-type: inline-size;
    margin: 10px;
    border: solid 1px #eee;
    box-shadow: 0 5px 5px 0 rgba(0, 0, 0, .2);
  }
  .wrapper {
    display: flex;
    flex-direction: column;
    padding: 10px;
    gap: 10px;
  }

  .image {
    height: 200px;
    width: 100%;
    border-radius: 10px;
    object-fit: cover;
  }

  dl {
    display: flex;
    flex-direction: column;
  }

  dt {
    font-size: 1.1rem;
    font-weight: 700;
  }
  
  dd {
    font-size: 1.0rem;
    font-weight: 400;
  }

  @container (min-width: 420px) {
    .wrapper {
      flex-direction: row;
    }

    .image {
      max-width: 50%
    }

    dl {
      flex-direction: row;
    }
    dd {
      margin-left: 10px
    }
  }
</style>

解説

Card コンポーネントの一番上位の要素に対して container-type: inline-size を指定してます。この要素配下のコンテナクエリは、この要素を基準にCSSを切り替えられます

<div class="container">
  <!-- 省略 -->
</div>
.container{
  container-type: inline-size;
}

コンテナクエリで指定の幅より狭くなった場合の指定をしています。以下の例だと container-type: inline-type が420pxを下回った場合にコンテナクエリ内のCSSが有効になります

@container (min-width: 420px) {
  /* 省略 */
}

まとめ

今回はコンテナクエリを Svelte で試してみました

全てのソースコードは Github に Push してます

この記事が誰かの参考になれば幸いです

参考

CSSの進化が早い! スタイルクエリ(@container style())の基礎知識と便利な使い方を解説 | コリス

コンテナクエリ @container が全ブラウザ対応。新時代のレスポンシブ対応を完全理解する