HTML5/Canvas/JavaScriptを使用したインブラウザ・スクリーンショットの撮影

Google'の「Report a Bug" or "Feedback Tool" 」では、ブラウザウィンドウの一部を選択してスクリーンショットを作成し、バグに関するフィードバックを送信することができます。

. Screenshot by Jason Small, posted in a duplicate question.

どのようにしているのでしょうか? Google'のJavaScriptフィードバックAPIは、こちらから読み込まれ、彼らのフィードバックモジュールの概要では、スクリーンショットの機能が示されています。

ソリューション

JavaScript は DOM を読み込んで、canvas を使ってかなり正確な表現をすることができます。私は、HTMLをcanvas画像に変換するスクリプトを作っています。今日、あなたが説明したようなフィードバックを送るために、それを実装することに決めました。

このスクリプトでは、クライアントのブラウザで作成されたスクリーンショットをフォームと一緒に含むフィードバックフォームを作成することができます。スクリーンショットはDOMに基づいており、実際のスクリーンショットを作成するのではなく、ページ上で利用可能な情報に基づいてスクリーンショットを構築するため、実際の表現とは100%一致しないかもしれません。

また、画像全体がクライアントのブラウザ上で作成されるため、サーバーからのレンダリングは一切必要ありません。HTML2Canvas スクリプト自体はまだ非常に実験的な状態で、必要な CSS3 属性がほとんど解析されておらず、プロキシが利用できる場合でも CORS 画像の読み込みがサポートされていません。

また、ブラウザの互換性も非常に限られています(サポートできないものが増えたわけではなく、クロスブラウザに対応させるための時間がなかっただけです)。

詳しくは、こちらのサンプルをご覧ください。

http://hertzen.com/experiments/jsfeedback/

**編集しました。 html2canvas スクリプトは、個別に ここ といくつかの ここの例 で利用できるようになりました。

edit Google が非常に類似した方法を使用していることを確認するために、Google Feedback チームの Elliott Sprehn 氏によるプレゼンテーションを紹介します。 http://www.elliottsprehn.com/preso/fluentconf/

解説 (20)

Webアプリケーションは、getUserMedia()を使って、クライアントのデスクトップ全体の「ネイティブ」なスクリーンショットを撮ることができます。

この例を見てください。

https://www.webrtc-experiment.com/Pluginfree-Screen-Sharing/

クライアントは(現時点では)クロームを使用している必要があり、chrome://flagsでスクリーンキャプチャのサポートを有効にする必要があります。

解説 (8)

Niklasが述べているように][1]、[html2canvas][2]ライブラリを使用して、ブラウザでJSを使ってスクリーンショットを撮ることができます。この点について、このライブラリを使ってスクリーンショットを撮る例を提供することで、彼の答えを拡張します。

function report() {
  let region = document.querySelector("body"); // whole screen
  html2canvas(region, {
    onrendered: function(canvas) {
      let pngUrl = canvas.toDataURL(); // png in dataURL format
      let img = document.querySelector(".screen");
      img.src = pngUrl; 

      // here you can allow user to set bug-region
      // and send it with 'pngUrl' to server
    },
  });
}
.container {
  margin-top: 10px;
  border: solid 1px black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<div>Screenshot tester</div>
Take screenshot

<div class="container">

</div>

onrenderedreport()`関数では、データURIとして画像を取得した後、それをユーザに表示し、ユーザがマウスで「バグ領域」を描画できるようにして、スクリーンショットと領域の座標をサーバに送信することができます。

この例][3]では、async/awaitバージョンが作成されました: 素敵な`makeScreenshot()関数[.

UPDATE

スクリーンショットを撮り、地域を選択し、バグを記述し、POSTリクエストを送ることができるシンプルな例 ([こちらのjsfiddle][5]) (主な機能は report()) です。

async function report() {
    let screenshot = await makeScreenshot(); // png dataUrl
    let img = q(".screen");
    img.src = screenshot; 

    let c = q(".bug-container");
    c.classList.remove('hide')

    let box = await getBox();    
    c.classList.add('hide');

    send(screenshot,box); // sed post request  with bug image, region and description
    alert('To see POST requset with image go to: chrome console > network tab');
}

// ----- Helper functions

let q = s => document.querySelector(s); // query selector helper
window.report = report; // bind report be visible in fiddle html

async function  makeScreenshot(selector="body") 
{
  return new Promise((resolve, reject) => {  
    let node = document.querySelector(selector);

    html2canvas(node, { onrendered: (canvas) => {
        let pngUrl = canvas.toDataURL();      
        resolve(pngUrl);
    }});  
  });
}

async function getBox(box) {
  return new Promise((resolve, reject) => {
     let b = q(".bug");
     let r = q(".region");
     let scr = q(".screen");
     let send = q(".send");
     let start=0;
     let sx,sy,ex,ey=-1;
     r.style.width=0;
     r.style.height=0;

     let drawBox= () => {
         r.style.left   = (ex > 0 ? sx : sx+ex ) +'px'; 
         r.style.top    = (ey > 0 ? sy : sy+ey) +'px';
         r.style.width  = Math.abs(ex) +'px';
         r.style.height = Math.abs(ey) +'px'; 
     }

     //console.log({b,r, scr});
     b.addEventListener("click", e=>{
       if(start==0) {
         sx=e.pageX;
         sy=e.pageY;
         ex=0;
         ey=0;
         drawBox();
       }
       start=(start+1)%3;       
     });

     b.addEventListener("mousemove", e=>{
       //console.log(e)
       if(start==1) {
           ex=e.pageX-sx;
           ey=e.pageY-sy
           drawBox(); 
       }
     });

     send.addEventListener("click", e=>{
       start=0;
       let a=100/75 //zoom out img 75%       
       resolve({
          x:Math.floor(((ex > 0 ? sx : sx+ex )-scr.offsetLeft)*a),
          y:Math.floor(((ey > 0 ? sy : sy+ey )-b.offsetTop)*a),
          width:Math.floor(Math.abs(ex)*a),
          height:Math.floor(Math.abs(ex)*a),
          desc: q('.bug-desc').value
          });

     });
  });
}

function send(image,box) {

    let formData = new FormData();
    let req = new XMLHttpRequest();

    formData.append("box", JSON.stringify(box)); 
    formData.append("screenshot", image);     

    req.open("POST", '/upload/screenshot');
    req.send(formData);
}
.bug-container { background: rgb(255,0,0,0.1); margin-top:20px; text-align: center; }
.send { border-radius:5px; padding:10px; background: green; cursor: pointer; }
.region { position: absolute; background: rgba(255,0,0,0.4); }
.example { height: 100px; background: yellow; }
.bug { margin-top: 10px; cursor: crosshair; }
.hide { display: none; }

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>

<div>Screenshot tester</div>
Report bug

<div class="example">Lorem ipsum</div>

<div class="bug-container hide">
  <div>Select bug region</div>
  <div class="bug">    

    <div class="region"></div> 
  </div>
  <div>
    <textarea class="bug-desc">Describe bug here...
解説 (4)