視認しにくいテキストを探す | その 2 puppeteer を使って任意の web サイト上のテキストに対して色差を計算する

前回は色差を計算する方法の簡単な紹介と chroma.js を使った計算について扱いました。
視認しにくいテキストを探す | その 1 chroma.js を使って色差を計算する | ごみばこいん Blog

今回は実践編として、特定の Web ページに対して色差を計算して「視認しにくいテキストを探す」ことをやります。

Web ページに対して色を確認するには、キャプチャしたものから気合で探すか、 CSS の結果を持って確認するか考えられそう。ここでは後者を puppeteer でやってみます。
puppeteer は以前紹介したとおり、 Headless Chrome を Node.js からお手軽に扱うためのものです。
Node.js から Headless Chrome を操作できる puppeteer を試す | ごみばこいん Blog

ページを読み込んで色差を計算するためにこんなコードを書きました。

// ブラウザの起動
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.setViewport({
    width: 1600,
    height: 900,
})

// デバッグ用に console.log を nodejs 側に渡す
page.on('console', msg => console.log(msg.text()));

// サイトにアクセスする
await page.goto('https://gomiba.co.in/test/color_difference.html');

// 色差を計算するために chroma をページ内で読み込む
await page.addScriptTag({
    url: 'https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.6/chroma.min.js',
});

// 色差が一定以上のものを探す
await page.evaluate(() => {
    // 全要素を探索していく
    document.querySelectorAll('*').forEach((element) => {
        // テキストノード、または SVG を子に持っている要素を探す
        let foundChildNode = Array.prototype.filter.call(element.childNodes, (e) => {
            let status = false;
            status = status || (e.nodeType === Node.TEXT_NODE && e.textContent.trim().length > 0);
            status = status || e.nodeName.toLowerCase() === 'svg';
            return status;
        });
        if (foundChildNode.length === 0) {
            return;
        }

        // 計算されたスタイルから色を取得
        let elementStyle = window.getComputedStyle(element);
        let fontColor = elementStyle.color;
        let backgroundColor = elementStyle.backgroundColor;

        // 色差を計算する
        let colorDiff = chroma.deltaE(fontColor, backgroundColor);

        // 計算された色差に透明度の色差を加える(透明度の差分 * 0.5 * 100)
        colorDiff += (Math.abs(chroma(fontColor).alpha() - chroma(backgroundColor).alpha()) * 0.5) * 100;

        // 色差が大きいものは無視
        if (colorDiff > 40) {
            return;
        }

        // 色差が小さいものに色付けをする
        console.log(element.nodeName + " : " + colorDiff)
        element.style.cssText = element.style.cssText + 'border: 5px dashed red !important;';
    });
});


// スクリーンショットを撮ってみる
await page.screenshot({path: 'example.png'});


// ブラウザを終了する
await browser.close();

puppeteer の playground がオンラインで提供されているので、コピペで動作確認できるはず、お試しあれ。
Try Puppeteer

手元に Node.js の環境があるなら、こちらのリポジトリでも。コマンドライン引数で対象 URL とキャプチャの保存するファイル名を受け取って、マーカーが引かれたキャプチャ画像が出力されます。
sters/web-color-difference

たとえば medium でやってみるとこんな感じ。白背景の部分で、薄めグレーの箇所ががマークされている。

たとえば、ランダムに色を設定するページを用意して試してみるとこんな具合。

確かに見にくい!!!なものはマークされているようだけど、ちょっと怪しいライン。もうちょっと基準値を調整したり CIEDE2000 のロジックも試すかなどして詰めたほうがいいかも…?とりあえず、のところはこれでもいいんじゃなかろーか。
元々やりたかった「視認しにくいテキストを探す」は出来たとしておしまい。

…と思ったけど、視認しにくいテキストっていうと話題が広いな、小さすぎる文字とか他の要素と被っているとかそういうのもそうなんだよねきっと、うーん。

前後の記事

Next:
Prev: