概要
ログイン不要で自由投稿が可能なものを考えてみたときに、リンクを貼られた場合にどんなサイトかわからないのが嫌でしたので、WEBページのサムネイルを表示できないかと思い、試行錯誤の結果実現できたものです。
(そんなサービスを作る気もないのですが・・・試したくなってしまいましたので)
※サーバーにはサムネイル画像は保存したくない前提です。
技術選定
まずスクリーンショットが取れるツールということで、以下のサービスがありましたが、規約とかに引っかかると面倒なので却下。
HeartRails Capture | サムネイル画像/PDF ファイル作成サービス
htmlをcanvas画像に変換できる以下のツールがありましたのでこれを使ってみました。
実装方法
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にしているのですが悪意のあるページをリンク表示したら危ない気がしてきました・・・なので、管理できる状況下でしか使えなさそうです。
追記
サイトの安全性をチェックするサービスがいくつかあるのでそれでチェックしたりすると良いかもしれません。