@okayuの大して技術的ではないブログ

記事を大幅アップデートしました。

はじめに

JavaScriptで画面に表示されているクリッカブルな要素を列挙してみました。

Hit-a-Hintができる拡張機能がどうやって動いているのか気になったので。
Vimiumを参考にしました。
特に参考にしたところ: link_hints.coffeeの657行目あたりから

全体像

const  
    settings = {  
        // 欲しいものをCSSセレクターで指定  
        allow: [  
            'a',  
            'button:not([disabled])',  
            'details',  
            'input:not([type="disabled" i]):not([type="hidden" i]):not([type="readonly" i])',  
            'select:not([disabled])',  
            'textarea:not([disabled]):not([readonly])',  

            '[contenteditable=""]',  
            '[contenteditable="true" i]',  

            '[role="button" i]',  
            '[role="checkbox" i]',  
            '[role="link" i]',  
            '[role="menuitemcheckbox" i]',  
            '[role="menuitemradio" i]',  
            '[role="option" i]',  
            '[role="radio" i]',  
            '[role="switch" i]',  
        ],  
        // 取得した要素、およびその要素の先祖で欲しくないものをCSSセレクターで指定  
        block: [  
        ]  
    }  
    ;  

const getClickableElms = (allowList, blockList) => {  
    const  
        windowH = window.innerHeight,  
        windowW = window.innerWidth  
        ;  

    return (  
        // 要素を取得  
        // スプレッド演算子超便利  
        [...document.querySelectorAll(allowList.join(',') || undefined)]  
            // blocklistをもとに要素を除外  
            // closest便利  
            .filter(elm => elm.closest(blockList.join(',') || undefined) === null)  
            // 座標などの情報を用いたフィルタリング  
            .filter(elm => {  
                const domRect = elm.getBoundingClientRect();  
                return (  
                    // 存在するものを抽出  
                    domRect.width > 0 && domRect.height > 0  
                    &&  
                    // 画面上に現在表示されているものを抽出  
                    // スッっと出てくるサイドバー上のリンク等の、ビューポート外の要素を除外  
                    // 上下左右の順  
                    domRect.bottom > 0 && domRect.top < windowH && domRect.right > 0 && domRect.left < windowW  
                );  
            })  
    );  
}  

const clickableElms = getClickableElms(settings.allow, settings.block);  
console.log(clickableElms);  

順を追って説明します。

CSSセレクター

CSSセレクターで欲しい要素を指定し、それをdocument.querySelectorAll()で取得します。

すべての要素を取得したあとフィルタリングしていくという方法もありましたが、パフォーマンス的にどうなのかと思い、クリッカブルっぽい要素を指定しています。

『クリッカブルっぽい要素』というのは、

  • a
  • button
    • disabledでない
  • details
    • 詳細情報を表示/非表示する際に使われるタグ
    • クリックで開閉できる
  • input
    • disabledhiddenreadonlyでない
  • select
    • disabledでない
  • textarea
    • disabledreadonlyでない

のようなものです。

また、

なども含めました。

closestを用いた選別

その要素、もしくはその要素の先祖がブラックリストに載っていた場合は選別します。
(こう書くとディストピアっぽい気がする(笑))。

JavaScriptのclosest()を用います。
これは、CSSセレクターにマッチする要素を先祖にまでさかのぼって見つけるというすごいものです。
Element.closest() - Web APIs | MDN

見つからず、nullだった場合は許可します。

今回はブラックリストは空です。

座標情報を用いた選別

Element.getBoundingClientRect() メソッドは、要素のサイズと、そのビューポートに対する位置を返します。
element.getBoundingClientRect - Web API | MDNより

便利そう。

lefttoprightbottomxywidthheightが得られます。
(xyはよくわからないです。lefttopと何が違うんでしょうか)。

実際の表示の情報をもとにしているので、普通にwidthやheightを参照するよりも良さそうです。

width > 0かつheight > 0で、ページ上に表示されている要素のみに選別。
なおかつ要素の位置から、画面上に表示されている要素にのみ選別しています。

おわりに

自分好みのHit-a-Hint拡張機能とか、作れたら楽しそうだろうなぁ。

この記事へのコメント

まだコメントはありません