LoginSignup
1
0

More than 3 years have passed since last update.

スクロールで動的に追加される要素をユーザスクリプトで加工する

Posted at
<div id="recommendations">
  <div><!-- ページ表示時のHTMLに静的に存在している要素 -->
    <a class="recommendation">...</a>
    <a class="recommendation">...</a>
    <a class="recommendation">...</a>
  </div>
  <div><!-- スクロール時に動的に追加された要素 -->
    <a class="recommendation">...</a>
    <a class="recommendation">...</a>
    <a class="recommendation">...</a>
  </div>
</div>

上記のようにスクロールに応じて、一定個数ずつ動的に読み込まれ表示されていくサイトの場合、ユーザスクリプトでページ表示時にHTMLの加工を実施すると、スクロールによって動的に追加される部分が処理されません。

不要なものを削除する系ならsetIntervalで定期実行しても十分ですが、DOMの追加に応じて丁寧に処理を挟みたい時には MutationObserverで要素の追加を監視します。

(function() {
    'use strict';
    const observer = new MutationObserver((mutations) => {
        //console.log(mutations)
        let m = mutations.filter(m => m.target.id == 'recommendations');
        if (m[0]) process(m[0].addedNodes[0]);
    });
    observer.observe(document.querySelector('#recommendations'), {
        childList: true,
        attributes: false,
        characterData: false,
        subtree: true,
    });
    function process(wrapEl) {
        Array.from(wrapEl.querySelectorAll('.recommendation')).forEach(function(el){
            let a = document.createElement('a');
            a.href = '#';
            a.innerHTML = '何か便利なリンク';
            el.querySelector('.title').appendChild(a);
        });
    }
    process(document.querySelector('#recommendations > div'));

})();

注意が必要な点として、ユーザスクリプト自身で行ったappendChild()の操作でも検知するため監視の条件によっては無限再起でハングアップします。
それを防ぐために変化の内容を確認してフィルタします。
監視の起点とする要素や監視の条件をうまく調整すれば省けるのですが、サイトによって作りが異なるのでそこまでは付き合わず、毎回フィルタするコードを書く方がやりやすいです。

応用例として、Qiitaでのタグから最新記事でのページネーションのようにJSで記事一覧を再構築している場合にも同じやり方が適用できます。

// ==UserScript==
// @name         nabeatsu
// @match        https://qiita.com/tags/*
// ==/UserScript==

(function() {
    'use strict';
    const observer = new MutationObserver((mutations) => {
        //console.log(mutations)
        let m = mutations
            .filter(m => m.addedNodes[0])
            .filter(m => m.addedNodes[0].tagName == 'DIV')
            .filter(m => !/Dummy/.test(m.addedNodes[0].className));
        if (m[0]) process(m[0].addedNodes[0]);
    });
    observer.observe(document.querySelector('[id^="TagNewestItemList-"]'), {
        childList: true,
        attributes: false,
        characterData: false,
        subtree: true,
    });
    function process(wrapEl) {
        Array.from(wrapEl.querySelectorAll('article')).forEach(function(el){
            let lgtm = el.querySelector('svg').nextSibling.textContent;
            if (/3/.test(lgtm) || (lgtm > 0 && lgtm % 3 == 0)) {
                let a = el.querySelector('h2 a');
                a.textContent = a.textContent.replace(/[a-zA-Z0-9]/g, m => String.fromCharCode(m[0].charCodeAt(0) + 0xFEE0));
            }
        });
    }
})();

LGTMの数字が3の倍数か3が含まれる時にタイトルの英数が全角になります。

Qiitaの場合、HTMLを読み込んだ初期状態で記事一覧は存在しないことと、
ローディング表示のダミー要素が一度出るためそれを無視するようなケアを行いましたが、
サイトが異なってもほぼ同じやり方が流用できそうでした。

1
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
1
0