¿Cómo tomar una captura de pantalla de un div con JavaScript?

175

Estoy construyendo algo llamado "HTML Quiz". Se ejecuta completamente en JavaScript y es bastante bueno.

Al final, aparece un cuadro de resultados que dice "Tus resultados:" y muestra cuánto tiempo tomaron, qué porcentaje obtuvieron y cuántas preguntas obtuvieron de 10. Me gustaría tener un botón que diga "Capture resultados" y haga que de alguna manera tome una captura de pantalla o algo del div, y luego simplemente muestre la imagen capturada en la página donde pueden hacer clic derecho y "Guardar imagen como".

Realmente me encantaría hacer esto para que puedan compartir sus resultados con otros. No quiero que "copien" los resultados porque pueden cambiar eso fácilmente. Si cambian lo que dice en la imagen, bueno.

¿Alguien sabe una manera de hacer esto o algo similar?

Nathan
fuente
2
Echa un vistazo a phantomjs.org . Al usarlo, podría generar una imagen completa de la página HTML en el lado del servidor y darle al usuario un enlace para descargar.
Joshua
Buen artículo, contenido HTML para convertir imágenes y descargar codepedia.info/…
Satinder singh

Respuestas:

99

No, no conozco una forma de 'capturar' un elemento, pero lo que podría hacer es dibujar los resultados de la prueba en un elemento de lienzo, luego usar la función HTMLCanvasElementdel objeto toDataURLpara obtener un data:URI con el contenido de la imagen.

Cuando finalice el cuestionario, haga esto:

var c = document.getElementById('the_canvas_element_id');
var t = c.getContext('2d');
/* then use the canvas 2D drawing functions to add text, etc. for the result */

Cuando el usuario haga clic en "Capturar", haga esto:

window.open('', document.getElementById('the_canvas_element_id').toDataURL());

Esto abrirá una nueva pestaña o ventana con la 'captura de pantalla', permitiendo al usuario guardarla. No hay forma de invocar un tipo de diálogo 'guardar como', por lo que es lo mejor que puedes hacer en mi opinión.

Delan Azabani
fuente
¡¡Gracias!! No sé nada sobre lienzo, pero lo aprenderé. ¿Cómo usaría los efectos de dibujo 2D de Canvas? ¿Podrías ayudarme con esto? ¡Muchas gracias! :)
Nathan
55
Pruebe la documentación de Mozilla, es realmente bastante fácil de seguir: developer.mozilla.org/en/canvas_tutorial
Delan Azabani
En lugar de abrirlo en una nueva ventana / pestaña, ¿puedo abrirlo en un cuadro de diálogo en línea? (¿Como el plugin Fancybox lightbox?)
Nathan
Sí, puede tener un cuadro de diálogo en línea con uno imgque tenga srcconfigurado el data:URI. Sin embargo, eso no es realmente necesario: simplemente puede colocarlo canvasen el cuadro de diálogo en línea; los usuarios pueden hacer clic con el botón derecho y guardar el estado actual como una imagen.
Delan Azabani
Gracias :) Podría usar la versión de la imagen para que puedan hacer clic derecho y hacer clic en "guardar imagen como"
Nathan
89

Esta es una expansión de la respuesta de @ Dathan , usando html2canvas y FileSaver.js .

$(function() { 
    $("#btnSave").click(function() { 
        html2canvas($("#widget"), {
            onrendered: function(canvas) {
                theCanvas = canvas;


                canvas.toBlob(function(blob) {
                    saveAs(blob, "Dashboard.png"); 
                });
            }
        });
    });
});

Este bloque de código espera btnSavea que se haga clic en el botón con la identificación . Cuando es así, convierte el widgetdiv en un elemento de lienzo y luego usa la interfaz saveAs () FileSaver (a través de FileSaver.js en navegadores que no lo admiten de forma nativa) para guardar el div como una imagen llamada "Dashboard.png".

Un ejemplo de este trabajo está disponible en este violín .

Andy
fuente
77
Necesitaba agregar referencias a: html2canvas, protector de archivos, blob, canvas-toBlob. Después de eso funcionó. ¡Gracias!
sirthomas
1
Impresionante, esta debería ser la respuesta aceptada. Sencillo plug and play.
rgshenoy
¿Sabes cómo funcionaría esto con three.js? Tengo un div en el que el renderizador / lienzo three.js está en (renderer.domElement), y luego divs encima de eso. Me gustaría tomar una instantánea del DIV principal y capturar todo. es posible? ¡Gracias!
Rankinstudio
1
¿Puedes evitar que aparezca una imagen en tu DOM? ¿No puede simplemente descargarse de inmediato?
kulan
1
Esto funcionó muy bien para mí en el primer intento, e incluso se ve como el estilo CSS original, etc. Increíblemente preciso. Más o menos una captura de pantalla virtual con este nivel de precisión. Utilicé CDN para las 2 bibliotecas que tuve que agregar: <! - html2canvas -> <script src = " cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/… > <! - protector de archivos - > <script src = " cdn.jsdelivr.net/npm/[email protected]/dist/… >
OG Sean
13

Después de horas de investigación, finalmente encontré una solución para tomar una captura de pantalla de un elemento, incluso si la origin-cleanFLAG está configurada (para evitar XSS), es por eso que incluso puede capturar, por ejemplo, Google Maps (en mi caso). Escribí una función universal para obtener una captura de pantalla. Además, lo único que necesita es la biblioteca html2canvas ( https://html2canvas.hertzen.com/ ).

Ejemplo:

getScreenshotOfElement($("div#toBeCaptured").get(0), 0, 0, 100, 100, function(data) {
    // in the data variable there is the base64 image
    // exmaple for displaying the image in an <img>
    $("img#captured").attr("src", "data:image/png;base64,"+data);
});

Tenga en cuenta console.log()y alert()no generará resultados si el tamaño de la imagen es excelente.

Función:

function getScreenshotOfElement(element, posX, posY, width, height, callback) {
    html2canvas(element, {
        onrendered: function (canvas) {
            var context = canvas.getContext('2d');
            var imageData = context.getImageData(posX, posY, width, height).data;
            var outputCanvas = document.createElement('canvas');
            var outputContext = outputCanvas.getContext('2d');
            outputCanvas.width = width;
            outputCanvas.height = height;

            var idata = outputContext.createImageData(width, height);
            idata.data.set(imageData);
            outputContext.putImageData(idata, 0, 0);
            callback(outputCanvas.toDataURL().replace("data:image/png;base64,", ""));
        },
        width: width,
        height: height,
        useCORS: true,
        taintTest: false,
        allowTaint: false
    });
}
orange01
fuente
donde ha definido la función html2canvas
Bharathi
Integré la biblioteca html2canvas (ver enlace en mi publicación) antes de alcanzar la función getScreenshotOfElement (). Por ejemplo con la etiqueta <script>. Simplemente descargue html2canvas y cópielo en su proyecto.
orange01
@ orange01: sigo sin poder capturar los carriles de Google Map. Esto solo captura de pantalla el botón para acercar y alejar, el mapa y el texto del satélite. ¿Pueden ayudarme a capturar el mapa completo? Gracias
Farhan Sahibole
7

Si desea tener el cuadro de diálogo "Guardar como", simplemente pase la imagen al script php, que agrega los encabezados apropiados

Ejemplo de script "todo en uno" script.php

<?php if(isset($_GET['image'])):
    $image = $_GET['image'];

    if(preg_match('#^data:image/(.*);base64,(.*)$#s', $image, $match)){
        $base64 = $match[2];
        $imageBody = base64_decode($base64);
        $imageFormat = $match[1];

        header('Content-type: application/octet-stream');
        header("Pragma: public");
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: private", false); // required for certain browsers
        header("Content-Disposition: attachment; filename=\"file.".$imageFormat."\";" ); //png is default for toDataURL
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: ".strlen($imageBody));
        echo $imageBody;
    }
    exit();
endif;?>

<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js?ver=1.7.2'></script>
<canvas id="canvas" width="300" height="150"></canvas>
<button id="btn">Save</button>
<script>
    $(document).ready(function(){
        var canvas = document.getElementById('canvas');
        var oCtx = canvas.getContext("2d");
        oCtx.beginPath();
        oCtx.moveTo(0,0);
        oCtx.lineTo(300,150);
        oCtx.stroke();

        $('#btn').on('click', function(){
            // opens dialog but location doesnt change due to SaveAs Dialog
            document.location.href = '/script.php?image=' + canvas.toDataURL();
        });
    });
</script>
shukshin.ivan
fuente
3

No puede tomar una captura de pantalla: sería un riesgo de seguridad irresponsable permitirle hacerlo. Sin embargo, puedes:

  • Hacer cosas del lado del servidor y generar una imagen
  • Dibuje algo similar a un lienzo y renderícelo en una imagen (en un navegador que lo admita)
  • Use alguna otra biblioteca de dibujo para dibujar directamente a la imagen (lento, pero funcionaría en cualquier navegador)
bgw
fuente
¡Gracias! Iré con el lienzo, ya que no conozco ninguna "biblioteca de dibujo" ni sé cómo hacerlo. Tampoco soy tan bueno en lienzo, pero lo intentaré.
Nathan
2
Lo siento, tengo que comentar esto ya que proviene de Google, pero el "riesgo de seguridad irresponsable" de WTF podría preguntar por qué, si deja que JavaScript tome una captura de pantalla o una toma dentro de los cables dados y devuelva los datos codificados en BASE64 como una cadena, no hay problema de seguridad
Barkermn01
@MartinBarker Podría (por ejemplo) abrir la página de Facebook de alguien en un iframe y luego capturarla. Normalmente, un iframe entre sitios no es parte del DOM accesible para evitar XSS, pero sería mucho más difícil para el proveedor del navegador detenerlo en este caso.
bgw
2
@MartinBarker El Javascript podría proporcionar los datos a una fuente maliciosa sin que el usuario se dé cuenta, mientras que la "pantalla de impresión" es controlada por el usuario y no por algún sitio web arbitrario y no confiable.
bgw
2
Qué paliza. @PiPeep, si una página web puede cargar un iFrame de Facebook, ya existe aislamiento, ¿por qué no extender dicho aislamiento con una API de captura de pantalla? p.ej. Div.ToPNG (), donde se elimina el iFrame antes mencionado? No es un riesgo de seguridad irresponsable, simplemente no está incorporado y nadie ha trabajado con problemas "potenciales" de privacidad / seguridad en el desarrollo de una API de captura de pantalla incorporada. Sería inmensamente útil para una miríada de casos de uso, como el soporte de aplicaciones web de intranet, por ejemplo.
Todd
3
var shot1=imagify($('#widget')[0], (base64) => {
  $('img.screenshot').attr('src', base64);
});

Eche un vistazo al paquete htmlshot , luego, revise profundamente la sección del lado del cliente :

npm install htmlshot
Abdennour TOUMI
fuente
¿Se mantiene este proyecto htmlshot? El enlace github de npm todavía no parece existir. ¿Hay otro sitio web para ello?
whatsTheDiff
@whatsTheDiff:: dame tus requisitos y asegúrate de que te ayudaré, ya que soy el autor de esta biblioteca.
Abdennour TOUMI
2
Gracias @ abdennour-toumi. Acabo de hacer una instalación de npm para ver el código. Parece que está usando html2canvas, que he probado y tengo problemas con IE y debido a la complejidad y SVG de mi página. Parece que esta biblioteca no me ayudará en ese sentido.
whatsTheDiff
44
Su cliente debe evitar IE ... ¡dígaselo!
Abdennour TOUMI
1
<script src="/assets/backend/js/html2canvas.min.js"></script>


<script>
    $("#download").on('click', function(){
        html2canvas($("#printform"), {
            onrendered: function (canvas) {
                var url = canvas.toDataURL();

                var triggerDownload = $("<a>").attr("href", url).attr("download", getNowFormatDate()+"电子签名详细信息.jpeg").appendTo("body");
                triggerDownload[0].click();
                triggerDownload.remove();
            }
        });
    })
</script>

cotización

fallen.lu
fuente
1

Es simple que pueda usar este código para capturar la captura de pantalla de un área particular que tiene que definir la identificación div en html2canvas. Estoy usando aquí 2 div-:

div id = "car"
div id = "chartContainer"
si quieres capturar solo autos, entonces usa car i'm capture here car solo puedes cambiar chartContainer para capturar el gráfico html2canvas ($ ( '#car') copia y pega este código

<html>
    <head>


<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.5/jspdf.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
<script>
    window.onload = function () {
    
    var chart = new CanvasJS.Chart("chartContainer", {
        animationEnabled: true,
        theme: "light2",
        title:{
            text: "Simple Line Chart"
        },
        axisY:{
            includeZero: false
        },
        data: [{        
            type: "line",       
            dataPoints: [
                { y: 450 },
                { y: 414},
                { y: 520, indexLabel: "highest",markerColor: "red", markerType: "triangle" },
                { y: 460 },
                { y: 450 },
                { y: 500 },
                { y: 480 },
                { y: 480 },
                { y: 410 , indexLabel: "lowest",markerColor: "DarkSlateGrey", markerType: "cross" },
                { y: 500 },
                { y: 480 },
                { y: 510 }

            ]
        }]
    });
    chart.render();
    
    }
</script>
</head>

<body bgcolor="black">
<div id="wholebody">  
<a href="javascript:genScreenshotgraph()"><button style="background:aqua; cursor:pointer">Get Screenshot of Cars onl </button> </a>

<div id="car" align="center">
    <i class="fa fa-car" style="font-size:70px;color:red;"></i>
    <i class="fa fa-car" style="font-size:60px;color:red;"></i>
    <i class="fa fa-car" style="font-size:50px;color:red;"></i>
    <i class="fa fa-car" style="font-size:20px;color:red;"></i>
    <i class="fa fa-car" style="font-size:50px;color:red;"></i>
    <i class="fa fa-car" style="font-size:60px;color:red;"></i>
    <i class="fa fa-car" style="font-size:70px;color:red;"></i>
</div>
<br>
<div id="chartContainer" style="height: 370px; width: 100%;"></div>
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>

<div id="box1">
</div>
</div>>
</body>

<script>

function genScreenshotgraph() 
{
    html2canvas($('#car'), {
        
      onrendered: function(canvas) {

        var imgData = canvas.toDataURL("image/jpeg");
        var pdf = new jsPDF();
        pdf.addImage(imgData, 'JPEG', 0, 0, -180, -180);
        pdf.save("download.pdf");
        
      
      
      }
     });

}

</script>

</html>

Rudra Pratap Singh
fuente
1
¿Por qué se incluye jQuery dos veces y por qué se incluye jQuery UI? Honestamente, jQuery no es necesario aquí por lo que puedo decir. document.getElementById('car')odocument.querySelector('#car')
Nathan
En realidad estoy trabajando en mi proyecto. así que me olvido de eliminar la biblioteca y nada.
Rudra Pratap Singh
0

Por lo que sé, no puedes hacer eso, puedo estar equivocado. Sin embargo, haría esto con php, generaría un JPEG usando las funciones estándar de php y luego mostraría la imagen, no debería ser un trabajo muy difícil, sin embargo, depende de cuán llamativos sean los contenidos del DIV

Saulius Antanavicius
fuente
El OP nunca sugirió que está usando el procesamiento del lado del servidor, aunque si lo fuera, estaría bien.
Delan Azabani
Sí, desafortunadamente no puedo usar cosas del lado del servidor. Probablemente iré con una de las respuestas anteriores. ¡Pero gracias! :)
Nathan
-3

Hasta donde yo sé, no es posible con JavaScript.

Lo que puede hacer por cada resultado es crear una captura de pantalla, guardarla en algún lugar y señalar al usuario cuando se hace clic en Guardar resultado. (Supongo que ninguno de los resultados es solo 10, por lo que no es un gran problema para crear una imagen de resultados de 10 jpeg)

Seúl
fuente
Gracias por responder :) Tienes razón! Simplemente podría crear 10 imágenes JPG de los resultados y luego, una vez que hagan clic en el botón, se mostrará la imagen para que la guarden. El único problema es que cuando comienzan la prueba, tienen que ingresar su nombre, y lo muestro a través de la prueba, y al final en el cuadro de resultados, muestra su nombre y dice un comentario como "Mejorando" etc. No podría ponerles el nombre en la imagen, ¿o sí?
Nathan