novatrix-background

PreviousNext

A novatrix background component.

Docs
eldorauiui

Preview

Loading preview…
registry/eldoraui/novatrix-background.tsx
"use client"

import { useEffect, useRef } from "react"
import { Color, Mesh, Program, Renderer, Triangle } from "ogl"

const vertexShader = `
attribute vec2 uv;
attribute vec2 position;

varying vec2 vUv;

void main() {
  vUv = uv;
  gl_Position = vec4(position, 0, 1);
}
`

const fragmentShader = `
precision highp float;

uniform float uTime;
uniform vec3 uColor;
uniform vec3 uResolution;
uniform vec2 uMouse;
uniform float uAmplitude;
uniform float uSpeed;

varying vec2 vUv;

void main() {
  float mr = min(uResolution.x, uResolution.y);
  vec2 uv = (vUv.xy * 2.0 - 1.0) * uResolution.xy / mr;

  uv += (uMouse - vec2(0.5)) * uAmplitude;

  float d = -uTime * 0.5 * uSpeed;
  float a = 0.0;
  for (float i = 0.0; i < 8.0; ++i) {
    a += cos(i - d - a * uv.x);
    d += sin(uv.y * i + a);
  }
  d += uTime * 0.5 * uSpeed;
  vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5);
  col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5) * uColor;
  gl_FragColor = vec4(col, 1.0);
}
`

interface NovatrixProps {
  color?: [number, number, number]
  speed?: number
  amplitude?: number
  mouseReact?: boolean
}

export default function Novatrix({
  color = [1, 1, 1],
  speed = 1.0,
  amplitude = 0.1,
  mouseReact = false,
  ...rest
}: NovatrixProps) {
  const ctnDom = useRef<HTMLDivElement>(null)
  const mousePos = useRef({ x: 0.5, y: 0.5 })

  useEffect(() => {
    if (!ctnDom.current) return
    const ctn = ctnDom.current
    const renderer = new Renderer()
    const gl = renderer.gl
    gl.clearColor(1, 1, 1, 1)

    // eslint-disable-next-line prefer-const
    let program: Program

    function resize() {
      const scale = 1
      renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale)
      if (program) {
        program.uniforms.uResolution.value = new Color(
          gl.canvas.width,
          gl.canvas.height,
          gl.canvas.width / gl.canvas.height
        )
      }
    }
    window.addEventListener("resize", resize, false)
    resize()

    const geometry = new Triangle(gl)
    program = new Program(gl, {
      vertex: vertexShader,
      fragment: fragmentShader,
      uniforms: {
        uTime: { value: 0 },
        uColor: { value: new Color(...color) },
        uResolution: {
          value: new Color(
            gl.canvas.width,
            gl.canvas.height,
            gl.canvas.width / gl.canvas.height
          ),
        },
        uMouse: {
          value: new Float32Array([mousePos.current.x, mousePos.current.y]),
        },
        uAmplitude: { value: amplitude },
        uSpeed: { value: speed },
      },
    })

    const mesh = new Mesh(gl, { geometry, program })
    let animateId: number

    function update(t: number) {
      animateId = requestAnimationFrame(update)
      program.uniforms.uTime.value = t * 0.001
      renderer.render({ scene: mesh })
    }
    animateId = requestAnimationFrame(update)
    ctn.appendChild(gl.canvas)

    function handleMouseMove(e: MouseEvent) {
      const rect = ctn.getBoundingClientRect()
      const x = (e.clientX - rect.left) / rect.width
      const y = 1.0 - (e.clientY - rect.top) / rect.height
      mousePos.current = { x, y }
      program.uniforms.uMouse.value[0] = x
      program.uniforms.uMouse.value[1] = y
    }
    if (mouseReact) {
      ctn.addEventListener("mousemove", handleMouseMove)
    }

    return () => {
      cancelAnimationFrame(animateId)
      window.removeEventListener("resize", resize)
      if (mouseReact) {
        ctn.removeEventListener("mousemove", handleMouseMove)
      }
      ctn.removeChild(gl.canvas)
      gl.getExtension("WEBGL_lose_context")?.loseContext()
    }
  }, [color, speed, amplitude, mouseReact])

  return <div ref={ctnDom} className="h-full w-full" {...rest} />
}

Installation

npx shadcn@latest add @eldoraui/novatrix-background

Usage

import { NovatrixBackground } from "@/components/ui/novatrix-background"
<NovatrixBackground />