"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} />
}