1

My use case is: I have a web site editor and a list of available web pages for users. Each page in this list is represented by a thumbnail. Every time a user makes a change to a page using the editor, the thumbnail of the respective site has to be updated to reflect the change. The way I'm doing is by mounting a ThumbnailSandbox component in the page, passing the props from the Redux store and then using dom-to-png to create the screenshot and use it in the list. But I wanted to do it without mounting the component on the page, because I think it would be a cleaner solution and with less chances of being affected by other interactions happening. So, I created a CodeSanbox to illustrate what I'm trying to achieve.

My logic is this:

import React from "react";
import ReactDOMServer from "react-dom/server";
import html2canvas from "html2canvas";
import MyComp from "./component.jsx";

export const createScrenshot = () => {
  const el = (
    <div>
      test component <MyComp />
    </div>
  );

  const markup = ReactDOMServer.renderToString(el);
  let doc = new DOMParser().parseFromString(markup, "text/html");
  let target = doc.body.getElementsByClassName("my-comp")[0];

  console.log(markup, target);

  html2canvas(target, {
    useCORS: true,
    allowTaint: true,
    scale: 1,
    width: 500,
    height: 500,
    x: 0,
    y: 0,
    logging: true,
    windowWidth: 500,
    windowHeight: 500
  })
    .then(function(canvas) {
      console.log(">> ", canvas);
    })
    .catch(error => {
      console.log(error);
    });
};

So, I'm passing the component to ReactDOM, then creating a DOM node using the string from first step and passing the node to html2canvas. But at this point I get the error Uncaught TypeError: Cannot read property 'pageXOffset' of null. Because the ownerDocument of the element passed to html2canvas is null and it doesn't have the properties: devicePixelRation, innerWidth, innerHeight, pageYOffset, and pageXOffset. As I understand, that's because the node element is not part of the DOM.

Now, my questions are:

1) Is there a way to solve this problem using html2canvas?

2) Is there any other way to take a screenshot of a React component, in the browser, without mounting the component in the DOM?

Thank you in advance!!

Rafael Rozon
  • 2,639
  • 1
  • 15
  • 27

2 Answers2

0

For point 1:

Why don't you mount the component and then after your handling delete the component in the ref? (can be done in ComponentDidMount too but ref would come before DidMount) That's the most standard solution to perform downloads (create an a tag do a click and then remove it)

This is a sample untested code using ref call back

export class CreateScrenshot extends React.Component {
    constructor() {
        super() {
            this._reactRef = this._reactRef.bind(this);
            this.state = {
                removeNode: false
            };
        }
    }

    _reactRef(node) {
        if(node) {
            // your html2Canvas handling and in the returned promise remove the node by
            this.setState({removeNode: true});
        }
    }

    render() {
        let childComponent = null;
        if(!this.state.removeNode) {
            {/*pass the ref of the child node to the parent component using the ref callback*/}
            childComponent =  (
                <div>
                    test component <MyComp refCallBack={this._reactRef}/>
                </div>
            );
        }
        return childComponent;
    }
}

the limitation however is that this will be asynchronous and might cause a flicker. So if possible try using a sync library so that the node can be removed on the next render.

For point 2: https://reactjs.org/docs/react-component.html#componentdidmount

From react's componentDidMount() doc section: "It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position."

This makes it clear that you can only get the nodes' measurements after it has been mounted.

Saurav Seth
  • 301
  • 3
  • 9
0

Set the react element to z-index and bottom -9999px