Search code examples
javascriptreactjshtml2canvas

Rendering a canvas object received from props


Good day!

I am new to React and html2canvas. I am making an app which will take "screenshots" of my DOM using html2canvas then store it to an array of screenshots which will then be also rendered on the screen.

I am storing each <canvas> object received from the html2canvas promise to an array then pass it to my ScreenshotsContainer component which passes the array to the Screenshots component. The Screenshots component will then map the array of <canvas> objects to individual Screenshot components.

In App.js, I am calling the html2canvas function then pass the array to ScreenshotsContainer component

import React, { Component } from 'react';
import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer'
import html2canvas from 'html2canvas';

import './App.css';

class App extends Component {
  state = {
    canvasArray: []
  }

  getScreenshotHandler = () => {
    console.log("[Canvas Array from state length:]" + this.state.canvasArray.length)
    let canvasArray = this.state.canvasArray;

    html2canvas(document.body).then((canvas) => {
      canvasArray.push(canvas)
    });

    console.log("[Canvas Object value: ]" + canvasArray);
    this.setState({ canvasArray: canvasArray })

  }

  render() {

    return (
      <React.Fragment>
        <button onClick={this.getScreenshotHandler}>Get Screenshot</button>
        <ScreenshotsContainer canvasArray={this.state.canvasArray} />
      </React.Fragment>
    );
  }
}

export default App;

The ScreenshotsContainer component will pass the received array to the Screenshots component:

    import React, { Component } from 'react';
import './ScreenshotsContainer.css'
import Screenshots from '../../components/Screenshots/Screenshots';

class ScreenshotsContainer extends Component {

    render() {
        return (
            <div className="ScreenshotsContainer">
                <Screenshots canvasArray={this.props.canvasArray} />
            </div>
        );
    }
}
export default ScreenshotsContainer;

The Screenshots component will map the array and pass each canvas object to the Screenshot component:

import React, { Component } from 'react';
import Screenshot from './Screenshot/Screenshot';

class Screenshots extends Component {
    render() {
        const screenshots = this.props.canvasArray.map(canvas => {
            return (
                <Screenshot
                    key={Math.random}
                    canvasObj={canvas}
                />
            )
        })
        return (
            <React.Fragment>
                {screenshots}
            </React.Fragment>
        );
    }
}
export default Screenshots;

Here is the Screenshot component

import React from 'react';
import './Screenshot.css';

const screenshot = (props) => (
    <div className="Screenshot" >
        <canvas ref={props.canvasObj} style={{
            width: '10%',
            height: '10%'
        }} />
    </div>
);

export default screenshot;

What I actually get when pressing the button:

Actual screenshot of my result I was wondering which part went wrong. Any help would be appreciated.


Solution

  • This particular library works in a specific way (looks like it's doing a lot of "magic" under the hood - you should look at the source code here more specifically the renderer folder inside src)

    Saving the canvas to the state inside of an array (the correct react way of doing things) will be a problem as it saves it as a complex object with many methods etc... and we can not render objects... This lib was not written with React in mind...

    The code sample below is a simple implementation in React...

    Here is a live demo: https://codesandbox.io/s/9y24vwn1py

    import React, { Component } from 'react';
    import html2canvas from 'html2canvas';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.captureRef = React.createRef();
        this.displayRef = React.createRef();
      }
    
      getScreenshotHandler = () => {
        html2canvas(this.captureRef.current).then(canvas =>
          this.displayRef.current.appendChild(canvas),
        );
      };
    
      render() {
        return (
          <div>
            <div ref={this.captureRef}>
              <h2>This enitre div will be captured and added to the screen</h2>
            </div>
            <button onClick={this.getScreenshotHandler}>Get Screenshot!</button>
            <section>
              <h5>Your screenshots will be availbale below</h5>
              <div ref={this.displayRef} />
            </section>
          </div>
        );
      }
    }
    
    export default App;
    

    EDIT: based on the comment below here is yet another workaround:

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = { canvasArray: [] };
        this.captureRef = React.createRef();
      }
    
      getScreenshotHandler = () => {
        html2canvas(this.captureRef.current).then(canvas =>
          this.setState({
            canvasArray: [canvas.toDataURL(), ...this.state.canvasArray],
          }),
        );
      };
    
      renderCanvas = () => {
        return this.state.canvasArray.map((canvas, i) => {
          return <img key={i} src={canvas} alt="screenshot" />;
        });
      };
    
      render() {
        return (
          <div className="wrapper">
            <div ref={this.captureRef}>
              <p>This enitre div will be captured</p>
            </div>
            <button onClick={this.getScreenshotHandler}>Get Screenshot!</button>
            <section>
              <h5>Your screenshots will be availbale below:</h5>
              {this.renderCanvas()}
            </section>
          </div>
        );
      }
    }
    

    Link to live demo: https://codesandbox.io/s/1r213057vq