スクリプトとイベントハンドリング
標準的なHTMLの<script>
タグを使用すれば、React・Svelte・Vue等のUIフレームワークを利用せずにインタラクティブ性をAstroコンポーネントへ追加できます。これによってブラウザ上で実行されるJavaScriptを送信してAstroコンポーネントに機能を追加できるようになります。
クライアントサイドスクリプト
セクションタイトル: クライアントサイドスクリプトスクリプトは、イベントリスナーの追加やアナリティクスデータの送信、アニメーションの再生など、JavaScriptがウェブ上でできることすべてに使用できます。
<button data-confetti-button>Celebrate!</button>
<script> // npmモジュールをインポートする。 import confetti from 'canvas-confetti';
// ページ上のコンポーネントのDOMを探す。 const buttons = document.querySelectorAll('[data-confetti-button]');
// ボタンがクリックされたときにconfettiを発火するイベントリスナーを追加する。 buttons.forEach((button) => { button.addEventListener('click', () => confetti()); });</script>
デフォルトでは、Astroは<script>
タグを処理してバンドルし、npmモジュールのインポートやTypeScriptの記述などをサポートします。
Astroで<script>
を使用する
セクションタイトル: Astroで<script>を使用する.astro
ファイル内に単数(もしくは複数)の<script>
タグを追加することによってクライアントサイドJavascriptを追加できます。
下の例では、<Hello />
コンポーネントをページに追加することでブラウザコンソール上にログメッセージを出力します。
<h1>Welcome, world!</h1>
<script> console.log('Welcome, browser console!');</script>
スクリプトの処理
セクションタイトル: スクリプトの処理デフォルトで<script
>タグはAstroで処理されます。
- すべてのインポートはバンドルされ、ローカルファイルやNodeモジュールをインポートできます。
- 処理されたスクリプトは、
type="module"
としてページの<head>
に挿入されます。 - TypeScriptを対応しており、TypeScriptファイルもインポートできます
- コンポーネントがページに複数回使われている場合、スクリプトは一度だけ含まれます。
<script> // 処理される! バンドルされる! TypeScriptサポート! // ローカルスクリプトやNodeモジュールのインポートも動作します。</script>
type="module"
属性は、ブラウザにスクリプトをJavaScriptモジュールとして扱わせます。これにはいくつかのパフォーマンス上のメリットがあります。
- レンダリングがブロックされません。モジュールスクリプトとその依存関係がロードされる間、ブラウザは残りのHTMLの処理を続けます。
- ブラウザはモジュールスクリプトを実行する前に、HTMLが処理されるのを待ちます。“load”イベントをリッスンする必要はありません。
async
とdefer
属性は不要です。モジュールスクリプトは常に延期されます。
async
属性はレンダリングのブロックを防ぐため、通常のスクリプトにとって価値があります。しかし、モジュールスクリプトでは既にこの動作が含まれています。モジュールスクリプトにasync
を追加すると、ページが完全に読み込まれる前にモジュールスクリプトが実行されます。おそらくこれはあなたが望んでいることではありません。
処理の対象外にする
セクションタイトル: 処理の対象外にするAstroがスクリプトを処理しないようにするには、is:inline
ディレクティブを追加します。
<script is:inline> // 書かれたとおりにレンダリングされます。 // ローカルインポートが解決されずに動作しません。 // コンポーネント内の場合、コンポーネントが利用されている箇所全てに繰り返しレンダリングされます。</script>
Astroは、状況によってはスクリプトタグを処理しません。特に、<script>
タグにtype="module"
やsrc
以外の属性を追加すると、Astroはそのタグをis:inline
ディレクティブがあるかのように扱います。スクリプトがJSX式で記述されている場合も同様です。
📚 <script>
タグで利用可能なディレクティブについてもっと知りたい場合は、テンプレートディレクティブを参照してください。
ページにJavaScriptファイルを含める
セクションタイトル: ページにJavaScriptファイルを含めるスクリプトを.js
や.ts
ファイルのように分離して書きたい場合や、他のサーバーから外部スクリプトを参照したい場合には<script>
タグのsrc
属性の参照を使えます。
ローカルスクリプトのインポート
セクションタイトル: ローカルスクリプトのインポートこれを使うケース: スクリプトがsrc
に存在している時。
Astroはスクリプトの処理のルールに従って、ビルド、最適化、そしてページ内にスクリプトを追加します。
<!-- `src/scripts/local.js`への相対パス --><script src="../scripts/local.js"></script>
<!-- このローカルのTypeScriptファイルも動作します。 --><script src="./script-with-types.ts"></script>
外部スクリプトをロードする
セクションタイトル: 外部スクリプトをロードするこれを使うケース: JavaScriptファイルがpublic
ディレクトリに存在しているか、CDNの時。
プロジェクトのsrc
フォルダ以外のスクリプトをロードするには、is:inline
ディレクティブを含めます。このアプローチでは、上記のようにスクリプトをインポートする際にAstroが提供しているJavaScriptの処理・バンドル・最適化はスキップされます
<!-- `public/my-script.js`スクリプトへの絶対パス --><script is:inline src="/my-script.js"></script>
<!-- リモートサーバー上にあるスクリプトへの完全なURL --><script is:inline src="https://my-analytics.com/script.js"></script>
共通スクリプトパターン
セクションタイトル: 共通スクリプトパターンonclick
や他のイベントをハンドリングする
セクションタイトル: onclickや他のイベントをハンドリングするいくつかのUIフレームワークではonclick={...}
(React/Preact)や@click="..."
(Vue)のように独自の構文でイベントを処理しています。Astroは標準的なHTMLにより近く、イベントのために独自の構文は利用しません。
その代わりに、ユーザーのインタラクションをハンドリングするために<script>
タグ内にaddEventListener
を利用します。
<button class="alert">Click me!</button>
<script> // ページ内から`alert`クラスを持つすべてのボタンを探す。 const buttons = document.querySelectorAll('button.alert');
// 各ボタンがクリックされたときのハンドリング buttons.forEach((button) => { button.addEventListener('click', () => { alert('Button was clicked!'); }); });</script>
ページに複数の<AlertButton />
コンポーネントが存在する場合でも、Astroは複数回スクリプトを実行しません。スクリプトはページに1つしか含まれないようにバンドルされます。querySelectorAll
を利用すると、このスクリプトはページ上でalert
クラスを持つすべてのボタンにイベントリスナーをアタッチします。
カスタム要素を持つWebコンポーネント
セクションタイトル: カスタム要素を持つWebコンポーネントWebコンポーネント標準を使うことで独自の動作をするHTML要素を作成できます。.astro
コンポーネントでカスタム要素を定義するとUIフレームワークライブラリを利用しなくてもインタラクティブなコンポーネントを作ることができます。
下の例では、ハートボタンがクリックされた回数を記録し、その最新のカウント数を<span>
に更新する<astro-heart>
HTML要素を新しく定義しています。
<!-- カスタム要素 "astro-heart" でコンポーネント要素を囲みます。 --><astro-heart> <button aria-label="Heart">💜</button> × <span>0</span></astro-heart>
<script> // 新しいタイプのHTML要素の動作を定義する class AstroHeart extends HTMLElement { constructor() { super(); let count = 0;
const heartButton = this.querySelector('button'); const countSpan = this.querySelector('span');
// ボタンがクリックされるごとにカウントを更新する。 heartButton.addEventListener('click', () => { count++; countSpan.textContent = count.toString(); }); } }
// <astro-heart>要素としてAstroHeartクラスを利用することをブラウザに教える customElements.define('astro-heart', AstroHeart);</script>
ここでカスタム要素を利用するメリットは2つあります。
document.querySelector()
を使って全ページを検索する変わりに、this.querySelector()
を使えば検索範囲が現在のカスタム要素のインスタンスに限られます。これにより、一回で一つのコンポーネントだけを操作しやすくなります。<script>
は一度だけしか実行されませんが、ブラウザはページ上の<astro-heart>
を見つける度にカスタム要素のconstructor()
メソッドを実行します。これにより、ページで複数回コンポーネントを使用する場合でも一つのコンポーネントを安全に記述できます。
📚 web.dev’s Reusable Web Components guideとMDN’s introduction to custom elementsから多くのカスタム要素について学ぶことができます。
フロントマター変数をスクリプトに渡す
セクションタイトル: フロントマター変数をスクリプトに渡すAstroコンポーネントでは、---
フェンスの間にあるフロントマターのコードはサーバー上で実行され、ブラウザでは利用できません。サーバーからクライアントへ変数を渡すには、変数を保存しておきJavaScriptがブラウザで実行されたときに読み込む必要があります。
これを実現するための1つの手段はdata-*
attributesを利用してHTML出力に変数の値を保存する方法です。カスタム要素を含むスクリプトはブラウザ上にHTMLがロードされると要素のdataset
を利用することで、この属性を読み込むことができます。
下の例では、message
propをdata-message
属性に保存されているので、カスタム要素がthis.dataset.message
を読み込みブラウザ上で値を取得しています。
---const { message = 'Welcome, world!' } = Astro.props;---
<!-- message propをdata属性に保存する --><astro-greet data-message={message}> <button>Say hi!</button></astro-greet>
<script> class AstroGreet extends HTMLElement { constructor() { super();
// data属性からmessageを読み込む const message = this.dataset.message; const button = this.querySelector('button'); button.addEventListener('click', () => { alert(message); }); } }
customElements.define('astro-greet', AstroGreet);</script>
これでコンポーネントは何度でも利用でき、異なるmessageを表示できます
---import AstroGreet from '../components/AstroGreet.astro';---
<!-- messageの初期値「Welcome, world!」を利用する --><AstroGreet />
<!-- propsとしてカスタムmessageを渡す --><AstroGreet message="Lovely day to build components!" /><AstroGreet message="Glad you made it! 👋" />
この方法はReactなどのUIフレームワークを用いて書かれたコンポーネントにpropsを渡す際にAstroが裏で行っていることです!client:*
ディレクティブを持つコンポーネントに対して、Astroは<astro-island>
カスタム要素にprops
属性を作成してサーバーサイドのprops
をHTML出力結果に保存します。