使用 HTML5 / Canvas / JavaScript 拍摄浏览器内屏幕截图

使用 Google 的 “报告错误” 或 “反馈工具”,您可以选择浏览器窗口的区域来创建屏幕截图,并在屏幕上提交有关错误的反馈。

Google反馈工具截图 Jason Small 的屏幕截图,张贴在一个重复的问题中

他们是如何做到的? Google 的 JavaScript 反馈 API 已从此处加载, 它们对反馈模块的概述将演示屏幕截图功能。

答案

JavaScript 可以读取 DOM 并使用canvas进行相当准确的表示。我一直在研究将 HTML 转换为画布图像的脚本。今天决定将其实现为发送您所描述的反馈。

该脚本允许您创建反馈表单,其中包括在客户端浏览器上创建的屏幕截图以及表单。屏幕截图基于 DOM,因此可能无法真实表示 100%的准确度,因为它无法生成实际的屏幕截图,而是根据页面上的可用信息构建屏幕截图。

不需要服务器提供任何渲染 ,因为整个图像都是在客户端的浏览器上创建的。 HTML2Canvas 脚本本身仍处于试验性状态,因为它无法解析我想要的几乎所有 CSS3 属性,即使有可用的代理,它也不支持加载 CORS 图像。

仍然与浏览器的兼容性非常有限(不是因为无法支持更多功能,只是没有时间使其更受跨浏览器支持)。

有关更多信息,请在此处查看示例:

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

编辑 html2canvas 脚本现在可以在此处单独使用, 在此处可以使用一些示例

编辑 2另一个证实 Google 使用了非常相似的方法(实际上,根据文档,唯一的主要区别是它们的遍历 / 绘制异步方法)可以在 Google 反馈小组的 Elliott Sprehn 的演示文稿中找到: http: //www.elliottsprehn.com/preso/fluentconf/

您的 Web 应用程序现在可以使用getUserMedia()拍摄客户端整个桌面的 “本地” 屏幕截图:

看一下这个例子:

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

客户端(现在必须)必须使用 chrome,并且需要在 chrome:// flags 下启用屏幕捕获支持。

正如Niklas 提到的,您可以使用html2canvas库在浏览器中使用 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>
<button onclick="report()">Take screenshot</button>

<div class="container">
  <img width="75%" class="screen">
</div>

在将图像作为数据 URI 获取后,在report()函数中onrendered可以显示给用户,并允许他用鼠标绘制 “错误区域”,然后将屏幕截图和区域坐标发送给服务器。

此示例中,创建了 async/await版本:具有不错的makeScreenshot()函数

更新

一个简单的示例,使您可以截屏,选择区域,描述错误并发送 POST 请求( 此处为 jsfiddle )(主要功能是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>
<body>
<div>Screenshot tester</div>
<button onclick="report()">Report bug</button>

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

<div class="bug-container hide">
  <div>Select bug region</div>
  <div class="bug">    
    <img width="75%" class="screen" >
    <div class="region"></div> 
  </div>
  <div>
    <textarea class="bug-desc">Describe bug here...</textarea>
  </div>
  <div class="send">SEND BUG</div>
</div>

</body>

document.body.innerHTML = '<video style="width: 100%; height: 100%; border: 1px black solid;"/>';

navigator.mediaDevices.getDisplayMedia()
.then( mediaStream => {
  const video = document.querySelector('video');
  video.srcObject = mediaStream;
  video.onloadedmetadata = e => {
    video.play();
    video.pause();
  };
})
.catch( err => console.log(`${err.name}: ${err.message}`));