Utilizzo di HTML5 / Canvas / JavaScript per acquisire schermate nel browser


924

"Segnala un bug" o "Strumento di feedback" di Google ti consente di selezionare un'area della finestra del browser per creare uno screenshot che viene inviato con il tuo feedback su un bug.

Schermata dello strumento di feedback di Google Schermata di Jason Small, pubblicata in una domanda duplicata .

Come lo stanno facendo? L'API di feedback JavaScript di Google viene caricata da qui e la loro panoramica del modulo di feedback dimostrerà la capacità dello screenshot.


2
Elliott Sprehn ha scritto in un Tweet qualche giorno fa:> @CatChen Quel post di StackOverflow non è accurato. Lo screenshot di Google Feedback viene eseguito interamente sul lato client. :)
Goran Rakic ​​l'

1
Questo è logico perché vogliono capire esattamente come il browser dell'utente sta eseguendo il rendering di una pagina, non come lo renderanno sul lato server usando il loro motore. Se invii solo il DOM della pagina corrente al server, mancherà qualsiasi incoerenza nel modo in cui il browser sta eseguendo il rendering dell'HTML. Ciò non significa che la risposta di Chen sia errata per lo screenshot, sembra solo che Google lo stia facendo in modo diverso.
Goran Rakic,

Elliott ha menzionato Jan Kuča oggi, e ho trovato questo link nel tweet di Jan: jankuca.tumblr.com/post/7391640769/…
Cat Chen,

Ci approfondirò più avanti e vedrò come si può fare con il motore di rendering sul lato client e verificherò se Google lo sta effettivamente facendo in quel modo.
Cat Chen,

Vedo l'uso di compareDocumentPosition, getBoxObjectFor, toDataURL, drawImage, tracking padding e cose del genere. Sono migliaia di righe di codice offuscato da de-offuscare e guardare attraverso. Mi piacerebbe vederne una versione con licenza open source, ho contattato Elliott Sprehn!
Luke Stanley,

Risposte:


1155

JavaScript può leggere il DOM e renderne una rappresentazione abbastanza accurata canvas. Ho lavorato su uno script che converte HTML in un'immagine tela. Oggi ho deciso di implementarlo per inviare feedback come da te descritto.

Lo script consente di creare moduli di feedback che includono uno screenshot, creato sul browser del client, insieme al modulo. Lo screenshot si basa sul DOM e come tale potrebbe non essere accurato al 100% rispetto alla rappresentazione reale in quanto non crea uno screenshot reale, ma crea lo screenshot in base alle informazioni disponibili sulla pagina.

Non richiede alcun rendering dal server , poiché l'intera immagine viene creata sul browser del client. Lo stesso script HTML2Canvas è ancora in uno stato molto sperimentale, in quanto non analizza quasi la maggior parte degli attributi CSS3 che vorrei, né ha alcun supporto per caricare immagini CORS anche se era disponibile un proxy.

Compatibilità del browser ancora piuttosto limitata (non perché non ne sia stato possibile supportare di più, ma non ho avuto il tempo di renderlo più supportato dal browser)

Per ulteriori informazioni, dai un'occhiata agli esempi qui:

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

modifica Lo script html2canvas è ora disponibile separatamente qui e alcuni esempi qui .

modifica 2 Un'altra conferma che Google utilizza un metodo molto simile (in effetti, in base alla documentazione, l'unica grande differenza è il loro metodo asincrono di attraversare / disegnare) può essere trovata in questa presentazione di Elliott Sprehn del team di Google Feedback: http: //www.elliottsprehn.com/preso/fluentconf/


1
Molto interessante, Sikuli o Selenium potrebbe essere utile per visitare diversi siti, confrontando una foto del sito dallo strumento di test con l'immagine renderizzata html2canvas.js in termini di somiglianza dei pixel! Mi chiedo se potresti attraversare automaticamente parti del DOM con un risolutore di formule molto semplice per scoprire come analizzare origini dati alternative per i browser in cui getBoundingClientRect non è disponibile. Probabilmente lo userei se fosse open source, stavo prendendo in considerazione l'idea di giocarci da solo. Bel lavoro Niklas!
Luke Stanley,

1
@Luke Stanley Molto probabilmente vomiterò la fonte su github questo fine settimana, ancora qualche piccola pulizia e modifiche che voglio fare prima di allora, oltre a sbarazzarmi della dipendenza jQuery non necessaria che ha attualmente.
Niklas,

43
Il codice sorgente è ora disponibile su github.com/niklasvh/html2canvas , alcuni esempi dello script in uso html2canvas.hertzen.com lì. Ci sono ancora molti bug da correggere, quindi non consiglierei ancora di usare lo script in un ambiente live.
Niklas,

2
qualsiasi soluzione per farlo funzionare per SVG sarà di grande aiuto. Non funziona con highcharts.com
Jagdeep il

3
@Niklas Vedo che il tuo esempio è diventato un vero progetto. Forse aggiorni il tuo commento più votato sulla natura sperimentale del progetto. Dopo quasi 900 impegni, penso che a questo punto sia un po 'più di un esperimento ;-)
Jogai,

70

L'app Web ora può acquisire uno screenshot "nativo" dell'intero desktop del client utilizzando getUserMedia():

Dai un'occhiata a questo esempio:

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

Il client dovrà utilizzare Chrome (per ora) e dovrà abilitare il supporto per l'acquisizione dello schermo in chrome: // flags.


2
non riesco a trovare alcuna dimostrazione di come fare uno screenshot: tutto ruota attorno alla condivisione dello schermo. Dovrò provarlo.
jwl

8
@XMight, puoi scegliere se permetterlo attivando il flag di supporto per la cattura dello schermo.
Matt Sinclair,

19
@XMight Per favore, non pensare in questo modo. I browser Web dovrebbero essere in grado di fare molte cose, ma sfortunatamente non sono coerenti con le loro implementazioni. È assolutamente ok, se un browser ha tale funzionalità, purché venga richiesto all'utente. Nessuno sarà in grado di fare uno screenshot senza la tua attenzione. Ma troppa paura si traduce in cattive implementazioni, come l'API degli Appunti, che è stata disabilitata del tutto, creando invece finestre di dialogo di conferma, come webcam, microfoni, funzionalità di screenshot, ecc.
StanE

3
Questo è stato deprecato e verrà rimosso dallo standard secondo developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia
Agustin Cautin

7
@AgustinCautin Navigator.getUserMedia()è deprecato, ma appena sotto dice "... Si prega di utilizzare il più nuovo navigator.mediaDevices.getUserMedia () ", ovvero è stato appena sostituito con una API più recente.
levant pied

37

Come accennato da Niklas , puoi usare la libreria html2canvas per fare uno screenshot usando JS nel browser. Estenderò la sua risposta in questo punto fornendo un esempio di come fare uno screenshot usando questa libreria:

In report()funzione onrendereddopo aver ottenuto l'immagine come URI di dati, puoi mostrarlo all'utente e permettergli di disegnare "bug region" con il mouse e quindi inviare uno screenshot e le coordinate della regione al server.

In questo esempio async/await è stata realizzata la versione: con una bella makeScreenshot()funzione .

AGGIORNARE

Semplice esempio che ti permette di fare screenshot, selezionare la regione, descrivere bug e inviare una richiesta POST ( qui jsfiddle ) (la funzione principale è report()).


10
Se vuoi dare un punto negativo, lascia anche un commento con una spiegazione
Kamil Kiełczewski,

Penso che il motivo per cui ti stai sottovalutando sia molto probabile che la libreria html2canvas sia la sua libreria, non uno strumento che ha semplicemente sottolineato.
zfrisch,

Va bene se non vuoi catturare effetti di post-elaborazione (come filtro di sfocatura).
vintproykt,

Limitazioni Tutte le immagini utilizzate dallo script devono risiedere nella stessa origine per poterle leggere senza l'assistenza di un proxy. Allo stesso modo, se nella pagina sono presenti altri elementi della tela, che sono stati contaminati con contenuti di origine incrociata, diventeranno sporchi e non più leggibili da html2canvas.
aravind3,

13

Ottieni screenshot come Canvas o Jpeg Blob / ArrayBuffer usando l' API 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)
    }

    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://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()
    })

    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()
    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)
    })
}

DEMO:

// 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",
})

Mi chiedo perché questo abbia avuto solo 1 voto, questo si è rivelato davvero utile!
Jay Dadhania,

Per favore, come funziona? Potete fornire una demo per i neofiti come me? Thx
kabrice

@kabrice Ho aggiunto una demo. Inserisci il codice nella console di Chrome. Se hai bisogno del supporto di vecchi browser, usa: babeljs.io/it/repl
Nikolay Makhonin

8

Ecco un esempio usando: 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}`));

Vale anche la pena dare un'occhiata ai documenti API di Screen Capture .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.