## HTMXの心臓部、トリガーと高度な制御技術 {#sec-f4f92dc347e1} これまでの記事では、[[HTMX]]を使ってサーバーにリクエストを送信する基本的な方法を見てきました。`hx-get`、`hx-post`、`hx-put`、`hx-delete`といった属性を使うことで、JavaScriptの`fetch()`なしでもかなりのAjax操作が実装できることを確認しました。 しかし、HTMXを使っていると、**リクエストを送信すること自体よりも、いつ送信するか**を制御したくなることがあります。その際に登場するのが`hx-trigger`です。 `hx-get`や`hx-post`が**「何をするか」**を決める属性だとすれば、`hx-trigger`は**「いつそれをするか」**を決める属性です。 `hx-trigger`を使わない場合、HTMXは単にボタンをクリックしたときにリクエストを送信するだけのシンプルなAjaxツールに過ぎないように見えるかもしれません。しかし、HTMXは`hx-trigger`を使い始めてからこそ、その真価を発揮し、満足度が向上するはずです。 例えば、以下のようなことがJavaScriptコードなしで可能になります。 - 入力が停止した後にのみ検索リクエストを送信する - 短時間での重複クリックを防ぐ - 一定周期で自動更新する - 要素が画面に表示されたときにのみロードする - 特定の条件でのみリクエストを送信する - 異なるリクエスト同士が衝突しないように優先順位を調整する 私も最初は「数行のfetchで済むのに、わざわざHTMXを使う意味があるのか」と考え、HTMXを単純なAjaxツールとして見ていました。しかし、`hx-trigger`の強力な制御機能を体験した後、その認識は完全に変わりました。 この記事では、HTMXの真の核とも言える**トリガーと高度な制御技術**をまとめてご紹介します。  --- ## [[HTMX]]は単なるボタンツールではない {#sec-31cdd005939f} 初めてHTMXに触れると、通常このような例から始めます。 ```html
``` これだけを見ると、HTMXは単に「ボタンを押すとAjaxリクエストを送信するツール」のように感じられるかもしれません。 もちろん、それだけでも十分に便利です。しかし、それはHTMXの一部に過ぎません。 本当に重要なのは、**リクエスト自体ではなく、そのリクエストが発生するタイミングと条件をHTMLで宣言できる点**です。 例えば、 - 検索窓でタイプするたびにサーバーにリクエストを送信するのは、最悪のUXです。逆に、入力が停止してから500ms後にのみリクエストが送信されれば、はるかに自然でしょう。 - あるボタンは、単なるクリックではなく、**Ctrlキーを押した状態でクリックしたときにのみ動作**するようにしたい場合もあります。 - あるいは、スクロールして特定の要素が画面に表示されたときに、初めてデータを取得したい場合もあるでしょう。 このような瞬間ごとにJavaScriptを直接書き始めると、コードが急速に増えていきます。一方、HTMXはこのような制御の大部分を**JavaScriptを1行も書かずに、属性の組み合わせだけで表現**できます。 この点が本当に驚くべきことです。**C++開発者がPythonコードを初めて見て「え?型宣言もなし??」と感じたときの衝撃が、私がHTMXのhx-triggerを初めて見たときの衝撃と似ているかもしれません**。 --- ## 基本トリガー:click, change, submit {#sec-a730147be101} まず知っておくべきは、[[HTMX]]はすでにHTML要素の基本動作とうまく連携するように作られているという点です。 例えば、ボタンは基本的に`click`、フォームは基本的に`submit`、入力要素は状況に応じて`change`といったイベントと自然に結びつきます。 ```html ``` このボタンは、特に`hx-trigger="click"`と記述しなくても、クリック時にリクエストを送信します。 同様に、フォームも同じです。 ```html ``` この場合、フォームの送信時にリクエストが発生します。 つまり、HTMXは非常に基本的なシナリオでは、すでにかなり賢く動作します。しかし、私たちが注目すべきはここからです。 **デフォルト値を超えて、望むタイミングと条件を直接宣言すること。** これこそが私たちがやりたいことです。 --- ## `hx-trigger`でよく使う標準イベント vs [[HTMX]]専用トリガー {#sec-8354ed76dc75} まず重要なのは、以下の表を理解することです。キーポイントは、**ブラウザが元々提供するDOMの標準イベントはもちろん使用可能であり、さらにいくつかのHTMX専用トリガーがある**という点です。 | 区分 | 値 | 意味 | よく使う状況 | 例 | | ---------- | ----------------- | ---------------------------- | ----------------------------------- | ---------------------------------------------------------------------- | | 標準イベント | `click` | クリック時にリクエストを送信 | ボタン、リンク、アクション実行 | `hx-trigger="click"` | | 標準イベント | `input` | 入力値が変わるたびにリクエストを送信 | リアルタイム検索、オートコンプリート | `hx-trigger="input changed delay:500ms"` | | 標準イベント | `change` | 値が確定して変わったときにリクエストを送信 | `select`、`checkbox`、blur後の入力反映 | `hx-trigger="change"` | | 標準イベント | `submit` | フォーム送信時にリクエストを送信 | form送信 | `hx-trigger="submit"` | | 標準イベント | `keyup` | キーを離したときにリクエストを送信 | キー入力ベースの検索、即時反応 | `hx-trigger="keyup delay:500ms"` | | 標準イベント | `keydown` | キーを押した瞬間にリクエストを送信 | ホットキー、キーボードインタラクション | `hx-trigger="keydown[from:body]"` | | 標準イベント | `mouseup` | マウスボタンを離したときにリクエストを送信 | ドラッグ/選択後の反応 | `hx-trigger="mouseup"` | | htmx専用 | `load` | 要素がロードされるとすぐにリクエストを送信 | 遅延ロード、初期データの投入 | `hx-trigger="load"` | | htmx専用 | `revealed` | 要素が画面に表示されたときにリクエストを送信 | 無限スクロール、lazy loading | `hx-trigger="revealed"` | | htmx専用 | `intersect` | 要素がビューポートと交差したときにリクエストを送信 | より精密なlazy loading、スクロールベースのロード | `hx-trigger="intersect once"` | | htmx専用構文 | `every 5s` | 一定周期でリクエストを送信 | ポーリング、ステータス更新 | `hx-trigger="every 5s"` | | カスタムイベント | `my-custom-event` | 直接定義したイベントでリクエストを送信 | サーバーヘッダー、JS連携、疎結合イベントアーキテクチャ | `hx-trigger="itemSaved from:body"` | | modifier | `delay:500ms` | 指定した時間に追加イベントがない場合のみリクエストを送信 | デバウンス、リアルタイム検索の最適化 | `hx-trigger="keyup delay:500ms"` | | modifier | `throttle:1s` | 短時間での繰り返しリクエストを制限 | 重複クリック防止、過剰なリクエスト抑制 | `hx-trigger="click throttle:1s"` | | modifier | `once` | 1度だけトリガーされるように制限 | 初回ロード、1回限りのイベント | `hx-trigger="intersect once"` | | modifier | `changed` | 値が実際に変更された場合のみリクエストを送信 | 入力フィールドの最適化、不要なリクエスト防止 | `hx-trigger="input changed delay:500ms"` | | modifier | `from:body` | イベント検出対象を別の要素に指定 | グローバルイベント受信、カスタムイベント処理 | `hx-trigger="itemSaved from:body"` | | modifier | `[condition]` | 条件を満たす場合のみリクエストを送信 | 修飾キーの組み合わせ、入力値の長さ条件 | `hx-trigger="click[ctrlKey]"` / `hx-trigger="keyup[value.length > 1]"` | | modifier | `consume` | 親など上位要素にイベントが伝播しないように消費 | ネストされたhtmxリクエストの衝突防止 | `hx-trigger="click consume"` | | modifier | `queue:first` | 新しいイベントをキューに入れる際、最初のもののみ保持 | 連続入力中に最初のリクエストのみ保持 | `hx-trigger="input queue:first"` | | modifier | `queue:last` | 新しいイベントをキューに入れる際、最後のもののみ保持 | 検索窓、オートコンプリート | `hx-trigger="input queue:last"` | | modifier | `queue:all` | 発生したイベントをすべてキューに保持 | すべてのイベントを順次処理する必要がある場合 | `hx-trigger="input queue:all"` | | modifier | `queue:none` | 進行中のリクエストがある場合、新しいイベントを無視 | 重複リクエストの完全ブロック | `hx-trigger="click queue:none"` | `hx-trigger`の値は通常、まず**イベント(event)**を記述し、必要に応じてその後に**フィルター(filter)**と**モディファイア(modifier)**を組み合わせて使用します。 つまり、**「何が起こったとき(event)、どのような条件であれば(filter)、どのような方法で処理するか(modifier)」**という順序で読み解くことができます。形式としては、通常`event[filter] modifier modifier`となります。 ```html ``` - `keyup` → キーを離したとき - `[value.length > 1]` → 入力値が2文字以上の場合のみ - `changed delay:500ms` → 値が変更され、0.5秒間追加の入力がない場合のみリクエストを送信 ## いくつかの便利な例 {#sec-d893c37bc6f7} 上記の表でまとめましたが、ここで終わらせるのはもったいないので、私が特に気に入っているトリガーの例をいくつかご紹介します。 ### 入力が停止した後にのみリクエストを送信:`delay` {#sec-cd31c7256394} 検索窓のオートコンプリートやリアルタイムフィルタリングのような機能を作成する際、ユーザーがタイプするたびにリクエストを送信すると、サーバーに負担がかかり、ユーザー体験もどこか落ち着かなくなります。 このような場合、`delay`を使用すると、はるかにスムーズになります。 ```html ``` このコードは、ユーザーがキーを押すたびにすぐにリクエストを送信するわけではありません。代わりに、**入力が停止してから500msが経過すると**リクエストを送信します。 これは実質的に**デバウンス(debouncing)**です。 JavaScriptで実装しようとすると、タイマーを設定し、以前のタイマーをキャンセルし、再度設定するといったコードが必要になります。しかし、[[HTMX]]では属性だけで完結します。 検索、自動提案、フィルタリングUIでは、この機能はほぼ必須と言っても良いでしょう。 --- ### 送信頻度を制限する:`throttle` {#sec-c84ae134ee88} `delay`が「入力が停止した後、少し待ってから送信する」というニュアンスであるのに対し、`throttle`は「短時間での送信回数が多すぎるのを制限する」という側面が強いです。 ```html ``` この場合、ユーザーがボタンを非常に素早く何度もクリックしても、1秒以内には過度なリクエストが連続して送信されないように制御できます。 以下のような状況で非常に役立ちます。 - 重複クリックの防止 - 速すぎる繰り返しリクエストの遮断 - サーバー負荷の軽減 - 誤って同じアクションを複数回実行するのを防ぐ 特に「いいね」、「保存」、「更新」、「同期」のようなボタンでは、一度検討してみる価値があります。 --- ### 一定周期で自動リクエスト:`every` {#sec-0195e4e0d263} [[HTMX]]を使っていると、意外と魅力的に感じるのがこの`every`機能です。 特定の領域を一定周期でサーバーから新しく取得したい場合、わざわざ別途ポーリングロジックをJavaScriptで書く必要がありません。 ```html