REACT + HTML2Canvas not exporting embedded IMGs

Viewed 87

I am currently trying to use html2canvas in a REACT app that I am building to export a DIV that contains several images (loaded from my localhost server, same domain) stacked on top of each other and every time I try to save the component I get a blank image file.

My code below. You will notice I've built it out two ways. A div with nested divs. And a div with nested images. Both do not work. If I change the REF from the DIV with the nested images, to another DIV that only has text, it works like a charm. I've been beating my head over this for the past 2.5 days. Tried several ways to try to export it, and every single time, data is empty.

Chrome console showing element, canvas populated until image data is added In Chrome's console it shows that the Element (Line 20: const element = this.printRef.current;) and Canvas (Line 22: const canvas = await html2canvas(element);) are always populated with all the proper tags. But once the DATA (line 25: const data = canvas.toDataURL("image/jpg");) gets created it no longer has anything inside of it. This, is where I feel this breaks. Screen Shot 2022-07-08 at 3 01 41 PM

Any idea what I am doing wrong? Ideally I would love to be able to export it with a transparent BG and to be able to resize the image to a different length by width.

import React, { Component } from "react";
import html2canvas from "html2canvas";

class Breeder extends Component {
constructor(props) {
super(props);

this.state = {
  dad: this.props.dad ? this.props.dad : "1",
  mom: this.props.mom ? this.props.mom : "1",
  gender: this.getGender(),
  attributes: this.getAttributes(this.props.dad, this.props.mom),
};

this.printRef = React.createRef();
}

render() {
const handleDownloadImage = async () => {
const element = this.printRef.current;
console.log("Element is: ", element);
const canvas = await html2canvas(element);
console.log("Canvas is: ", element);

  const data = canvas.toDataURL("image/jpg");
  const link = document.createElement("a");

  console.log("1 Data: ", data);

  if (typeof link.download === "string") {
    console.log("Link: ", link);
    link.href = data;
    link.download = "image.jpg";

    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  } else {
    console.log("Data: ", data);
    window.open(data);
  }
};

const renderAttributes = this.state.attributes.map((attribute, i) => {
  if (attribute[1] != "*" && attribute[1].toLowerCase() != "none")
    return (
      <img
        src={`./images/traits/${attribute[2]}/${
          attribute[0]
        }/${attribute[1].replace(" ", "-")}.png`}
        key={i}
        style={{
          position: "absolute",
          zIndex: 1 * i,
          top: "0px",
          width: "200px",
          height: "215px",
          border: "solid thin black",
        }}
      ></img>
      //   <div
      //     key={i}
      //     style={{
      //       position: "absolute",
      //       zIndex: 1 * i,
      //       top: "0px",
      //       width: "200px",
      //       height: "215px",
      //       border: "solid thin black",
      //       backgroundImage: `url("./images/traits/${attribute[2]}/${
      //         attribute[0]
      //       }/${attribute[1].replace(" ", "-")}.png")`,
      //       backgroundPosition: "center center",
      //       backgroundRepeat: "no-repeat",
      //       backgroundSize: "contain",
      //     }}
      //   ></div>
    );
});

return (
  <div className="col">
    <button className="btn-primary rounded" onClick={handleDownloadImage}>
      Export As PNG
    </button>
    <div className="">{this.state.gender === "m" ? "male" : "female"}</div>
    <div className="">
      {this.state.attributes.map((txt, i) => (
        <p key={i}>
          {txt[0]} {txt[1]} {txt[2]}
        </p>
      ))}
    </div>
    <div className="" style={{ position: "relative" }} ref={this.printRef}>
      {renderAttributes}
    </div>
  </div>
);
}

getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive
}

getGender() {
const value = this.getRandomIntInclusive(1, 10);
if (value < 6) return "m";
else return "f";
}

getAttributes(dad, mom) {
dad = require(./kishu/male-json/${dad}.json);
mom = require(./kishu/female-json/${mom}.json);

/**
 * Generate a random number between 1 - 100.
 *  1 - 47 = dad's trait
 * 48 - 95 = mom's trait
 * 96 -100 = *  means select a new trait (mutation)
 *
 */
var attributes = [];
var randNum = 0;

//Cycle through attributes and store
for (var pos = 0; pos < 7; pos++) {
  randNum = this.getRandomIntInclusive(1, 100);
  if (randNum < 48) {
    attributes.push([
      dad["attributes"][pos]["trait_type"],
      dad["attributes"][pos]["value"],
      "m",
    ]);
  } else if (randNum < 95) {
    attributes.push([
      mom["attributes"][pos]["trait_type"],
      mom["attributes"][pos]["value"],
      "f",
    ]);
  } else {
    //look up all artwork assets and set a new one
    attributes.push([mom["attributes"][pos]["trait_type"], "*", "*"]);
  }
}
return attributes;
}
}

export default Breeder;    
1 Answers

Solved it! For others who run into the same problem as me... you need to explicitly set the HEIGHT and WIDTH of the DIV containing the elements you want to export.

Original Code:

<div className="" style={{ position: "relative" }} ref={this.printRef}>
  {renderAttributes}
</div>

Revised Code:

<div className="" style={{ position: "relative", width: "200px", height: "215px" }} ref={this.printRef}>
    {renderAttributes}
</div>
Related