DEV Community

Cover image for GLSL Canvas Component for React, Hooks & Typescript
samasastudio
samasastudio

Posted on

GLSL Canvas Component for React, Hooks & Typescript

Hey Devs,

Recently I've been spending some creative time in KodeLife and designing a small library of GLSL shaders that I like to have on hand for projects. When finally rendering these shaders on the web, I can't recommend glslCanvas 👨🏻‍🎨 enough.

Canvas

There are a few common canvas-related bugs that you may run into when dealing with CSS sizing, resolution and adaptive resizing though. Usually this comes in the form of pixelated shaders, poor u_mouse movement mapping, and the shader just rendering overall at the wrong size. 👾 So! I took the time experiment with the glslCanvas rendering lifecycle and React's useRef to make a handy functional component that skips you right past these bugs and directly to an easy-to-use canvas the fills whatever container you want to wrap it in. 🍱

Resizing helper function

To start, here's a simple helper function that will save you some headache when adaptively sizing the canvas:

  const resizer = (
    canvas: HTMLCanvasElement,
    container: HTMLDivElement
  ): void => {
    canvas.width = container.clientWidth + window.devicePixelRatio;
    canvas.height = container.clientHeight + window.devicePixelRatio;
    canvas.style.width = container.clientWidth + "px";
    canvas.style.height = container.clientHeight + "px";
  };
Enter fullscreen mode Exit fullscreen mode

GLSL Freebie

Also if you haven't worked with GLSL before and want to try out the component, here's a a solid one that works right out of the box without any images. ✨ Just pass in 'frag' as your prop for frag in the component.

Alt Text

The Component

Last but not least here is the entire component. It contains the helper function for sizing the canvas element, as well as a loop that helps set each uniform you want to pass in through the setUniform prop. 🥋 For example, if you wanted to set a uniform called u_image with the value of an image variable, then you could pass in {u_image: image}. Also pay close attention to the sequence that glslCanvas instantiates the canvas, sizes it, and then loads the frag. This is important to the shader connecting its resolution to the canvas size. 🌱

import { FC, useEffect, useRef } from "react";
import GlslCanvas from "glslCanvas";

interface ShaderCanvasProps {
  frag: string;
  setUniforms?: { [key: string]: string };
}

export const ShaderCanvas: FC<ShaderCanvasProps> = (props): JSX.Element => {

  const canvasRef = useRef<HTMLCanvasElement>();
  const containerRef = useRef<HTMLDivElement>();

  const resizer = (
    canvas: HTMLCanvasElement,
    container: HTMLDivElement
  ): void => {
    canvas.width = container.clientWidth + window.devicePixelRatio;
    canvas.height = container.clientHeight + window.devicePixelRatio;
    canvas.style.width = container.clientWidth + "px";
    canvas.style.height = container.clientHeight + "px";
  };

  useEffect(() => {
    const node = canvasRef.current;
    const container = containerRef.current;
    const sandbox = new GlslCanvas(canvasRef.current);
    for (let k in props.setUniforms) {
      sandbox.setUniform(k, props.setUniforms[k]);
    }

    resizer(node, container);
    sandbox.load(props.frag);

    const handler = () => {
      if (
        node.clientWidth !== container.clientWidth ||
        node.clientHeight !== container.clientHeight
      )
        resizer(canvasRef.current, containerRef.current);
    };

    window.addEventListener("resize", handler);

    return () => {
      window.removeEventListener("resize", handler);
    };
  }, []);

  return (
    <div ref={containerRef} style={{ width: "100%", height: "100%" }}>
      <canvas ref={canvasRef}></canvas>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

If you want to see this code alive in the wild, start here and please respond with any ideas for refactoring or optimizing! 🛠

Top comments (0)