CSS で inner-text を条件にしたセレクタを書くための Tampermonkey スクリプトと Stylus 案

あけおメンチカツ・ことよロースカツ (2023年・2024年に引き続き2年ぶり3回目)。

伊集院光が昔ラジオで、正月の曲「春の海」に合わせて言っていた替え歌「親族みな病死~」が結構好きです。(?)


以前こんな記事を書いた。

CSS で div:inner-text*="ほげ" みたいに、「要素内にその文字列を含んでいたら」というセレクタを書けたら良いな~という話。当時はユーザスクリプトでの対応を面倒臭がったのだけど、案外簡単にできそうだったので書いてみた。

昔は Firefox 向けに Greasemonkey というのがあって (今もあるんだけど)、Chrome 系ブラウザでは Tampermonkey というほぼ同等の拡張機能がある。それ向けに書いているけど、やっていることは DOM 走査して setAttribute してるだけなので多分どのブラウザでも行けるんじゃないだろうか?

// ==UserScript==
// @name         Add data-inner-text
// @namespace    http://tampermonkey.net/
// @version      2025-12-17
// @description  data-inner-text 属性を付与する
// @author       Neos21
// @match        https://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(() => {
  'use strict';
  
  /** data-inner-text を付与しない要素 */
  const excludedTags = new Set([
    'SCRIPT', 'NOSCRIPT',
    'STYLE',
    'IFRAME',
    'VIDEO',
    'AUDIO',
    'CANVAS', 'SVG',
    'OBJECT', 'EMBED', 'NOEMBED',
    'IMG', 'PICTURE', 'SOURCE', 'MAP', 'AREA',
    'HR',
    'PROGRESS', 'METER',
    'COLGROUP', 'COL'
  ]);
  
  /** 対象要素か否かを判定する */
  const isTargetElement = element => element.nodeType === Node.ELEMENT_NODE && !excludedTags.has(element.tagName);
  
  /** 1要素分の data-inner-text 属性を更新する */
  const updateElement = element => {
    if(isTargetElement(element)) element.setAttribute('data-inner-text', element.innerText ?? '');
  };
  
  /** 指定要素の配下にある要素をまとめて更新する */
  const updateTree = rootElement => {
    if(isTargetElement(rootElement)) updateElement(rootElement);
    rootElement.querySelectorAll('*').forEach(updateElement);
  };
  
  // ページ初回読み込み時の DOM に適用する
  updateTree(document.body);
  
  // DOM 変更を監視する
  new MutationObserver(mutations => {
    for(const mutation of mutations) {
      // 子要素が追加・削除された場合
      if(mutation.type === 'childList') {
        mutation.addedNodes.forEach(updateTree);
      }
      
      // テキストが変更された場合
      if(mutation.type === 'characterData') {
        const parentElement = mutation.target.parentElement;
        if(parentElement != null) updateElement(parentElement);
      }
    }
  }).observe(document.body, {
    subtree: true,
    childList: true,
    characterData: true
  });
})();

以上のユーザスクリプトを実行すると、閲覧しているページの全要素に対して innerText の内容を data-inner-text 属性値に反映する。SPA などで DOM 変更が発生した場合も対応している。

そして、Stylus などのユーザスタイルシートが定義できる拡張機能を使って、以下のようにセレクタを書けば、指定の文言を含んだ要素を非表示にできる

/* いずれかの親要素の配下に、指定のキーワードを含んだ要素があったらその親要素を非表示にする例 */
:is(.PARENT-ELEMENT-1, .PARENT-ELEMENT-2):has([data-inner-text*="何らかのキーワード" i], [data-inner-text*="見たくないキーワード" i]) {
  display: none !important;
}

:is() を使って、非表示にしたい親要素を複数指定できる。例えば「YouTube トップページにある動画の大枠となる要素」とか、「Twitter の1投稿を囲んでいる要素」とか、そういう単位で指定すると良い。

:has() を使って「配下にある文字列を含んだ要素」が存在するかどうかをチェックしているが、属性セレクタの *= (部分一致) と i (ケースインセンシティブ = 大文字小文字を区別しない) をお好みで指定することで、ゆるく条件指定できる。

例えば Twitter のホームタイムラインで「煽り運転」に関する投稿を見たくないな、と思ったら、以下のように書けば良いだろう。

:is([aria-label*="タイムライン"] > div > [data-testid="cellInnerDiv"]):has([data-inner-text*="煽り運転" i], [data-inner-text*="Road Rage" i]) {
  display: none !important;
}

アイデア次第では、非表示にするのではなく逆に目立たせるようなスタイルも書けるので、良きように活用していただければと思う。