Html2canvas: 'HTMLCanvasElement' рдкрд░ 'toDataURL' рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓: рджрд╛рдЧреА рдХреИрдирд╡рд╕ рдирд┐рд░реНрдпрд╛рдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

рдХреЛ рдирд┐рд░реНрдорд┐рдд 2 рдЕрдЧре░ 2018  ┬╖  12рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: 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');
});

рджреЛрд╖ рд░рд┐рдкреЛрд░реНрдЯ:

рдзреНрдпрд╛рди рдореЗрдВ рдирд╣реАрдВ рдЖрдпрд╛ (рд╡рд╛рджреЗ рдореЗрдВ) DOMException: 'HTMLCanvasElement' рдкрд░ 'toDataURL' рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓: рджрд╛рдЧреА рдХреИрдирд╡рд╛рд╕ рдирд┐рд░реНрдпрд╛рдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

  • html2canvas рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд╕рд╛рде рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рдЧрдпрд╛:
  • рдХреНрд░реЛрдо ремрен.реж.реж.рейрейрепрем.репреп
  • рд╡рд┐рдВрдбреЛрдЬ 10

рд╕рдмрд╕реЗ рдЙрдкрдпреЛрдЧреА рдЯрд┐рдкреНрдкрдгреА

рдореЗрд░реЗ рдкрд╛рд╕ рднреА рд╡рд╣реА рдореБрджреНрджреЗ рд╣реИрдВред
рдХреНрдпрд╛ рдЖрдкрдХреЛ рдХреЛрдИ рд╕рдорд╛рдзрд╛рди рдпрд╛ рд╕рдорд╛рдзрд╛рди рдорд┐рд▓рд╛?

рд╕рднреА 12 рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

рдореЗрд░реЗ рдкрд╛рд╕ рднреА рд╡рд╣реА рдореБрджреНрджреЗ рд╣реИрдВред
рдХреНрдпрд╛ рдЖрдкрдХреЛ рдХреЛрдИ рд╕рдорд╛рдзрд╛рди рдпрд╛ рд╕рдорд╛рдзрд╛рди рдорд┐рд▓рд╛?

рдореИрдВ рдлрд╝рд╛рдпрд░рдлрд╝реЙрдХреНрд╕ 61.0.2 рдореЗрдВ рд╡рд╣реА рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдФрд░ рдЕрдиреБрдорддрд┐ рджреЗрдиреЗ рдХреЗ рдмрд╛рд╡рдЬреВрдж рдореБрдЭреЗ рд╕реБрд░рдХреНрд╖рд╛ рддреНрд░реБрдЯрд┐ рдорд┐рд▓ рд░рд╣реА рд╣реИ

рдЬрд╣рд╛рдВ рддрдХ тАЛтАЛтАЛтАЛрдореБрдЭреЗ рдкрддрд╛ рд╣реИ рдХреЛрдИ рд╕рдорд╛рдзрд╛рди рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдореЗрд░реЗ рдкрд╛рд╕ рд╡рд░реНрдХрдЕрд░рд╛рдЙрдВрдб рд╣реИ: рдкреНрд░рддреНрдпреЗрдХ рдЫрд╡рд┐ рдХреЛ рдмреЗрд╕ 64 рдореЗрдВ рдмрджрд▓реЗрдВред рдЗрд╕ рддрд░рд╣, рдЖрдк рдЗрд╕реЗ рдХреИрдирд╡рд╛рд╕ рдореЗрдВ рдкреНрд░рд╕реНрддреБрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рднрд▓реЗ рд╣реА рдпрд╣ рдореВрд▓ рд░реВрдк рд╕реЗ рднрд┐рдиреНрди рдбреЛрдореЗрди рд╕реЗ рд╣реЛред

рдХреНрдпрд╛ рдЖрдкрдиреЗ рдЗрд╕реЗ рджреЗрдЦрд╛ рд╣реИ: CORS_enabled_image

рдпрджрд┐ рд╡рд┐рджреЗрд╢реА рд╕рд╛рдордЧреНрд░реА рдХрд╛ рд╕реНрд░реЛрдд HTML рд╣реИ рддрддреНрд╡, рдХреИрдирд╡рд╛рд╕ рдХреА рд╕рд╛рдордЧреНрд░реА рдХреЛ рдкреБрдирдГ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдирд╣реАрдВ рд╣реИред

рдЬреИрд╕реЗ рд╣реА рдЖрдк рдХрд┐рд╕реА рдХреИрдирд╡рд╛рд╕ рдореЗрдВ рдХреЛрдИ рдбреЗрдЯрд╛ рдЦреАрдВрдЪрддреЗ рд╣реИрдВ рдЬреЛ рдмрд┐рдирд╛ CORS рдЕрдиреБрдореЛрджрди рдХреЗ рдХрд┐рд╕реА рдЕрдиреНрдп рдореВрд▓ рд╕реЗ рд▓реЛрдб рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдХреИрдирд╡рд╛рд╕ рджрд╛рдЧрджрд╛рд░ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред

рдореИрдВ рдЗрд╕ рддрд░рд╣ рдХреЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВ:

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

рдирдорд╕реНрддреЗ, рд╣рдореЗрдВ рдПрдХ рд╣реА рд╕рдорд╕реНрдпрд╛ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рд╛ред рд╣рдордиреЗ @dorklord23 рд╕реБрдЭрд╛рд╡ рдХрд╛ рдкрд╛рд▓рди рдХрд┐рдпрд╛ рдХреНрдпреЛрдВрдХрд┐ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рдкреНрд░реЙрдХреНрд╕реА url рдерд╛ рдЬрд┐рд╕рдиреЗ рд░реВрдкрд╛рдВрддрд░рдг рдХрд┐рдпрд╛ рдерд╛ред

рдЕрдЧрд░ рдХрд┐рд╕реА рдХреЛ рдпрд╣ рдорджрджрдЧрд╛рд░ рд▓рдЧрд╛ рддреЛ рд╕рдорд╛рдзрд╛рди рдерд╛:

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

рдЬрд╣рд╛рдВ рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдп 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 };

рд╡реИрд╕реЗ рдпрд╣ рдЙрд╕ рд╕рд╣рд╛рдпрдХ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдкрд░реАрдХреНрд╖рдг рд╣реИрдВ (рд╣рдо рдкрд░реАрдХреНрд╖рдг рдФрд░ рдореЙрдХрдлрд╝реЗрдЪ рдкреИрдХреЗрдЬ рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдЬреЗрд╕реНрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ):

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

рд░реВрдмреА рдмреИрдХрдПрдВрдб рд╕рдорд╛рдкрди рдмрд┐рдВрджреБ:

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

рдореБрдЭреЗ рдЖрд╢рд╛ рд╣реИ рдХрд┐ рдХрд┐рд╕реА рдХреЛ рдпрд╣ рдорджрджрдЧрд╛рд░ рд▓рдЧреЗрдЧрд╛ред рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпрд╣ рдХрд┐рд╕реА рдХреЛ рдЙрддрдирд╛ рд╕рдордп рдирд┐рд╡реЗрд╢ рдирд╣реАрдВ рдХрд░рдиреЗ рдореЗрдВ рдорджрдж рдХрд░рддрд╛ рд╣реИ рдЬрд┐рддрдирд╛ рд╣рдордиреЗ рдЗрд╕реЗ рдареАрдХ рд╕реЗ рдареАрдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдерд╛ред

рдпрджрд┐ рдЖрдк рдХреЛрдИ рд╕реБрдзрд╛рд░ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рддреЛ рдХреГрдкрдпрд╛ рд╕реБрдЭрд╛рд╡ рджреЗрдВред
рд╕рд╛рджрд░ред

рдпрджрд┐ рдЖрдк рдиреАрдЪреЗ рдХреА рддрд░рд╣ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдХреНрдпрд╛ рд╣реЛрдЧрд╛?

const TempImage = window.Image

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

рдПрдХ рд╕рдорд╛рдзрд╛рди рдорд┐рд▓рд╛ рдФрд░ рдпрд╣ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ

  1. html2canvas рдХреЛ рдХреЙрд▓ рдХрд░рддреЗ рд╕рдордп, CORS true рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ
    html2canvas(selectorElement, {useCORS:true} )
    //рдХреБрдЫ рдХрд░реЛ
    });

  2. рд╕рд╣реА html2canvas.js рдлрд╝рд╛рдЗрд▓ред рдЯрд╛рдЗрдкреЛ рдХреА рдЧрд▓рддреА рд╣реИ
    рдЗрд╕рдореЗрдВ "рдЕрдирд╛рдо" рдХреЛ "рдмреЗрдирд╛рдореА" рдореЗрдВ рдмрджрд▓реЗрдВ рдпрджрд┐ рдмреНрд▓реЙрдХ
    рдЕрдЧрд░ (isInlineBase64Image(src) || тАЛтАЛuseCORS) {
    img.crossOrigin = 'рдмреЗрдирд╛рдореА';
    }

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

рджреЛрд╖ рд░рд┐рдкреЛрд░реНрдЯ:

рдзреНрдпрд╛рди рдореЗрдВ рдирд╣реАрдВ рдЖрдпрд╛ (рд╡рд╛рджреЗ рдореЗрдВ) DOMException: 'HTMLCanvasElement' рдкрд░ 'toDataURL' рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓: рджрд╛рдЧреА рдХреИрдирд╡рд╛рд╕ рдирд┐рд░реНрдпрд╛рдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

  • html2canvas рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд╕рд╛рде рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рдЧрдпрд╛:
  • рдХреНрд░реЛрдо ремрен.реж.реж.рейрейрепрем.репреп
  • рд╡рд┐рдВрдбреЛрдЬ 10

рдЖрдкрдХреЛ рдХреЗрд╡рд▓ рд╕рдВрдкрддреНрддрд┐ 'useCORS: true' рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреА, рдпрджрд┐ рдЖрдк 'allowTaint: true' рд╕рдВрдкрддреНрддрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ рддреЛ рдЖрдк рдЕрдкрдиреЗ рдХреИрдирд╡рд╛рд╕ рдХреЛ рджрд╛рдЧреА рдХреИрдирд╡рд╛рд╕ рдореЗрдВ рдмрджрд▓рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВ

рдЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ:

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

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛:

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

рд╣реИрд▓реЛ, html2canvas рдХреЗ рд▓рд┐рдП рдЕрдЪреНрдЫрд╛ рдХрд╛рдоред

рджреБрдЦ рдХреА рдмрд╛рдд рд╣реИ рдХрд┐ рдореБрдЭреЗ рдПрдХ рд╣реА рд╕рдорд╕реНрдпрд╛ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝ рд░рд╣рд╛ рд╣реИ, рдХреНрдпрд╛ рдХрд┐рд╕реА рдиреЗ рдЗрд╕реЗ рд╣рд▓ рдХрд┐рдпрд╛ рд╣реИ?
рдкрд╣рд▓реЗ рд╕реЗ рд╣реА @motarock рдФрд░

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

рдЕрдЧреНрд░рд┐рдо рдореЗрдВ рдзрдиреНрдпрд╡рд╛рдж

рдореЗрд░реЗ рдкрд╛рд╕ рднреА рд╡рд╣реА рдореБрджреНрджреЗ рд╣реИрдВред
рдХреЛрдИ рдЗрд╕ рдореБрджреНрджреЗ рдХреЛ рдареАрдХ рдХрд░ рд╕рдХрддрд╛ рд╣реИ?

:( рдПрдХ рд╣реА рдореБрджреНрджрд╛ред рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдиреЗрд╕реНрдЯреЗрдб рдПрд╕рд╡реАрдЬреА рдХреЗ рд╕рд╛рде рдПрдЪрдЯреАрдПрдордПрд▓ рд╣реИ рдФрд░ рдпрд╣ рдкреНрд░рд╕реНрддреБрдд рдирд╣реАрдВ рдХрд░реЗрдЧрд╛

рдкрд╣рд▓реЗ рд╕реЗ рд╣реА @motarock рдФрд░

рдореЗрд░реЗ рдкрд╛рд╕ рдпрд╣ рд╕рдорд╕реНрдпрд╛ рд╣реИ рдЬрдм рдореИрдВ рдПрд╕рдПрд╕рдПрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд╣реАрдВ рдХрд░рддрд╛, рдПрд╕рдПрд╕рдПрд▓ рдХреЗ рд╕рд╛рде рд╕рд╣реА рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ

рдХреНрдпрд╛ рдпрд╣ рдкреГрд╖реНрда рдЙрдкрдпреЛрдЧреА рдерд╛?
0 / 5 - 0 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

Juliazhong picture Juliazhong  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

bishwapoudel picture bishwapoudel  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

ABHIKSINGHH picture ABHIKSINGHH  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

AlphaDu picture AlphaDu  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

tjchambers32 picture tjchambers32  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ