Search code examples
html5-canvasreact-hooksreasonreason-reactbs-webapi

Using HTML Canvas from ReasonML using React Hooks


I'm looking for a quick example on how to get started using the following technologies together:

To get me started a snippet that does the following would be great:

  • Manages a reference to the HTML5 Canvas element elegantly and correctly
  • Is a simple react component
  • Clears the canvas and draws something

I already have the basic ReasonML React project setup.


Solution

  • Here is a sample that shows one way to put everything together:

    
    // Helper type to pass canvas size
    type dimensions = {
      width: float,
      height: float,
    };
    
    // Actual drawing happens here, canvas context and size as parameters.
    let drawOnCanvas =
        (context: Webapi.Canvas.Canvas2d.t, dimensions: dimensions): unit => {
      open Webapi.Canvas.Canvas2d;
      clearRect(context, ~x=0., ~y=0., ~w=dimensions.width, ~h=dimensions.height);
    
      setFillStyle(context, String, "rgba(0,128,169,0.1)");
      fillRect(context, ~x=10.0, ~y=10.0, ~w=30.0, ~h=30.0);
    };
    
    // Extract canvas dimensions from canvas element
    let canvasDimensions = (canvasElement: Dom.element): dimensions =>
      Webapi.Canvas.CanvasElement.{
        width: float_of_int(width(canvasElement)),
        height: float_of_int(height(canvasElement)),
      };
    
    // An adapter to give nicer parameters to drawOnCanvas above
    let drawOnCanvasElement = (canvasElement: Dom.element): unit =>
      Webapi.Canvas.CanvasElement.(
        drawOnCanvas(
          getContext2d(canvasElement),
          canvasDimensions(canvasElement),
        )
      );
    
    [@react.component]
    let make = () => {
      open React;
      let canvasElementRef: Ref.t(option(Dom.element)) = useRef(None);
    
      useLayoutEffect0(() => {
        Ref.current(canvasElementRef)
        |> Belt.Option.map(_, drawOnCanvasElement)
        |> ignore;
        None;
      });
    
      <canvas
        width="200"
        height="100"
        ref={ReactDOMRe.Ref.callbackDomRef(elem =>
          React.Ref.setCurrent(canvasElementRef, Js.Nullable.toOption(elem))
        )}
      />;
    };
    

    Here are some random links I used when learning how to do this. (Adding them here in case they are useful for others too.):

    The code has a bit more type declarations than necessary and some open statements could be added, but I like my answers a bit on the verbose side for a bit more instructiveness.

    It should be relatively easy to shorten the code.

    The intermediate functions canvasDimensions and drawOnCanvasElement add a bit of structure to the code in my opinion, but I'm not sure if they make the sample more or less clear for readers or if there would be a more elegant way to work with the canvas size.