この記事は最終更新日から1年以上が経過しています。
@programming
投稿日 2019/10/24
更新日 2020/5/11 ✏
ピュアJS: HTMLの該当文字列をmarkタグで囲う方法
外部ライブラリを使わず生JSだけでHTML内の特定の文字列にmark
タグで囲う(マーカーを引く)方法です。
目次
マーカーを引くための関数を自作
次の2つの関数を自作します。 検索文字列にマーカーを引くmarkText()
とマーカーを除去するunmarkText()
です。
markText(node, word)
- 第一引数に親要素を、第二引数には検索文字列を指定します。この関数は、検索文字列にヒットしたTextNodeの箇所を
mark
タグで囲います。
- 第一引数に親要素を、第二引数には検索文字列を指定します。この関数は、検索文字列にヒットしたTextNodeの箇所を
unmarkText(node)
- 第一引数に親要素を指定します。この関数は、指定された要素の子要素の
mark
タグを全て外します。
- 第一引数に親要素を指定します。この関数は、指定された要素の子要素の
それぞれの関数の実際のコードは次のサンプルコードで示しています。
サンプルコード
<style type="text/css">
/* マーカーの色変更はお好みで。 */
mark {
background: yellow;
}
</style>
...
<input type="text" id="ipt-word" value="マーク">
<button id="btn-markText">テキストにマーカー</button>
<div>
<div class="item">マークあいうえお</div>
<div class="item">かきくけこ</div>
<div class="item">さしすマークせそ</div>
<div class="item">なにぬねのマーク</div>
<div class="item"><code>mark</code>タグで囲います。</div>
<div class="item"><b>注意:</b> 上記メッセージは「mark」というワードではマーカーを引けますが、「markタグ」というワードでは引けません。これは上記HTMLの「<code>mark</code>タグで囲います。」のHTMLが「<code>mark</code>タグで囲います。」だからです。</div>
</div>
<script type="text/javascript">
document.querySelector("#btn-markText").onclick = function() {
// 一旦全てのマーカーを消す:
unmarkText(document.querySelector("body"));
const word = document.querySelector("#ipt-word").value;
// マーカーを引く:
const items = document.querySelectorAll("body [class=item]");
for (let len = items.length, i = 0; i < len; i++) {
const item = items[i];
markText(item, word);
}
};
/**
* マッチテキストにマーカーを引く(markタグを付与)
* @param {HTMLElement} node - 親要素
* @param {String} word - 検索文字列
* 空文字列をセットするとマーカーが除去されます。
*/
function markText(node, word) {
if (node instanceof HTMLElement !== true) {
return console.warn("markText: node must be an HTMLElement");
}
if (!word || typeof word !== "string") return;
_markText(node, new RegExp(word, "g"), function(node, match, offset) {
const mark = document.createElement("mark");
// mark.className = "search-term";
mark.textContent = match;
return mark;
});
// @see: https://stackoverflow.com/a/29301739
function _markText(node, regex, callback, excludeElements) {
excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
let child = node.firstChild;
while (child) {
switch (child.nodeType) {
case 1:
{
if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1)
break;
_markText(child, regex, callback, excludeElements);
break;
}
case 3:
{
let bk = 0;
child.data.replace(regex, function(all) {
let args = [].slice.call(arguments),
offset = args[args.length - 2],
newTextNode = child.splitText(offset + bk),
tag;
bk -= child.data.length + all.length;
newTextNode.data = newTextNode.data.substr(all.length);
tag = callback.apply(window, [child].concat(args));
child.parentNode.insertBefore(tag, newTextNode);
child = newTextNode;
});
regex.lastIndex = 0;
break;
}
}
child = child.nextSibling;
}
return node;
}
}
/**
* 指定された要素からマーカーを除去(markタグを外す)
* @param {HTMLElement} node - ノード
*/
function unmarkText(node) {
if (node instanceof HTMLElement !== true) {
console.warn("unmarkText: node must be an HTMLElement.");
return;
}
const items = node.querySelectorAll("mark");
for (let len = items.length, i = 0; i < len; i++) {
const node = items[i];
if (node.nodeName === "MARK" || node.nodeName === "mark") {
const pn = node.parentNode;
_unmarkText(node);
// NOTE: タグの付与でバラけてしまったTextNodeをまとめたりする:
pn.normalize();
}
}
function _unmarkText(node) {
const txtn = document.createTextNode(node.textContent);
node.parentNode.replaceChild(txtn, node);
}
}
</script>
...
サンプルを実行
サンプルコードを実行してみましょう。 入力BOXに文字を入れて[テキストにマーカー]ボタンを押すと該当箇所の背景色が変わります。 また、空入力でボタンを押すとマーカーが外れます。
ひとこと
どうぞご自由にコピペしてお使いください。
Enjoy!