使用HTML5/Canvas/JavaScript获取浏览器屏幕截图
谷歌的“报告一个Bug”或“反馈工具”允许你在浏览器窗口中选择一个区域来创建一个屏幕截图,该截图与你对Bug的反馈一起提交 杰森·斯莫尔截图,发布在使用HTML5/Canvas/JavaScript获取浏览器屏幕截图,javascript,html,canvas,screenshot,Javascript,Html,Canvas,Screenshot,谷歌的“报告一个Bug”或“反馈工具”允许你在浏览器窗口中选择一个区域来创建一个屏幕截图,该截图与你对Bug的反馈一起提交 杰森·斯莫尔截图,发布在 他们是怎么做到的?Google的JavaScript反馈API是从加载的,并将演示屏幕截图功能。JavaScript可以读取DOM,并使用画布呈现相当准确的DOM表示。我一直在编写一个脚本,将HTML转换为画布图像。今天决定将其实现为发送您所描述的反馈 该脚本允许您创建反馈表单,其中包括在客户端浏览器上创建的屏幕截图以及表单。屏幕截图基于DOM,
他们是怎么做到的?Google的JavaScript反馈API是从加载的,并将演示屏幕截图功能。JavaScript可以读取DOM,并使用
画布
呈现相当准确的DOM表示。我一直在编写一个脚本,将HTML转换为画布图像。今天决定将其实现为发送您所描述的反馈
该脚本允许您创建反馈表单,其中包括在客户端浏览器上创建的屏幕截图以及表单。屏幕截图基于DOM,因此可能不会100%精确到真实的表示,因为它不会生成实际的屏幕截图,而是基于页面上可用的信息构建屏幕截图
它不需要从服务器进行任何渲染,因为整个图像是在客户端浏览器上创建的。HTML2Canvas脚本本身仍然处于非常实验性的状态,因为它没有解析我希望它解析的CSS3属性,也没有任何支持来加载CORS图像,即使有代理可用
浏览器兼容性仍然相当有限(不是因为不能支持更多,只是没有时间让它更支持跨浏览器)
有关更多信息,请查看以下示例:
编辑
html2canvas脚本现在可以单独使用,有些还可以
编辑2
谷歌反馈团队Elliott Sprehn在本次演示中还证实了谷歌使用了一种非常类似的方法(事实上,根据文档,唯一的主要区别是他们的异步遍历/绘制方法):
您的web应用现在可以使用
getUserMedia()
获取客户端整个桌面的“本机”屏幕截图:
看看这个例子:
客户端将必须使用chrome(目前),并且需要在下启用屏幕捕获支持chrome://flags.PoC
您可以使用该库在浏览器中使用JS截图。在这一点上,我将通过提供一个使用此库截图的示例(“概念验证”)来扩展他的答案:
函数报告(){
让region=document.querySelector(“body”);//整个屏幕
html2canvas(区域{
onrendered:函数(画布){
让pngUrl=canvas.toDataURL();//数据URL格式的png
设img=document.querySelector(“.screen”);
img.src=pngUrl;
//在这里,您可以允许用户设置错误区域
//并将其与“pngUrl”一起发送到服务器
},
});
}
.container{
边缘顶部:10px;
边框:实心1px黑色;
}
截图测试器
截图
以下是一个示例:
document.body.innerHTML='';
navigator.mediaDevices.getDisplayMedia()
。然后(mediaStream=>{
const video=document.querySelector('video');
video.srcObject=mediaStream;
video.onloadedmetadata=e=>{
video.play();
video.pause();
};
})
.catch(err=>console.log(`${err.name}:${err.message}`));
同样值得一看的是这些文档。使用API以画布或Jpeg Blob/ArrayBuffer的形式获取屏幕截图: 修复1:仅对Electron.js使用带有chromeMediaSource的getUserMedia
修复2:抛出错误,而不是返回空对象
修复3:修复演示以防止错误:
必须从用户手势处理程序调用getDisplayMedia
// docs: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
// see: https://www.webrtc-experiment.com/Pluginfree-Screen-Sharing/#20893521368186473
// see: https://github.com/muaz-khan/WebRTC-Experiment/blob/master/Pluginfree-Screen-Sharing/conference.js
function getDisplayMedia(options) {
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
return navigator.mediaDevices.getDisplayMedia(options)
}
if (navigator.getDisplayMedia) {
return navigator.getDisplayMedia(options)
}
if (navigator.webkitGetDisplayMedia) {
return navigator.webkitGetDisplayMedia(options)
}
if (navigator.mozGetDisplayMedia) {
return navigator.mozGetDisplayMedia(options)
}
throw new Error('getDisplayMedia is not defined')
}
function getUserMedia(options) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(options)
}
if (navigator.getUserMedia) {
return navigator.getUserMedia(options)
}
if (navigator.webkitGetUserMedia) {
return navigator.webkitGetUserMedia(options)
}
if (navigator.mozGetUserMedia) {
return navigator.mozGetUserMedia(options)
}
throw new Error('getUserMedia is not defined')
}
async function takeScreenshotStream() {
// see: https://developer.mozilla.org/en-US/docs/Web/API/Window/screen
const width = screen.width * (window.devicePixelRatio || 1)
const height = screen.height * (window.devicePixelRatio || 1)
const errors = []
let stream
try {
stream = await getDisplayMedia({
audio: false,
// see: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints/video
video: {
width,
height,
frameRate: 1,
},
})
} catch (ex) {
errors.push(ex)
}
// for electron js
if (navigator.userAgent.indexOf('Electron') >= 0) {
try {
stream = await getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
// chromeMediaSourceId: source.id,
minWidth : width,
maxWidth : width,
minHeight : height,
maxHeight : height,
},
},
})
} catch (ex) {
errors.push(ex)
}
}
if (errors.length) {
console.debug(...errors)
if (!stream) {
throw errors[errors.length - 1]
}
}
return stream
}
async function takeScreenshotCanvas() {
const stream = await takeScreenshotStream()
// from: https://stackoverflow.com/a/57665309/5221762
const video = document.createElement('video')
const result = await new Promise((resolve, reject) => {
video.onloadedmetadata = () => {
video.play()
video.pause()
// from: https://github.com/kasprownik/electron-screencapture/blob/master/index.js
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
const context = canvas.getContext('2d')
// see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)
resolve(canvas)
}
video.srcObject = stream
})
stream.getTracks().forEach(function (track) {
track.stop()
})
if (result == null) {
throw new Error('Cannot take canvas screenshot')
}
return result
}
// from: https://stackoverflow.com/a/46182044/5221762
function getJpegBlob(canvas) {
return new Promise((resolve, reject) => {
// docs: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.95)
})
}
async function getJpegBytes(canvas) {
const blob = await getJpegBlob(canvas)
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.addEventListener('loadend', function () {
if (this.error) {
reject(this.error)
return
}
resolve(this.result)
})
fileReader.readAsArrayBuffer(blob)
})
}
async function takeScreenshotJpegBlob() {
const canvas = await takeScreenshotCanvas()
return getJpegBlob(canvas)
}
async function takeScreenshotJpegBytes() {
const canvas = await takeScreenshotCanvas()
return getJpegBytes(canvas)
}
function blobToCanvas(blob, maxWidth, maxHeight) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = function () {
const canvas = document.createElement('canvas')
const scale = Math.min(
1,
maxWidth ? maxWidth / img.width : 1,
maxHeight ? maxHeight / img.height : 1,
)
canvas.width = img.width * scale
canvas.height = img.height * scale
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height)
resolve(canvas)
}
img.onerror = () => {
reject(new Error('Error load blob to Image'))
}
img.src = URL.createObjectURL(blob)
})
}
演示:
document.body.onclick = async () => {
// take the screenshot
var screenshotJpegBlob = await takeScreenshotJpegBlob()
// show preview with max size 300 x 300 px
var previewCanvas = await blobToCanvas(screenshotJpegBlob, 300, 300)
previewCanvas.style.position = 'fixed'
document.body.appendChild(previewCanvas)
// send it to the server
var formdata = new FormData()
formdata.append("screenshot", screenshotJpegBlob)
await fetch('https://your-web-site.com/', {
method: 'POST',
body: formdata,
'Content-Type' : "multipart/form-data",
})
}
// and click on the page
下面是一个完整的屏幕截图示例,它在2021年与chrome一起使用。最终结果是一个准备好传输的blob。流程是:请求媒体>抓取帧>绘制到画布>传输到blob。如果您想更高效地使用内存,请探索
非常酷,Sikuli或Selenium可能适合访问不同的站点,将测试工具中的站点快照与html2canvas.js渲染图像的像素相似性进行比较!想知道您是否可以使用一个非常简单的公式求解器自动遍历DOM的各个部分,以找到如何为getBoundingClientRect不可用的浏览器解析备用数据源。如果它是开源的,我可能会使用它,我自己也在考虑玩弄它。干得好Niklas@Luke Stanley我很可能会在这个周末在github上发布源代码,在此之前我还想做一些小的清理和更改,并消除它目前不必要的jQuery依赖性。源代码现在可以在,一些正在使用的脚本示例中找到。仍然有很多bug需要修复,所以我不建议在实时环境中使用该脚本。任何使其适用于SVG的解决方案都会有很大帮助。它不适用于海图。com@Niklas我看到你的例子变成了一个真正的项目。也许可以更新你对该项目实验性质的最高估评论。近900次提交后,我认为这不仅仅是一次实验;-)Elliott Sprehn几天前:>@CatChen说stackoverflow帖子不准确。Google Feedback的屏幕截图完全是在客户端完成的。:)这是合乎逻辑的,因为他们希望准确地捕捉用户的浏览器呈现页面的方式,而不是在服务器端使用引擎呈现页面的方式。如果只将当前页面DOM发送到服务器,它将丢失浏览器呈现HTML的方式中的任何不一致。这并不意味着陈的回答在截图方面是错误的,只是看起来谷歌正在以不同的方式进行截图。Elliott今天提到了Jan Kuča,我在Jan的推文中找到了这个链接:我稍后会深入研究,看看如何与clien合作
document.body.onclick = async () => {
// take the screenshot
var screenshotJpegBlob = await takeScreenshotJpegBlob()
// show preview with max size 300 x 300 px
var previewCanvas = await blobToCanvas(screenshotJpegBlob, 300, 300)
previewCanvas.style.position = 'fixed'
document.body.appendChild(previewCanvas)
// send it to the server
var formdata = new FormData()
formdata.append("screenshot", screenshotJpegBlob)
await fetch('https://your-web-site.com/', {
method: 'POST',
body: formdata,
'Content-Type' : "multipart/form-data",
})
}
// and click on the page
// Request media
navigator.mediaDevices.getDisplayMedia().then(stream =>
{
// Grab frame from stream
let track = stream.getVideoTracks()[0];
let capture = new ImageCapture(track);
capture.grabFrame().then(bitmap =>
{
// Stop sharing
track.stop();
// Draw the bitmap to canvas
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext('2d').drawImage(bitmap, 0, 0);
// Grab blob from canvas
canvas.toBlob(blob => {
// Do things with blob here
console.log('output blob:', blob);
});
});
})
.catch(e => console.log(e));