【Astro】Alpine.jsでアコーディオンのコンポーネントを作る

目次

執筆時の作業環境は以下のとおりです。

  • MacOS: Sonoma 14.2.1
  • Node.js: v21.6.1
  • npm: v10.2.4
  • Astro: v4.4.0
  • Alpine.js: v3.13.5

はじめに

Alpine.jsは、HTMLの要素に直接処理を記述することでDOM操作やインタラクティブなUIを実装することができるJavaScriptフレームワークです。

公式HP

Alpine.js

A rugged, minimal framework for composing behavior directly in your markup.

今回はアコーディオンにHTML標準の<details>タグを使うのでそのままでもアコーディオンとして機能しますが、開閉時のアニメーションを簡単に実装するためにAlpine.jsも併用します。

完成形はこちらです。

クリックしてください

開きました!

<!-- コード全体 -->
<details x-data="{showDetails: false}">
  <summary @click="showDetails = !showDetails" class="block">
    <slot name="summary" />
  </summary>
  <div x-show="showDetails" x-collapse x-cloak>
    <slot name="details" />
  </div>
</details>

<style lang="scss">
  summary {
    position: relative;
    padding-right: 1rem;

    &::before,
    &::after {
      content: '';
      position: absolute;
      top: 50%;
      right: 0.5rem;
      background-color: var(--stone-800);
      translate: 50% -50%;
    }

    &::before {
      width: 1rem;
      height: 2px;
    }

    &::after {
      width: 2px;
      height: 1rem;
      transition: rotate 0.3s ease;
    }
  }

  details[open] {
    summary::after {
      rotate: 90deg;
    }
  }
</style>

Alpine.jsをプロジェクトに追加する

Astroでは、Alpine.jsの公式のインテグレーションが用意されているので、以下のコマンドをターミナルで入力すると自動的にプロジェクトにAlpine.jsが追加されます。

npx astro add alpinejs

collapseプラグイン

アコーディオン開閉時に伸び縮みするようなアニメーションを追加するためにAlpine.jsのcollapseプラグインを導入します。

ターミナルで以下のコマンドを入力して、プラグインと型ファイルをインストールします。

npm install @alpinejs/collapse
npm install --save-dev @types/alpinejs__collapse

astro.config.mjsのAlpine.jsインテグレーションの部分にentrypointを指定します。

import { defineConfig } from 'astro/config'
import alpine from '@astrojs/alpinejs'

export default defineConfig({
  integrations: [alpine({ entrypoint: '/src/entrypoint' })],
})

src/entrypoint.tsを作成し以下のコードを記述します。

import type { Alpine } from 'alpinejs'
import collapse from '@alpinejs/collapse'

export default (Alpine: Alpine) => {
  Alpine.plugin(collapse)
}

変数名と初期値を設定する

まずはx-dataディレクティブを使って変数名と初期値を設定します。 x-dataの値にオブジェクトの形で{変数名: 初期値}と書くことで設定できます。

<details x-data="{showDetails: false}">・・・</details>

JavaScriptでlet showDetails = falseと宣言するのと同じようなイメージです。

スコープ

この変数のスコープは、x-dataディレクティブを書いた要素をその子要素内に限定されます。先祖要素や兄弟要素などでは使うことができません。

逆に言うと、同じアコーディオンコンポーネントがページ内に複数あっても、お互いの変数の値が影響してしまうことはありません。

クリック時に変数を反転させる

アコーディオンを開閉するトリガーになる要素(今回はsummary要素)に@clickディレクティブを指定し、値をshowDetails = !showDetailsとします。

<details x-data="{showDetails: false}">
  <!-- ↓いまここ -->
  <summary @click="showDetails = !showDetails" class="block"> ・・・ </summary>
  ・・・
</details>

これにより、showDetailstrueならfalseに、falseならtrueに反転します。

class="block"は、summary要素にデフォルトで存在する ”▶” というマーカーを非表示にしています。

Tailwind CSS を使用しているので、それ以外の方法でスタイリングしている場合は合わせて記述を変えてください。

変数に応じて表示⇔非表示を切り替える

表示⇔非表示を切り替えたい要素にx-showディレクティブを記述し、値をshowDetailsにします。これにより、その変数がtrueなら表示され、falseなら非表示になります。

そのままだと、パッパッと機械的に切り替わるので、x-collapseディレクティブをつけて開閉時の伸び縮みのアニメーションを付けます。

さらに、ページ表示時に開いた状態が一瞬見えてしまうことがあるので、x-cloakディレクティブをつけ、CSSで[x-cloak]: {display: none}というスタイルを指定することで回避します。

<details x-data="{showDetails: false}">
  <summary @click="showDetails = !showDetails" class="block"> ・・・ </summary>
  <!-- ↓いまここ -->
  <div x-show="showDetails" x-collapse x-cloak>・・・</div>
</details>

名前付きスロットで子要素の位置を指定する

アコーディオンの中身は、Astroの名前付きスロットで挿入します。 クリックする部分のスロットにname="summary"、開閉する部分のスロットにname="details"と設定します。

<details x-data="{showDetails: false}">
  <summary @click="showDetails = !showDetails" class="block">
    <!-- ↓いまここ 1 -->
    <slot name="summary" />
  </summary>
  <div x-show="showDetails" x-transition x-cloak>
    <!-- ↓いまここ 2 -->
    <slot name="details" />
  </div>
</details>

ページ内でアコーディオンコンポーネントを使用する

Astroのページのコードフェンス内でAccordionコンポーネントをインポートし、<Accordion>タグの子要素にslot="summary"slot="details"を持つ要素をいれることでアコーディオンとして機能します。

---
import Accordion from 'path/to/file/Accordion.astro'
---

<Accordion>
  <p slot="summary">詳しく見る</p>
  <p slot="details">詳細なテキスト・・・</p>
</Accordion>

スタイリング

<summary>タグの右端に、開閉時に変化する+/-マークを付けるため、<style>タグで以下のCSSを書いています(SCSSを使用しています)

<style lang="scss">
  summary {
    position: relative;
    padding-right: 1rem;

    &::before,
    &::after {
      content: '';
      position: absolute;
      top: 50%;
      right: 0.5rem;
      background-color: var(--stone-800);
      translate: 50% -50%;
    }

    &::before {
      width: 1rem;
      height: 2px;
    }

    &::after {
      width: 2px;
      height: 1rem;
      transition: rotate 0.3s ease;
    }
  }

  details[open] {
    summary::after {
      rotate: 90deg;
    }
  }
</style>

その他は機能だけを提供してスタイルを持たせていないので、使う側で自由にスタイリングできます。

このアコーディオンのスタイリング例を見る

※ Tailwind CSS を使っています。

  <div class="py-2 px-4 bg-stone-200 ">
    <Accordion>
        <p slot="summary" class="flex justify-between rounded-md">このアコーディオンのスタイリング例を見る</p>
        <div slot="details" class="grid gap-8 mt-4">
          ・・・
        </div>
    </Accordion>
  </div>

おわり

Alpine.jsを利用したアコーディオンコンポーネントの作り方でした。 名前付きスロットの活用やスタイルレスな設計で、汎用的に使えるコンポーネントにすることができました。

参考

@astrojs/alpinejs

Learn how to use the @astrojs/alpinejs framework integration to extend component support in your Astro project.

コンポーネント

Astroコンポーネント構文の紹介です。

Collapse — Alpine.js

Collapse and expand elements with robust animations