0
0

JavaScriptでマウスカーソル位置の単語を取得する方法が難しかった

Last updated at Posted at 2024-08-24

これはなに?

ばにら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 "";
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0