HTMXでmorphdomを使ってDOMの入れ替えをせずに更新する
あんまり凝ったことをHTMXでやるべきではないというのが持論ですが、どうしても足したい機能があって、HTMXがDOMを入れ替えてしまうことが問題になるケースではmorphdomを使うときれいに解決できるかもしれません。
HTMXとは
HTMXとは、タグに hx-hogehoge
といった属性を足すことで、HTMLだけを書いているように見せつつも動的にDOMを組み立てることができるライブラリ。
ちょっとした情報更新がある動的サイトを書きたいときはとてもスッキリ書けるので、結構画期的なライブラリに思えます。
が、書くには結局DOMから発せられるイベントに熟知している必要があるなど、よく言われる「JavaScript書かなくていい」みたいなのはちょっと全面的には肯定できないですね。「JavaScriptをよく知ってる人が頑張って工夫すると簡潔に書ける」ぐらいが関の山かなと思います。
個人的にこのライブラリは、バニラなJavaScript+bootstrapで構成されたサイトを置き換えるシーンで活躍しそうだなという感じがします。
DOMが入れ替わってしまうと何が起こるのか
HTMXは、デフォルトの動作では、指摘したターゲット(要素)を取得したHTMLで書き換えるという動作をします。この書き換えは基本「入れ替え」となりますので、以下のようなことが起こります。
- 画像の再読み込み
- カーソル位置のリセット
- イベントのバインドの解除
要はまったくの新しい要素が追加されるということなので、要素の入れ替わりが起こった時には毎度ページを読み込んだ時のように初期化を行ってあげる必要が出てくるわけです。
ここらへん、Reactで頑張って再レンダリングを抑止!とか普段やってる人間からすると「うおお気持ち良くない」となるポイントですが、受け入れるほかありません。
代替の方法 morphing
前章では「デフォルト動作では入れ替え」と書きましたが、入れ替え以外の方法として 「morphing」 という方法があります。morphとは「変化させる」という意味の英単語ですが、すでにあるDOMを指定された状態に置き換えではなく、変更する(morphing) ということをします。
morphingを行った結果は同じHTMLになるので、見た目的には同じものができますが、新しいものに差し変わるわけではないので状態が保存されます。
デメリットとしてはガバッと雑に差し替えるわけではなく、既存要素を舐めて変更箇所を見つけるので、単純な置換よりは重たい処理になるというのが挙げられます。まぁただ、HTMXでかかる変更はHTTP通信を伴うので、そもそも通信の時間を考えると置換にかかる時間は微々たるものという考え方もできなくはないので、毛嫌いするのではなくやってみて影響度合いを確認するのがよいでしょう。
HTMXでmorphingできるように拡張を書く
DOMのmorphingを実現するライブラリとして「morphdom」というものがあります。
このmorphdomですが、HTMXではそれ用の拡張機能が提供されていて、簡単に導入することができます。
htmx-extensions/src/morphdom-swap at main · bigskysoftware/htmx-extensions · GitHub
morphdomがそのまま使えればいいよ~というケースでは上記で十分です。
つかいかた
使い方については上記拡張機能のリンクに書かれているのでそちらを参照してください。
終わりに
今回はHTMXでmorphingすると便利だよという内容で書かせていただきました。
morphdomを使用してのswapは要素の入れ替わりを抑止することができるので、拡張機能を導入するだけで
- 画像が再読み込みされない
- カーソル位置が保持されたまま
- イベントのバインドが解除されない
といったメリットを得られます。
実際の使用では、JavaScript経由で追加した「要素のクラスもキープしたい」という要望もあったので、拡張機能をさらにいじって、属性で指定した要素のクラスを不変にするといったことも行いました。
htmx.defineExtension('morphdom-swap', { isInlineSwap: function (swapStyle) { /* 略 */ }, handleSwap: function (swapStyle, target, fragment) { if (swapStyle === 'morphdom') { /* 中略 */ morphdom(target, targetFragment, { onBeforeElUpdated: function (fromEl, toEl) { /* fromEl から toEl へクラスをコピーする */ return toCls; } }); return [target]; } } })
上記内容についてはmorphing自体の内容から逸れてしまうのでメインには書きませんでしたが、morphdom使用を契機に拡張機能自体にもチャレンジする機会を得ることができました。
個人的にあんまりゴリゴリJavaScript書いたり、それのために拡張機能を書いたりするのは、HTMXの特徴である「JavaScriptを意識せずに書ける」という点を潰してしまうのであんまり美しくないなあと思うところですが、このような対処方法もあるよということでこの記事を書かせていただきました。
長くなってしまいましたが、以上です。みなさまもよきHTMXライフを