HTML
JavaScript
CORS
html2canvas

リンク先のサムネイル画像を表示させる(画像保存無し)

概要

ログイン不要で自由投稿が可能なものを考えてみたときに、リンクを貼られた場合にどんなサイトかわからないのが嫌でしたので、WEBページのサムネイルを表示できないかと思い、試行錯誤の結果実現できたものです。
(そんなサービスを作る気もないのですが・・・試したくなってしまいましたので)

※サーバーにはサムネイル画像は保存したくない前提です。

技術選定

まずスクリーンショットが取れるツールということで、以下のサービスがありましたが、規約とかに引っかかると面倒なので却下。

HeartRails Capture | サムネイル画像/PDF ファイル作成サービス

htmlをcanvas画像に変換できる以下のツールがありましたのでこれを使ってみました。

html2canvas
documentation

実装方法

Ajaxでリンク先のページのhtmlを取得し、それをcanvas化して画面に出力というイメージで作業しました。

詰まったところ

  • CORS問題 

こちらの方法で解決。

  • 取得してきたhtmlの見た目に影響を与えずに画面に配置してサムネイルを取得したい

baseタグで見た目維持を行い、iframeに出力することで解決。

問題点

  • 私が使用したバージョンのhtml2canvasはsvg画像がキャプチャできなかった

最新バージョンではできるみたいな記事もありましたがうまくいかなかった。
これはサムネイルなのでサイトの大体の見た目がわかればよかったのでとりあえずはOK。

  • canvasをtoDataURL()で画像に出来なかった

canvas.toDataURL()を使用したところ

DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

canvasに取り込まれている画像がCross-Originなのでcanvasが汚染されて画像に出来ないということらしいです。

どうしようもないので、画像にすることはあきらめ、canvasのまま表示させることにしました。

結果

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>外部リンクのサムネイルを表示する</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

<script>
document.addEventListener("DOMContentLoaded", () => {
    let index = 0;
    document.querySelectorAll('.link-area').forEach(async (target) => {

        const link = target.querySelector('a').getAttribute('href');
        const response = await axios({
            method:'get',
            url:'/cors?url=' + link,
        });

        // キャプチャ対象のhtmlをレンダリングするiframeを作る(hiddenにしてabsoluteにして不可視にしておく)
        const renderAreaId = 'render-area' + index;
        const renderArea = '<iframe id="' + renderAreaId + '" sandbox="allow-scripts allow-same-origin" width="1280" height="1024" scrolling="no" frameborder="no" style="position: absolute;"></iframe>'
        document.querySelector('#load-area').insertAdjacentHTML('beforeend', renderArea);

        const iframe = document.querySelector('#' + renderAreaId);
        iframe.contentDocument.open();
        iframe.contentDocument.write(response.data);
        iframe.contentDocument.close();

        iframe.onload = async () => {
            // キャプチャを取得
            const canvas = await html2canvas(iframe.contentDocument.querySelector('body'),{
                logging: false,
                allowTaint: true,
                useCORS: true,
                width: 1280,
                height: 1024,
            });

            canvas.style.width = parseInt(canvas.width / 4, 10) + 'px';
            canvas.style.height = parseInt(canvas.height / 4, 10) + 'px';
            target.insertBefore(canvas, target.firstChild);// aタグの前にキャプチャを追加
            iframe.remove();// レンダリング用iframe削除
        }
        index++;
    });
});
</script>
<style>
.link-area canvas {
    border: 2px solid #000;
    margin: 1rem;
}
</style>
</head>
<body>
    <div id="load-area" style="visibility: hidden;"></div>
    <div class="link-area">
        <a href="https://github.com/">https://github.com/</a>
    </div>
    <div class="link-area">
        <a href="https://www.google.com/">https://www.google.com/</a>
    </div>
    <div class="link-area">
        <a href="https://qiita.com/">https://qiita.com/</a>
    </div>
</body>
</html>

CORS問題とか色々あって出来そうにないと思っていました。一応できました!しかし、後々気づきました、残念ですが、画面表示維持のためにiframeでallow-scriptsにしているのですが悪意のあるページをリンク表示したら危ない気がしてきました・・・なので、管理できる状況下でしか使えなさそうです。

追記

サイトの安全性をチェックするサービスがいくつかあるのでそれでチェックしたりすると良いかもしれません。

Google Safe Browsingとか

test.png