Maze Pattern

A maze-themed background with customizable colors and size.

Find Your Way

maze-pattern-background-preview.jsx

import MazePatternBackground from "@/components/backgrounds/maze-pattern-background/maze-pattern-background";

const MazePatternBackgroundPreview = () => {
  return (
    <MazePatternBackground
      mazeSize={10}
      mazeColor="rgba(162,201,229,0.8)"
      style={{
        width: "100%",
        height: "100%"
      }}
      wrapperProps={{
        style: {
          height: "100%",
          display: "grid",
          placeItems: "center",
        }
      }}
    >
      <h1>Find Your Way</h1>
    </MazePatternBackground>
  );
};

export default MazePatternBackgroundPreview;

Installation

Run the command from your project root directory (the folder that contains package.json).

Terminal / Console

npx mosaicui-cli@latest backgrounds/maze-pattern-background

1. Copy the component file

Create a new file called maze-pattern-background.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

maze-pattern-background.jsx

import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import styles from "./maze-pattern-background.module.css";

const MazePatternBackground = (props) => {
  const {
    backgroundColor = "rgba(0,0,0,0)",
    mazeColor = "rgba(127,127,127,0.5)",
    mazeSize = 20,
    children,
    className,
    wrapperProps = {},
    wrapperTagName = "div",
    ...rest
  } = props;

  const {
    className: wrapperClassName = "",
    ...restWrapperProps
  } = wrapperProps;

  const _mazeSize = Math.max(1, mazeSize);

  const Wrapper = wrapperTagName || "div";

  const containerRef = useRef(null);
  const canvasRef = useRef(null);

  const [mounted, setMounted] = useState(false);
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);

  const { devicePixelRatio, canvasWidth, canvasHeight } = useMemo(() => {
    const devicePixelRatio = Math.max(1, globalThis.devicePixelRatio || 1);
    return {
      devicePixelRatio,
      canvasWidth: width * devicePixelRatio,
      canvasHeight: height * devicePixelRatio,
    };
  }, [width, height]);

  const ctx = useMemo(() => {
    return canvasRef.current?.getContext("2d");
  }, [canvasRef.current]);

  const render = useCallback(() => {
    ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);
    for (let y = 0; y < height; y += _mazeSize) {
      for (let x = 0; x < width; x += _mazeSize) {
        ctx.beginPath();
        if (Math.random() < 0.5) {
          ctx.moveTo(x, y);
          ctx.lineTo(x + _mazeSize, y + _mazeSize);
        } else {
          ctx.moveTo(x + _mazeSize, y);
          ctx.lineTo(x, y + _mazeSize);
        }
        ctx.strokeStyle = mazeColor;
        ctx.lineWidth = 1;
        ctx.stroke();
        ctx.closePath();
      }
    }
  }, [
    ctx,
    devicePixelRatio,
    backgroundColor,
    mazeColor,
    width,
    height,
    canvasWidth,
    canvasHeight,
    _mazeSize,
  ]);

  useEffect(() => {
    const updateContainerDimensions = () => {
      const {
        width,
        height,
      } = containerRef.current.getBoundingClientRect();
      setWidth(width);
      setHeight(height);
    };
    const resizeObserver = new ResizeObserver(updateContainerDimensions);
    resizeObserver.observe(containerRef.current);
    updateContainerDimensions();
    setMounted(true);
    return () => {
      resizeObserver.disconnect();
    };
  }, []);

  useLayoutEffect(() => {
    if (!mounted) return;
    render();
  }, [mounted, render]);

  return (
    <div
      {...rest}
      ref={containerRef}
      className={[
        className,
        styles["maze-pattern-background"]
      ].join(" ")}
    >
      <canvas
        aria-hidden={true}
        width={canvasWidth}
        height={canvasHeight}
        ref={canvasRef}
      />
      <Wrapper
        {...restWrapperProps}
        className={[
          wrapperClassName,
          styles["wrapper"]
        ].join(" ")}
      >
        {children}
      </Wrapper>
    </div>
  )
};

export default memo(MazePatternBackground);

2. Copy the CSS module file

In the same folder, create a file called maze-pattern-background.module.css and paste the following CSS.

maze-pattern-background.module.css

.maze-pattern-background {
  position: relative;

  > canvas {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
  }

  > .wrapper {
    position: relative;
    z-index: 2;
  }
}

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
backgroundColorstringNo"rgba(0,0,0,0)"Background color behind the maze.
mazeColorstringNo"rgba(127,127,127,0.5)"Color of the maze lines.
mazeSizenumberNo20Size of each maze cell in pixels.
childrenReactNodeYesContent rendered on top of the maze background.
classNamestringNoAdditional CSS classes applied to the main container.
wrapperPropsReact.HTMLAttributes<any>NoExtra props passed to the wrapper element containing the children.
wrapperTagNamestringNo"div"HTML tag used as the wrapper element around the children.