0

I'm trying to have a grid as a background using ImageKonva and filling it with a pattern. The problem is that when I load the canvas, the ImageKonva looks completely black, and it shows up as soon as I add children elements.

import React from 'react'
import { Stage, Layer, Image as ImageKonva } from 'react-konva'

const Canvas = ({ children, width, height, onClick }) => {
  const image = new Image()

   // this hash is the image of a grid
  image.src =   'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIElEQVQoU2NkIAK8e/fuPyMR6hhGFeINJXDwgAhiwhIAOZAl5YYZd5AAAAAASUVORK5CYII='


  return (
    <Stage width={width} height={height}>
        <Layer>
          <ImageKonva
            preventDefault={false}
            width={width / 2}
            height={width / 2}
            fillPatternImage={image}
            onClick={onClick}
          />
          {children}
        </Layer>
    </Stage>
  )
}

I've created this codesandbox where you can see the issue: https://codesandbox.io/s/angry-river-kszh0?file=/src/App.js

Note: It's not always reproducible, (can't figure out what's the pattern). Sometimes it wouldn't happen, and I need to open chrome's console and refresh the page to make it happen.

You'll see the black box, and it will only show the grid after you click the button. The button only adds a child element.. from there, the image would always show. This problem only happens during the first load.

Matias
  • 527
  • 1
  • 4
  • 19
  • Image loading is always via a parallel thread in browsers so therefore always an async process. This is the number #1 coding issue with scripting images, measuring images & image handling in script in general. The image cannot be 'used' until the onload event fires. – Vanquished Wombat May 07 '20 at 08:24

1 Answers1

0

I believe I've figured it out, it seems I didn't consider that the image could be added asynchronously... so when I load the canvas, the image is not there yet.

So the solution is to only load ImageKanvas once the image is loaded using by setting an onload method to the image object

const Canvas = ({ children, width, height, onClick }) => {
  const [loadGrid, setLoadGrid] = useState(false);
  const image = new Image();

  // this hash is the image of a grid
  image.src =
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIElEQVQoU2NkIAK8e/fuPyMR6hhGFeINJXDwgAhiwhIAOZAl5YYZd5AAAAAASUVORK5CYII=";

  image.onload = () => {
    setLoadGrid(true);
  };

  return (
    <Stage width={width} height={height}>
      <Layer>
        {loadGrid && (
          <ImageKonva
            preventDefault={false}
            width={width / 2}
            height={width / 2}
            fillPatternImage={image}
            onClick={onClick}
          />
        )}
        {children}
      </Layer>
    </Stage>
  );
};

https://codesandbox.io/s/wild-wind-br2qo?file=/src/App.js

Matias
  • 527
  • 1
  • 4
  • 19
  • 2
    Also, it is better not to create new `Image` on every render. Try to use `use-image` hook for that task: https://github.com/konvajs/use-image – lavrton May 06 '20 at 19:08