Webの業務システムなんかを作ってるとシステムのスクリーンショットを撮りたいっていう要望がだいたいあがる。PrintScreenでがんばってで終わればいいんだけど、システムの中でやってとか、まーお客さんの環境によってはキャプチャが動かせなかったり色々あるので、システムでやらないとまずい場合がある。あと、スクロールするページの場合、一回のスクショで全体を撮りたいみたいな要望もあったりする。

そん時にどうやるかという事。VueとかNuxtで作ったSPAなアプリとかでやりたい場合とかも。

最近のやり方としては↓の感じなのかなーと思う。

  1. ブラウザのネイティブの機能を使う
  2. javascriptのライブラリを使う
  3. サーバ側で処理する

ブラウザのネイティブの機能を使う

これはChrome前提。自分は使ったことないんだけど、↓あたりのものを使えばいけそう。

Chrome拡張を使うか自分で作ってやる感じのやり方。

javascriptのライブラリを使う

javascriptでやる場合はだいたい下記を使うんじゃないだろうか。

htmlをcanvasにレンダリングするのをjavascriptで作った変態(褒め言葉)モジュール。スターの数とかえらいことになってるの。相当使えるんだけど、100%の再現率じゃない。対応してないstyleの属性とかもあって、ブラウザで見えてるものとはちょっと変わっちゃう。スクショの用途にもよるけど、ある程度の再現が出来てない場合は使えない。あと、ここ半年くらい更新されて無いのでちょっと心配。

でも、特定のDOMだけレンダリングしないとか出来て、例えば個人情報的にこの場所は非表示にしてスクショ撮りたいとかの要望にも対応出来たりする。

サーバ側で処理する

で、自分はこれがいいと思う。サーバにブラウザで表示されてるHTMLのDOMを丸ごと送って、サーバ側で、サーバのchromeなんかで、レンダリングしてスクショ撮る感じ。

今見えてる状態のHTMLを送りつけるので、VueとかNuxtとかのSPAでもちゃんと上手くいく。

で、ブラウザのエミュレータみたいなものが必要なんだけど、最近だとpuppeteer っていうのがすごく使える。Googleが作ってるOSSのものらしいですよ。

スクショ撮った後に、パスワードかけて圧縮してDLさせたりとか色々制御できるのでこのやり方が一番融通効くと思う。

で、やり方。

クライアント側
//現在のHTMLを丸ごと取得
const htmldoc = document.documentElement.cloneNode(true)
//スクリプトタグが邪魔なので全部消す
;[...htmldoc.querySelectorAll('script')].forEach(e => e.remove())
//HTML文字列を取得
const htmlstr = htmldoc.outerHTML
//サーバに送信
this.$axios
.post('http://hoge.fuga/api/save', { data: htmlstr })
.then(function() {
	console.log('OK')
})

サーバにHTMLの文字列丸ごと送ってサーバ側のローカルに保存。そしたらパペティア動かす。

ローカルのファイルを見る場合は↓の感じでやる。

サーバ側

"use strict";
const puppeteer = require("puppeteer");

(async () => {
  //centosで動かす場合は--no-sandboxのオプションをつけないと動かない
  const browser = await puppeteer.launch({
    args: ["--no-sandbox", "--disable-setuid-sandbox"]
  });
  //ブラウザの定義
  const page = await browser.newPage();
  //画面の大きさ指定
  page.setViewport({ width: 1600, height: 900 });
  //保存してあるファイルを読み込み
  await page.goto("file:///home/hogeuser/saved.html", {
    waitUntil: "networkidle0" //遅延ロード
  });
  //スクリーンショットをページ全体で取る場合
  await page.screenshot({ path: "example.png", fullPage: true });
  //PDFをA4で作る場合
  await page.pdf({ path: "test.pdf", format: "A4" });
  //ブラウザを閉じる
  await browser.close();
})();

パペティアの方で色々制御したりも出来るのであとは要件しだい。

ついでに、Webフォントとか使ってる場合、font-faceの指定のところとかサーバ側のローカルにしておけばスクショ側でもちゃんとWebフォントが反映される。

パペティア自体はe2eのテストに使ったり、色んなサイトを巡回したりする用途が普通なのかしらね。

ただ、ブラウザの一番外のスクロールなら「fullPage: true」付ければ全画面のスクショ撮ってくれんるんけど、中のDOMのスクロールはさすがに無理なので、必要ならその辺を展開してからサーバに送るとかが必要かな。