DOMDOMタイムス#11: コメントノードあれこれ
今回はコメントノードのコメントではない側面を紹介していきます👶
いろいろ考えたり調べたりはしてみましたが、まあまあの内容になってしまいました。もうすこし面白い応用があるよという方がいたら是非教えていただきたいです!!!
なお本記事は下記の記事にインスパイアされています、ありがとうございます!
コメントノードの色々な使いみち
イベントを仕掛ける
冒頭で紹介した記事でも詳しく説明されています。
とにかく普通の要素と同じようにイベントを仕掛けることができます。
const comment = document.createComment('comment')
document.body.appendChild(comment)
comment.addEventListener('click', () => {
console.log('comment is clicked')
})
ただ、もちろんコメントノードがユーザーによってクリックされることもなければ、コメントノードにclick()
が生えているわけでもないので、おそらく無意味です😭
一方、カスタムイベントなら意味があるかもしれませんね。
const comment = document.createComment('comment')
document.body.appendChild(comment)
comment.addEventListener('comment-event', () => {
console.log('comment-event!')
})
comment.dispatchEvent(new Event('comment-event'))
ページ上の要素からイベントを送ると、それがカスタムイベントであれ問題になる場面で使えそうです。
そんな場面あるか?というのはさておき!
集める
コメントノードを集めるのは意外と難しいです。
というのもidはつけられない、classもムリ、タグ名も扱えないというわけで、CSSセレクタを書くことができません。querySelectorAll
では集められないんですね。
でもみんな大好きなxPath(みんな大好きとは?)でなら集められます!
function getAllComments(node) {
const xPath = "//comment()";
const result = [];
const query = document.evaluate(
xPath,
node,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0, length = query.snapshotLength; i < length; ++i) {
result.push(query.snapshotItem(i));
}
return result;
}
getAllComments(document.documentElement);
これは下記を参考にしています。
もちろん他にもやり方はあり、例えばTreeWalker
を使って探索しながらNode.nodeType === 8
を実行するのもアリです。
集めてどうするのかというところですが、これが次の話につながります。
目印にする
集められるという話になると、コメントノードを目印として使うことができます。
例えば下記のブログ記事では、まさに先ほど言ったTreeWalker
を使ったやり方でコメントノードを渉猟して、pタグに置き換えていくというムーブを決めています。
また、実はフロントエンドフレームワークであるlitでも、要素のアップデートをするときにコメントノードをある種の目印として使用しています。
The comment nodes are required to mark the delimiters of dynamic content. Whenever you use ${ escapes } lit-html inserts these comment nodes so the dynamic part has a fixed context where it can render. With the current architecture these nodes are necessary.
https://github.com/lit/lit/issues/748
litが使用されたページのDOMを見ると、<!--?lit$534310711$-->
といったコメントがたくさん入っています。これにはそんな目的があったわけです。
テンプレートにする
DOMに下記のようなコメントノードを忍ばせておきます。
<!--テンプレート-->
<div id="template" style="display: none;">
<!--
<div class="item">
<h2></h2>
<p></p>
</div>
-->
</div>
<!--テンプレートを使用して要素を注入する対象のコンテナ-->
<div id="container"></div>
そのうえでjsでこんなふうにしてみる。
// JSONデータ
var JSON_data = [
{"title" : "Item1 Title", "content" : "Item1 Content"},
{"title" : "Item2 Title", "content" : "Item2 Content"},
{"title" : "Item3 Title", "content" : "Item3 Content"}
];
// テンプレートの取得
var template = document.getElementById("template").childNodes[0].nodeValue;
// データの生成と挿入
for(let i = 0; i < JSON_data.length; i++){
let item = document.createElement("div");
item.innerHTML = template.trim();
item.querySelector("h2").innerText = JSON_data[i].title;
item.querySelector("p").innerText = JSON_data[i].content;
document.getElementById("container").appendChild(item.firstChild);
}
どうやら要素を1つずつ作るよりも、commentノードの中に書いたHTMLをinnerHTMLとしてドカッと詰め込む方がブラウザに優しい場合があるとかないとか?あまり調べていないし、そこまで見る気力がなかったのでわかりません。cloneNode()
とかがなかった時代のテクなのかもしれませんね?
document-fragmentの中ですべてDOMの操作をしたあとにdocumentに戻すみたいなやり方の雰囲気を少し感じます。
きょうはおしまい
まあこんな感じです。どれもいまのフロントエンドDOMわーるどではそこまで役に立たないかもしれないけど、思いもよらない場所で身を助けてくれるかも??
来週はもう少し実践的なことが書けるといいなあ👶
Discussion