Html2canvas: Falha ao executar 'toDataURL' em 'HTMLCanvasElement': telas contaminadas não podem ser exportadas.

Criado em 2 ago. 2018  ·  12Comentários  ·  Fonte: 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');
});

Relatório de erros:

Não capturado (em promessa) DOMException: Falha ao executar 'toDataURL' em 'HTMLCanvasElement': Telas contaminadas não podem ser exportadas.

  • Versão html2canvas testada com:
  • Chrome 67.0.3396.99
  • Windows 10

Comentários muito úteis

Eu também tenho os mesmos problemas.
Você encontrou uma solução ou alternativa?

Todos 12 comentários

Eu também tenho os mesmos problemas.
Você encontrou uma solução ou alternativa?

estou fazendo a mesma coisa no Firefox 61.0.2 e estou recebendo um SecurityError apesar de definir allowTaint como true

Nenhuma solução até onde eu sei, mas eu tenho uma solução alternativa: mude todas as imagens para base64. Dessa forma, você pode renderizá-lo no canvas, mesmo que seja originalmente de um domínio diferente.

Você já viu isto: CORS_enabled_image

Se a fonte do conteúdo estrangeiro for um HTML , a tentativa de recuperar o conteúdo da tela não é permitida.

Assim que você desenha em uma tela quaisquer dados que foram carregados de outra origem sem a aprovação do CORS, a tela fica contaminada.

Eu uso as opções de configuração assim:

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

Olá, enfrentamos o mesmo problema. Seguimos a sugestão de

Se alguém achou útil, a solução foi:

      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')}`);
      });

Onde a função auxiliar convertAllImagesToBase64 é:

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 };

A propósito, estes são os testes para a função auxiliar (estamos usando jest para escrever pacotes de teste e 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',
      });
    });
  });
});

Enpdoint de back-end 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 alguém ache isso útil. Espero que ajude alguém a não investir tanto tempo quanto nós para consertar isso de maneira adequada.

Se você puder ver alguma melhoria, por favor, sugira.
Cumprimentos.

Se você gostar de abaixo, o que vai acontecer?

const TempImage = window.Image

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

Encontrou uma solução e está funcionando

  1. Ao chamar html2canvas, passe useCORS true
    html2canvas (selectorElement, {useCORS: true} ) .then (canvas => {
    //faça alguma coisa
    });

  2. Arquivo html2canvas.js correto. Há um erro de digitação
    Altere "anônimo" para "Anônimo" neste bloco 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');
});

Relatório de erros:

Não capturado (em promessa) DOMException: Falha ao executar 'toDataURL' em 'HTMLCanvasElement': Telas contaminadas não podem ser exportadas.

  • Versão html2canvas testada com:
  • Chrome 67.0.3396.99
  • Windows 10

Você precisará usar apenas a propriedade 'useCORS: true', se você usar a propriedade 'allowTaint: true' você dá permissão para transformar sua tela em uma tela contaminada

USA ISTO:

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

EM VEZ DE ISSO:

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

Olá, bom trabalho para html2canvas.

Infelizmente estou enfrentando o mesmo problema, alguém resolveu isso?
Já tentei @motarock e tudo antes disso, mais combinações, etc. A imagem é branca sem 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..
      })
    });

desde já, obrigado

Eu também tenho os mesmos problemas.
Qualquer um pode consertar esse problema?

:( mesmo problema. Temos html com svg aninhado e não irá renderizar

Já tentei @motarock e tudo antes disso, mais combinações, etc. A imagem é branca sem nada pintado.

Eu tenho esse problema quando não uso SSL, com SSL funciona perfeitamente

Esta página foi útil?
0 / 5 - 0 avaliações