Использование HTML5/Canvas/JavaScript для создания снимков экрана в браузере
Google "Сообщить об ошибке" или "Инструмент обратной связи" позволяет выбрать область окна браузера, чтобы создать снимок экрана, который будет отправлен вместе с вашим отзывом об ошибке.
Снимок экрана Джейсона Смолла, размещенный в дублирующем вопросе.
Как они это делают? API обратной связи Google JavaScript загружается отсюда, и их обзор модуля обратной связи продемонстрирует возможность снимка экрана.
7 ответов
JavaScript может читать DOM и отображать достаточно точное представление, используя canvas
, Я работал над сценарием, который преобразует HTML в изображение холста. Решил сегодня внедрить его в отправку отзывов, как вы описали.
Сценарий позволяет создавать формы обратной связи, которые включают в себя скриншот, созданный в браузере клиента, а также форму. Снимок экрана основан на DOM и, как таковой, может не быть на 100% точным к реальному представлению, поскольку он не создает фактического снимка экрана, а создает снимок экрана на основе информации, доступной на странице.
Не требует рендеринга с сервера, так как все изображение создается в браузере клиента. Сам скрипт HTML2Canvas все еще находится в очень экспериментальном состоянии, так как он не анализирует почти столько атрибутов CSS3, сколько я хотел бы, ни поддерживает загрузку изображений CORS, даже если прокси-сервер был доступен.
Все еще довольно ограниченная совместимость браузера (не потому, что больше не может быть поддержано, просто не было времени сделать его более кросс-браузерным).
Для получения дополнительной информации посмотрите примеры здесь:
http://hertzen.com/experiments/jsfeedback/
edit Сценарий html2canvas теперь доступен отдельно здесь и некоторые примеры здесь.
edit 2 Еще одно подтверждение того, что Google использует очень похожий метод (фактически, на основе документации, единственным существенным отличием является их асинхронный метод обхода / рисования), можно найти в этой презентации Эллиотта Спрена из команды Google Feedback: http://www.elliottsprehn.com/preso/fluentconf/
Ваше веб-приложение теперь может сделать "родной" скриншот всего рабочего стола клиента, используя getUserMedia()
:
Посмотрите на этот пример:
https://www.webrtc-experiment.com/Pluginfree-Screen-Sharing/
Клиент должен будет использовать Chrome (на данный момент) и должен будет включить поддержку захвата экрана в chrome://flags.
Получите снимок экрана как Canvas или Jpeg Blob / ArrayBuffer с помощью getDisplayMedia API:
// 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)
}
try {
// for electron js
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)
}
return stream
}
async function takeScreenshotCanvas() {
const stream = await takeScreenshotStream()
if (!stream) {
return null
}
// from: https://stackru.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()
})
return result
}
// from: https://stackru.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()
if (!canvas) {
return null
}
return getJpegBlob(canvas)
}
async function takeScreenshotJpegBytes() {
const canvas = await takeScreenshotCanvas()
if (!canvas) {
return null
}
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)
})
}
ДЕМО:
// 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
let formdata = new FormData()
formdata.append("screenshot", screenshotJpegBlob)
await fetch('https://your-web-site.com/', {
method: 'POST',
body: formdata,
'Content-Type' : "multipart/form-data",
})
Как упоминает Никлас, вы можете использовать библиотеку 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>
В report()
функция в onrendered
после получения изображения в качестве data-uri вы можете показать его пользователю и позволить ему нарисовать "область ошибки" с помощью мыши, а затем отправить скриншот и координаты региона на сервер.
В этом примере async/await
версия была сделана: с хорошим makeScreenshot()
функция.
Вот полный пример скриншота, который работает с Chrome в 2021 году. Конечным результатом является blob-объект, готовый к передаче. Последовательность действий: запрос мультимедиа> захват кадра> рисование на холсте> передача в большой двоичный объект. Если вы хотите сделать это более эффективным с точки зрения памяти, изучите OffscreenCanvas или, возможно, ImageBitmapRenderingContext.
https://jsfiddle.net/v24hyd3q/1/
// 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));
Вот пример использования: getDisplayMedia
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}`));
Также стоит ознакомиться с документацией Screen Capture API.
Вы можете попробовать мою новую библиотеку JS: screenshot.js.
Это позволяет сделать реальный снимок экрана.
Вы загружаете скрипт:
<script src="https://raw.githubusercontent.com/amiad/screenshot.js/master/screenshot.js"></script>
и сделайте скриншот:
new Screenshot({success: img => {
// callback function
myimage = img;
}});
Вы можете прочитать больше вариантов на странице проекта.