Tarayıcı içi ekran görüntüleri almak için HTML5 / Canvas / JavaScript kullanma


924

Google'ın "Hata Bildir" veya "Geri Bildirim Aracı", bir hata hakkındaki görüşlerinizi içeren bir ekran görüntüsü oluşturmak için tarayıcı pencerenizin bir bölümünü seçmenize olanak tanır.

Google Geri Bildirim Aracı Ekran Görüntüsü Ekran görüntüsü Jason Small, yinelenen bir soru yayınladı .

Onlar bunu nasıl yapıyor? Google'ın JavaScript geri bildirim API'sı buradan yüklenir ve geri bildirim modülüne genel bakış ekran görüntüsü yeteneğini gösterir.


2
Elliott Sprehn bir kaç gün önce bir Tweet yazdı :> @CatChen Bu stackoverflow yazı doğru değil. Google Feedback'in ekran görüntüsü tamamen müşteri tarafında yapılır. :)
Goran Rakic

1
Bu, kullanıcının tarayıcısını motorunu kullanarak sunucu tarafında nasıl görüntüleyeceklerini değil, kullanıcının tarayıcısının tam olarak nasıl oluşturduğunu yakalamak istedikleri için mantıklı bir şekilde ekler. Geçerli DOM sayfasını sunucuya gönderirseniz, tarayıcının HTML'yi oluşturma biçimindeki tutarsızlıkları kaçırırsınız. Bu, Chen'in cevabının ekran görüntüsü almak için yanlış olduğu anlamına gelmez, Google'ın bunu farklı bir şekilde yaptığı gibi görünüyor.
Goran Rakic

Elliott bugün Jan Kuča'dan
Cat Chen

Bunu daha sonra inceleyeceğim ve istemci tarafı oluşturma motoruyla nasıl yapılabileceğini göreceğim ve Google'ın bunu gerçekten bu şekilde yapıp yapmadığını kontrol edeceğim.
Cat Chen

CompareDocumentPosition, getBoxObjectFor, toDataURL, drawImage, izleme dolgu ve bunun gibi şeylerin kullanımını görüyorum. Gizlemek ve yine de bakmak için binlerce satır gizlenmiş kod. Açık kaynaklı lisanslı bir versiyonunu görmek isterim, Elliott Sprehn ile iletişime geçtim!
Luke Stanley

Yanıtlar:


1154

JavaScript DOM'yi okuyabilir ve bunu kullanarak oldukça doğru bir şekilde temsil edilebilir canvas. HTML bir tuval görüntü dönüştüren bir komut dosyası üzerinde çalışıyorum. Bugün, sizin tarif ettiğiniz gibi geri bildirim göndermeye bir uygulama yapmaya karar verdi.

Komut dosyası, formla birlikte istemcinin tarayıcısında oluşturulan bir ekran görüntüsünü içeren geri bildirim formları oluşturmanıza olanak tanır. Ekran görüntüsü DOM'a dayanmaktadır ve bu nedenle gerçek bir ekran görüntüsü oluşturmadığı için gerçek temsile% 100 doğru olmayabilir, ancak ekran görüntüsünü sayfadaki bilgilere dayanarak oluşturur.

Bu sunucudan herhangi oluşturmayı gerektirmez bütün görüntü müşterinin tarayıcısında oluşturulur olarak,. HTML2Canvas betiğinin kendisi hala çok deneysel bir durumdadır, çünkü neredeyse istediğim CSS3 özniteliklerinin çoğunu ayrıştırmaz veya bir proxy mevcut olsa bile CORS görüntülerini yüklemek için herhangi bir desteği yoktur.

Hala oldukça sınırlı tarayıcı uyumluluğu (daha fazlası desteklenemediğinden değil, daha fazla tarayıcıyı destekleyecek zamanım olmadı).

Daha fazla bilgi için buradaki örneklere göz atın:

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

düzenlemek The'in html2canvas komut ayrı kullanıma sunulmuştur burada ve bazı buraya örnekler .

edit 2 Google'ın çok benzer bir yöntem kullandığına dair bir başka onay (aslında, belgelere dayanarak, tek büyük fark onların çapraz / çizim zaman uyumsuz yöntemidir) bu sunumda Google Geri Bildirim ekibinden Elliott Sprehn tarafından bulunabilir: http: //www.elliottsprehn.com/preso/fluentconf/


1
Çok havalı, Sikuli veya Selenium, sitenin test aracından html2canvas.js ile oluşturulmuş resminizi piksel benzerliği açısından karşılaştırmak için farklı sitelere gitmek için iyi olabilir! GetBoundingClientRect'in kullanılamadığı tarayıcılar için alternatif veri kaynaklarının nasıl ayrıştırılacağını bulmak için DOM'un parçalarını otomatik olarak çok basit bir formül çözücü ile geçip geçemeyeceğinizi merak edin. Açık kaynak olsaydı, kendim oynamayı düşünüyor olsaydım bunu kullanardım. İyi iş Niklas!
Luke Stanley

1
@Luke Stanley Büyük olasılıkla bu hafta sonu github'a kaynak atacağım, o zamandan önce yapmak istediğim bazı küçük temizlik ve değişiklikler ve şu anda sahip olduğu gereksiz jQuery bağımlılığından kurtulun.
Niklas

43
Kaynak kodu şu anda github.com/niklasvh/html2canvas adresinde mevcuttur , burada kullanılan html2canvas.hertzen.com komut dosyasının bazı örnekleri . Hala düzeltmek için hatalar çok, bu yüzden henüz canlı bir ortamda komut dosyası kullanmanızı tavsiye etmem.
Niklas

2
SVG için çalışmasını sağlayacak herhangi bir çözüm çok yardımcı olacaktır. Highcharts.com ile çalışmaz
Jagdeep

3
@Niklas Örneğinizin gerçek bir projeye dönüştüğünü görüyorum. Belki de projenin deneysel doğası hakkında en çok değerlendirilen yorumunuzu güncelleyin. Neredeyse 900 taahhüt sonra ben bu noktada bir deney biraz daha fazla düşünürdüm ;-)
Jogai

70

Web uygulamanız artık aşağıdakileri kullanarak müşterinin tüm masaüstünün 'yerel' ekran görüntüsünü alabilir getUserMedia():

Bu örneğe bir göz atın:

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

İstemcinin (şimdilik) chrome kullanması ve chrome: // flags altında ekran yakalama desteğini etkinleştirmesi gerekecek.


2
sadece bir ekran görüntüsü almanın herhangi bir demosunu bulamıyorum - her şey ekran paylaşımı ile ilgili. denemek zorunda kalacak.
jwl

8
@XMight, ekran yakalama desteği bayrağını değiştirerek buna izin verip vermemeyi seçebilirsiniz.
Matt Sinclair

19
@XMight Lütfen böyle düşünmeyin. Web tarayıcıları pek çok şey yapabilmelidir, ancak ne yazık ki uygulamaları ile tutarlı değildir. Kullanıcı sorulduğu sürece bir tarayıcı böyle bir işleve sahipse kesinlikle tamamdır. Hiç kimse dikkatinizi çekmeden ekran görüntüsü yapamaz. Ancak çok fazla korku, tamamen devre dışı bırakılan pano API'si gibi kötü uygulamalara neden olur, bunun yerine web kameraları, mikrofonlar, ekran görüntüsü yeteneği
vb.Gibi


7
@AgustinCautin Navigator.getUserMedia()kullanımdan kaldırıldı, ancak hemen altında "... Lütfen daha yeni navigator.mediaDevices.getUserMedia () kullanın " yani yeni bir API ile değiştirildi.
levant

37

Niklas'ın belirttiği gibi , tarayıcıda JS kullanarak ekran görüntüsü almak için html2canvas kütüphanesini kullanabilirsiniz . Bu kütüphanede bir ekran görüntüsü alma örneği vererek cevabını bu noktada genişleteceğim:

İçinde report() işlevin onrenderedveri URI olarak görüntüyü aldıktan sonra kullanıcıya göstermek ve ona fare tarafından "böcek bölgesini" çizmek ve daha sonra sunucuya bir ekran görüntüsü ve bölge koordinatları göndermek için izin verebilir.

In bu örnek async/await sürümünde yapıldı: güzel ile makeScreenshot()fonksiyonu .

GÜNCELLEME

Ekran görüntüsü almanıza, bölgeyi seçmenize, hatayı tanımlamanıza ve POST isteği göndermenize izin veren basit bir örnek ( burada jsfiddle ) (ana işlev report()).


10
Eksi puan vermek istiyorsanız, açıklama ile de yorum yapın
Kamil Kiełczewski

Bence neden indirilmemenizin nedeni büyük olasılıkla html2canvas kütüphanesinin kütüphanesi olması, sadece işaret ettiği bir araç değil.
zfrisch

İşlem sonrası efektleri (bulanıklaştırma filtresi olarak) yakalamak istemiyorsanız sorun yok.
vintproykt

Sınırlamalar Komut dosyasının kullandığı tüm görüntülerin bir proxy yardımı olmadan okuyabilmesi için aynı köken altında bulunması gerekir. Benzer şekilde, sayfada çapraz kaynaklı içerikle kirlenmiş başka tuval öğeleriniz varsa kirlenir ve artık html2canvas tarafından okunamaz.
aravind3

13

Tuval veya JPEG Blob / ArrayBuffer kullanma gibi ekran görüntüsü alın 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://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",
})

Bunun neden sadece 1 oy verdiğini merak ediyorum, bu gerçekten yardımcı oldu!
Jay Dadhania

Lütfen nasıl çalışır? Benim gibi yeni başlayanlar için bir demo sunabilir misiniz? Thx
kabrice

@kabrice Bir demo ekledim. Kodu Chrome konsoluna koymanız yeterlidir. Eski tarayıcı desteğine ihtiyacınız varsa kullanın: babeljs.io/en/repl
Nikolay Makhonin

8

Heres örneği: 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}`));

Ayrıca, Screen Capture API belgeleri de kontrol edilmeye değer.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.