Html2canvas: Error al ejecutar 'toDataURL' en 'HTMLCanvasElement': los lienzos contaminados no se pueden exportar.

Creado en 2 ago. 2018  ·  12Comentarios  ·  Fuente: niklasvh/html2canvas

var canvasPromise  = html2canvas(document.body, {
                allowTaint: true,
                useCORS: true
            });
canvasPromise.then(function(canvas) {
    document.body.appendChild(canvas);
    console.log(canvas);
    canvas.toDataURL('image/png');
});

Informes de errores:

No detectado (en promesa) DOMException: no se pudo ejecutar 'toDataURL' en 'HTMLCanvasElement': los lienzos contaminados no se pueden exportar.

  • Versión html2canvas probada con:
  • Cromo 67.0.3396.99
  • Windows 10

Comentario más útil

También tengo los mismos problemas.
¿Encontraste una solución o una solución alternativa?

Todos 12 comentarios

También tengo los mismos problemas.
¿Encontraste una solución o una solución alternativa?

Estoy haciendo lo mismo en Firefox 61.0.2 y obtengo un SecurityError a pesar de configurar allowTaint en true

No hay solución que yo sepa, pero tengo una solución alternativa: cambie cada imagen a base64. De esa manera, puede representarlo en lienzo aunque sea originalmente de un dominio diferente.

¿Has visto esto? CORS_enabled_image

Si la fuente del contenido extranjero es un HTML elemento, no se permite intentar recuperar el contenido del lienzo.

Tan pronto como dibuja en un lienzo cualquier dato que se cargó desde otro origen sin la aprobación de CORS, el lienzo se contamina.

Yo uso las opciones de configuración como esta:

html2canvas(document.body, {
    allowTaint: true,
    foreignObjectRendering: true
});

Hola, enfrentamos el mismo problema. Seguimos la sugerencia de @ dorklord23 porque ya teníamos una URL de proxy que hizo la conversión.

Si alguien lo encontró útil, la solución fue:

      html2canvas(document.body, {
        proxy: this._proxyURL,
        allowTaint: true,
        onclone: (cloned) => convertAllImagesToBase64(this._proxyURL, cloned),
      }).then((canvas) => {
        this._postmessageChannel.send(`get.screenshot:${canvas.toDataURL('image/png')}`);
      });

Donde la función auxiliar convertAllImagesToBase64 es:

const convertAllImagesToBase64 = (proxyURL, cloned) => {
  const pendingImagesPromises = [];
  const pendingPromisesData = [];

  const images = cloned.getElementsByTagName('img');

  for (let i = 0; i < images.length; i += 1) {
    // First we create an empty promise for each image
    const promise = new Promise((resolve, reject) => {
      pendingPromisesData.push({
        index: i, resolve, reject,
      });
    });
    // We save the promise for later resolve them
    pendingImagesPromises.push(promise);
  }

  for (let i = 0; i < images.length; i += 1) {
    // We fetch the current image
    fetch(`${proxyURL}?url=${images[i].src}`)
      .then((response) => response.json())
      .then((data) => {
        const pending = pendingPromisesData.find((p) => p.index === i);
        images[i].src = data;
        pending.resolve(data);
      })
      .catch((e) => {
        const pending = pendingPromisesData.find((p) => p.index === i);
        pending.reject(e);
      });
  }

  // This will resolve only when all the promises resolve
  return Promise.all(pendingImagesPromises);
};

export { convertAllImagesToBase64 };

Por cierto, estas son las pruebas para esa función auxiliar (estamos usando jest para wrting test y paquetes mockFetch):

import { convertAllImagesToBase64 } from '../images';

fetch.resetMocks();

// Mock fetch to respond different for each image so we can assert that the image return the correct response
// Also make one of the response be delayed (2 seconds) to simulate the response is not in the same order we do the call (network latency, image size, etc)
fetch.mockImplementation((url) => {
  if (url.includes('imagesrc1')) {
    return Promise.resolve(new Response(JSON.stringify('data:image/png;base64,1')));
  } else if (url.includes('imagesrc2')) {
    return new Promise((resolve) => setTimeout(resolve(new Response(JSON.stringify('data:image/png;base64,2'))), 2000));
  } else if (url.includes('imagesrc3')) {
    return Promise.resolve(new Response(JSON.stringify('data:image/png;base64,3')));
  }
  return Promise.resolve(new Response(JSON.stringify('')));
});

const mocksImages = [
  { id: 1, src: 'imagesrc1' },
  { id: 2, src: 'imagesrc2' },
  { id: 3, src: 'imagesrc3' },
];

const mockClone = {
  getElementsByTagName: jest.fn(() => mocksImages),
};

describe('utils/images', () => {  
  it('convertAllImagesToBase64. Expect to call 3 times to the correct enpoint using the image source', async () => {
    const allPromises = convertAllImagesToBase64('http://localhost/fake_proxy', mockClone);

    // Expect the clone elements gets all the image tags
    expect(mockClone.getElementsByTagName).toBeCalledWith('img');

    allPromises.then(() => {
      // Expect to have done the 3 fetch calls and with the correct params
      expect(fetch).toBeCalledTimes(3);
      expect(fetch).toHaveBeenNthCalledWith(1, 'http://localhost/fake_proxy?url=imagesrc1');
      expect(fetch).toHaveBeenNthCalledWith(2, 'http://localhost/fake_proxy?url=imagesrc2');
      expect(fetch).toHaveBeenNthCalledWith(3, 'http://localhost/fake_proxy?url=imagesrc3');

      // Expect that our images where updated properly
      expect(mocksImages).toContainEqual({
        id: 1, src: 'data:image/png;base64,1',
      });
      expect(mocksImages).toContainEqual({
        id: 2, src: 'data:image/png;base64,2',
      });
      expect(mocksImages).toContainEqual({
        id: 3, src: 'data:image/png;base64,3',
      });
    });
  });
});

Ejecución de backend de Ruby:

require 'base64'
require 'net/http'

module Api
  module V1
    class ImageProxyController < ApiController
      def index
        url   = URI.parse(params[:url])
        image = Net::HTTP.get_response(url)

        render json: data_url(image).to_json, callback: params[:callback]
      end

      private

        def data_url(image)
          "data:#{image.content_type};base64,#{Base64.encode64(image.body)}"
        end

    end
  end
end

Espero que alguien lo encuentre útil. Espero que ayude a alguien a no invertir tanto tiempo como nosotros para solucionar este problema correctamente.

Si puede ver alguna mejora, sugiérala.
Saludos.

Si te gusta a continuación, ¿qué pasará?

const TempImage = window.Image

 const Image = function() {
        const img = new TempImage()
        img.crossOrigin = 'anonymous'
        return img
 }

Encontré una solución y está funcionando

  1. Al llamar a html2canvas, pase useCORS true
    html2canvas (selectorElement, {useCORS: true} ) .entonces (lienzo => {
    //hacer algo
    });

  2. Corrija el archivo html2canvas.js. Hay un error tipográfico
    Cambie "anónimo" a "Anónimo" en este bloque if
    if (isInlineBase64Image (src) || ​​useCORS) {
    img.crossOrigin = 'Anónimo';
    }

var canvasPromise  = html2canvas(document.body, {
                allowTaint: true,
                useCORS: true
            });
canvasPromise.then(function(canvas) {
    document.body.appendChild(canvas);
    console.log(canvas);
    canvas.toDataURL('image/png');
});

Informes de errores:

No detectado (en promesa) DOMException: no se pudo ejecutar 'toDataURL' en 'HTMLCanvasElement': los lienzos contaminados no se pueden exportar.

  • Versión html2canvas probada con:
  • Cromo 67.0.3396.99
  • Windows 10

Deberá usar solo la propiedad 'useCORS: true', si usa la propiedad 'allowTaint: true', le da permiso para convertir su lienzo en un lienzo contaminado

UTILIZAR ESTA:

var canvasPromise  = html2canvas(document.body, {
                useCORS: true
            });
canvasPromise.then(function(canvas) {
    document.body.appendChild(canvas);
    console.log(canvas);
    canvas.toDataURL('image/png');
});

EN LUGAR DE ESTO:

var canvasPromise  = html2canvas(document.body, {
                allowTaint: true,
                useCORS: true
            });
canvasPromise.then(function(canvas) {
    document.body.appendChild(canvas);
    console.log(canvas);
    canvas.toDataURL('image/png');
});

Hola, buen trabajo para html2canvas.

Lamentablemente, estoy enfrentando el mismo problema, ¿alguien lo ha resuelto?
Ya probé @motarock y todo antes, más combinaciones, etc. La imagen es blanca sin nada pintado.

downloadQRCode = (fileName) => {
    html2canvas(document.getElementById('generated-qr-code'), {
      useCORS: true,
      // allowTaint: false,
      // logging: true,
    }).then((canvas) => {
      // document.body.appendChild(canvas); // checking
      const data = canvas.toDataURL('image/jpeg');
      const element = document.createElement('a');
      element.setAttribute('href', data);
      element.setAttribute('download', fileName + '.jpeg');
      document.body.appendChild(element);
      element.click();
      // document.body.removeChild(element);
      console.log("%c data", "font-size:2em;", data, fileName);
      console.log("%c canvas", "font-size:2em;", canvas );
      this.setState({
        imageSrc: data // setting the src of a img tag to check the result. Nothing in it either..
      })
    });

Gracias por adelantado

También tengo los mismos problemas.
¿Alguien puede solucionar este problema?

:( mismo problema. tenemos html con svg anidado y no se procesará

Ya probé @motarock y todo antes, más combinaciones, etc. La imagen es blanca sin nada pintado.

Tengo este problema cuando no uso SSL, con SSL funciona perfectamente

¿Fue útil esta página
0 / 5 - 0 calificaciones