これはなに?
ばにらJavaScriptにおいて、ページ全体に設定したEventListener内で、マウス位置の単語を取得する方法。
概略
JavaScriptで特定の文字列のクリックイベントやマウスオーバーイベントを取得するにはspanタグで囲うなどの方法が用いられるが、例えばUserScriptやBookmarkletで表示中のページにあるすべて特定の文字列にspanを設定することは難しい。たとえば、innerHTMLで単純置換を行うと、hrefやalt attributeに特定の文字列が設定されていれば、ページが崩壊してしまう。また、DOM操作を行うにしても、TextNodeから一部にspanタグを設定して、しかも前後のタグとの関係を処理することは困難である。そこで、この記事ではwindow.addEventListenerでページ全体に設定したイベント内で、マウスカーソルの位置にある単語(英語などのスペース区切りの言語に限る)を取得する方法を紹介する。
コード
document.addEventListener("mousemove", (event) => {
// マウス座標を取得
const x = event.clientX;
const y = event.clientY;
// 座標位置にあるDOM要素を取得
const element = document.elementFromPoint(x, y);
console.log(getWordFromElement(element, x, y));
});
function getWordFromElement(element, x, y) {
// 変数の初期化
const range = document.createRange();
// エレメント内に複数のノードがある場合、文字列の含まれるTextNodeを探す。
for (let i = 0; i < element.childNodes.length; i++){
// 子要素を1つずつ見ていく
let node = element.childNodes[i];
// 子要素がテキストノードなら選択中の単語を確認
if(node.nodeType == Node.TEXT_NODE){
const text = node.textContent;
// 正規表現で単語を検索
const re =/[a-zA-Z0-9\-]+/g;
let match;
// 見つかった単語を1つずつ確認していく
while((match = re.exec(text)) != null){
// rangeに始まりに見つかった単語の1文字目の位置を記録
range.setStart(node, match.index);
// rangeの終わりに見つかった単語の最後の文字の位置を記録
range.setEnd(node, match.index + match[0].length);
// rangeのページ上での座標を取得
const rects = range.getClientRects();
// マウスカーソルの位置が見つかった単語の描画域の中にあるか判定
for (let j = 0; j < rects.length; j++){
const rect = rects[j];
if(rect.left <= x && x <= rect.right &&
rect.top <= y && y <= rect.bottom
){
// マウスカーソルの位置であれば、その単語を返す。
return match.toString();
}
}
}
} else {
// TextNodeじゃなかったら子要素も探索
word = getWordFromElement(node, x, y);
if(word.length > 0){
return word;
}
}
}
return "";
}