Zanam
Zanam

Reputation: 4807

Dynamically creating canvas height for image display

I am using the following code to dynamically create canvas height. But even though there is onLoad, console log for canvas height is 0.

index.js

import React, {useRef, useState, useEffect} from "react";
import ReactDOM from "react-dom";
import background from "./background.png";

const Canvas = (props) => {
  const canvas = useRef(null);
  const image = useRef(null);  
  const [xLoc, setxLoc] = useState()
  const [yLocTop, setyLocTop] = useState()
  const [yLocBottom, setyLocBottom] = useState()
  const [canX, setCanX] = useState()
  const [canY, setCanY] = useState()

  useEffect(() => {
    const ctx = canvas.current.getContext("2d");
    image.current.onload = () => {
      ctx.drawImage(image.current, 0, 0);            
      ctx.font = "20px Courier";
      ctx.textAlign = "center";  
      ctx.fillText(props.textTop, xLoc, yLocTop);
      ctx.textAlign = "center";
      ctx.fillText(props.textBottom, xLoc, yLocBottom);
    };
  });

  useEffect(() => {
    const ctx = canvas.current.getContext("2d");
    ctx.drawImage(image.current, 0, 0);    
    ctx.font = "20px Courier";
    ctx.textAlign = "center";
    ctx.fillText(props.textTop, xLoc, yLocTop);
    ctx.textAlign = "center";
    ctx.fillText(props.textBottom, xLoc, yLocBottom);
  });

  const handleOnLoad = e => {    
    console.log(e.target.offsetHeight)
    setCanX(e.target.offsetWidth)
    setCanY(e.target.offsetHeight)
    setxLoc(canX / 2);
    setyLocTop(canY * 0.87);
    setyLocBottom(canY * 0.13);
  };

  return (
    <div>
      {console.log(canX)}
      {/* <canvas ref={canvas} width={canX || 0} height={canY || 0} /> */}
      <canvas ref={canvas} width="270" height="80" />
      <img 
        ref={image} 
        src={props.background} 
        onLoad={handleOnLoad} 
        hidden/>
    </div>
  );
};


function App() {  
  return (
    <div className="App">      
      <Canvas textTop="Top" textBottom="Bottom" background={background} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

The sandbox link is here

Upvotes: 0

Views: 844

Answers (2)

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15688

The problem is that if you set <img hidden/> then the image dimensions would simply be 0 height, 0 width.

You can still hide the image without using the hidden prop. Use a style that sets visibility to hidden, the image dimensions will be passed correctly. If you want to push img behind the canvas so that it will never be clicked. Use the following:

  <img
    ref={image}
    src={props.background}
    onLoad={handleOnLoad}
    style={{ visibility: "hidden", position: "absolute", top: "0", zIndex: "-1" }}
    alt=""
  />

See sandbox: https://codesandbox.io/s/affectionate-wildflower-jov21

Additionally, you can workaround the synchronous behavior of your handleLoad() function. State-updating functions do not wait for other logic to complete before they execute. Before setCanX has time to complete, setxLoc is already running and in that case canX is still undefined. Which is why we get NaN when we try to divide undefined by 2. They do not wait like an asynchronous manner.

You should just pass in event.target.offsetStuff to the setter functions directly and it will work

 const handleOnLoad = e => {
    const { offsetHeight, offsetWidth } = e.target

    setCanX(offsetWidth);
    setCanY(offsetHeight);
    setxLoc(offsetWidth / 2);
    setyLocTop(offsetHeight * 0.87);
    setyLocBottom(offsetHeight * 0.13);
  };

Upvotes: 3

brandonwang
brandonwang

Reputation: 1653

It seems like there are two onload functions for the image, one in the useEffect() and one in the property of the image. You can actually combine them and it'll work well. Here's the sandbox link.

import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import background from "./background.png";

const Canvas = props => {
  const canvas = useRef(null);
  const image = useRef(null);
  const [xLoc, setxLoc] = useState();
  const [yLocTop, setyLocTop] = useState();
  const [yLocBottom, setyLocBottom] = useState();
  const [canX, setCanX] = useState();
  const [canY, setCanY] = useState();

  useEffect(() => {
    const ctx = canvas.current.getContext("2d");
    image.current.onload = () => {
      ctx.drawImage(image.current, 0, 0);
      ctx.font = "20px Courier";
      ctx.textAlign = "center";
      ctx.fillText(props.textTop, xLoc, yLocTop);
      ctx.textAlign = "center";
      ctx.fillText(props.textBottom, xLoc, yLocBottom);

      setCanX(image.current.width);
      setCanY(image.current.height);
      setxLoc(canX / 2);
      setyLocTop(canY * 0.87);
      setyLocBottom(canY * 0.13);
    };
  });

  useEffect(() => {
    const ctx = canvas.current.getContext("2d");
    ctx.drawImage(image.current, 0, 0);
    ctx.font = "20px Courier";
    ctx.textAlign = "center";
    ctx.fillText(props.textTop, xLoc, yLocTop);
    ctx.textAlign = "center";
    ctx.fillText(props.textBottom, xLoc, yLocBottom);
  });

  return (
    <div>
      <canvas ref={canvas} width={canX || 0} height={canY || 0} />
      {/* <canvas ref={canvas} width="270" height="80" /> */}
      <img ref={image} src={props.background} hidden />
    </div>
  );
};

function App() {
  return (
    <div className="App">
      <Canvas textTop="Top" textBottom="Bottom" background={background} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Upvotes: 0

Related Questions