링크처의 썸네일 화상을 표시시킨다(화상 보존 없음)

개요



로그인 불필요하고 자유 투고가 가능한 것을 생각해 보았을 때에, 링크를 붙일 경우에 어떤 사이트 모르는 것이 싫었습니다. 입니다.
(그런 서비스를 만들 생각도 없지만 ... 시험하고 싶어져 버렸기 때문에)

※서버에는 썸네일 화상은 보존하고 싶지 않은 전제입니다.

기술선정



우선 스크린샷을 취할 수 있는 툴이라고 하는 것으로, 이하의 서비스가 있었습니다만, 규약등에 걸리면 귀찮기 때문에 각하.

HeartRails Capture | 썸네일 이미지 / PDF 파일 작성 서비스

html을 canvas 이미지로 변환 할 수있는 다음 도구가 있었으므로 이것을 사용해 보았습니다.

html2canvas
documentation

구현 방법



Ajax에서 링크 대상 페이지의 html을 취득하고 그것을 캔버스화하여 화면에 출력이라는 이미지로 작업했습니다.

막힌 곳


  • CORS 문제

  • 여기 방법 에서 해결.
  • 가져온 HTML의 외형에 영향을주지 않고 화면에 배치하여 썸네일을 얻고 싶습니다.
    base 태그로 외형 유지를 실시해, iframe 에 출력하는 것으로 해결.

    문제점


  • 내가 사용한 버전의 html2canvas는 svg 이미지를 캡처하지 못했습니다

  • 최신 버젼에서는 할 수 있는 것 같은 기사도 있었습니다만 잘 되지 않았다.
    이것은 썸네일이므로 사이트의 대략의 외형을 알면 좋았기 때문에 우선은 OK.
  • canvas를 toDataURL()로 이미지 할 수 없었습니다
  • canvas.toDataURL()
    DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
    

    캔버스에 캡처 된 이미지가 Cross-Origin이므로 캔버스가 오염되어 이미지로 할 수 없다는 것 같습니다.

    어쩔 수 없기 때문에, 이미지로 하는 것은 포기하고, 캔버스 그대로 표시하게 하기로 했습니다.

    결과


    <!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

    좋은 웹페이지 즐겨찾기