305 lines
8.0 KiB
TypeScript
Raw Normal View History

2024-10-21 13:57:45 -06:00
"use client"
import { cn } from "@/lib/utils"
import { Canvas, useFrame, useThree } from "@react-three/fiber"
import React, { useMemo, useRef } from "react"
import * as THREE from "three"
2024-05-16 10:47:34 -07:00
export const CanvasRevealEffect = ({
animationSpeed = 0.4,
opacities = [0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.8, 0.8, 0.8, 1],
colors = [[0, 255, 255]],
containerClassName,
dotSize,
showGradient = true,
}: {
2024-10-21 13:57:45 -06:00
animationSpeed?: number
opacities?: number[]
colors?: number[][]
containerClassName?: string
dotSize?: number
showGradient?: boolean
2024-05-16 10:47:34 -07:00
}) => {
return (
<div className={cn("h-full relative bg-white w-full", containerClassName)}>
<div className="h-full w-full">
<DotMatrix
colors={colors ?? [[0, 255, 255]]}
dotSize={dotSize ?? 3}
opacities={
opacities ?? [0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.8, 0.8, 0.8, 1]
}
shader={`
float animation_speed_factor = ${animationSpeed.toFixed(1)};
float intro_offset = distance(u_resolution / 2.0 / u_total_size, st2) * 0.01 + (random(st2) * 0.15);
opacity *= step(intro_offset, u_time * animation_speed_factor);
opacity *= clamp((1.0 - step(intro_offset + 0.1, u_time * animation_speed_factor)) * 1.25, 1.0, 1.25);
`}
center={["x", "y"]}
/>
</div>
{showGradient && (
2024-05-17 23:54:34 -07:00
<div className="absolute inset-0 bg-gradient-to-t from-background to-[100%]" />
2024-05-16 10:47:34 -07:00
)}
</div>
2024-10-21 13:57:45 -06:00
)
}
2024-05-16 10:47:34 -07:00
interface DotMatrixProps {
2024-10-21 13:57:45 -06:00
colors?: number[][]
opacities?: number[]
totalSize?: number
dotSize?: number
shader?: string
center?: ("x" | "y")[]
2024-05-16 10:47:34 -07:00
}
const DotMatrix: React.FC<DotMatrixProps> = ({
colors = [[0, 0, 0]],
opacities = [0.04, 0.04, 0.04, 0.04, 0.04, 0.08, 0.08, 0.08, 0.08, 0.14],
totalSize = 4,
dotSize = 2,
shader = "",
center = ["x", "y"],
}) => {
const uniforms = React.useMemo(() => {
let colorsArray = [
colors[0],
colors[0],
colors[0],
colors[0],
colors[0],
colors[0],
2024-10-21 13:57:45 -06:00
]
2024-05-16 10:47:34 -07:00
if (colors.length === 2) {
colorsArray = [
colors[0],
colors[0],
colors[0],
colors[1],
colors[1],
colors[1],
2024-10-21 13:57:45 -06:00
]
2024-05-16 10:47:34 -07:00
} else if (colors.length === 3) {
colorsArray = [
colors[0],
colors[0],
colors[1],
colors[1],
colors[2],
colors[2],
2024-10-21 13:57:45 -06:00
]
2024-05-16 10:47:34 -07:00
}
return {
u_colors: {
value: colorsArray.map((color) => [
color[0] / 255,
color[1] / 255,
color[2] / 255,
]),
type: "uniform3fv",
},
u_opacities: {
value: opacities,
type: "uniform1fv",
},
u_total_size: {
value: totalSize,
type: "uniform1f",
},
u_dot_size: {
value: dotSize,
type: "uniform1f",
},
2024-10-21 13:57:45 -06:00
}
}, [colors, opacities, totalSize, dotSize])
2024-05-16 10:47:34 -07:00
return (
<Shader
source={`
precision mediump float;
in vec2 fragCoord;
uniform float u_time;
uniform float u_opacities[10];
uniform vec3 u_colors[6];
uniform float u_total_size;
uniform float u_dot_size;
uniform vec2 u_resolution;
out vec4 fragColor;
float PHI = 1.61803398874989484820459;
float random(vec2 xy) {
return fract(tan(distance(xy * PHI, xy) * 0.5) * xy.x);
}
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
void main() {
vec2 st = fragCoord.xy;
${
center.includes("x")
? "st.x -= abs(floor((mod(u_resolution.x, u_total_size) - u_dot_size) * 0.5));"
: ""
}
${
center.includes("y")
? "st.y -= abs(floor((mod(u_resolution.y, u_total_size) - u_dot_size) * 0.5));"
: ""
}
float opacity = step(0.0, st.x);
opacity *= step(0.0, st.y);
vec2 st2 = vec2(int(st.x / u_total_size), int(st.y / u_total_size));
float frequency = 5.0;
float show_offset = random(st2);
float rand = random(st2 * floor((u_time / frequency) + show_offset + frequency) + 1.0);
opacity *= u_opacities[int(rand * 10.0)];
opacity *= 1.0 - step(u_dot_size / u_total_size, fract(st.x / u_total_size));
opacity *= 1.0 - step(u_dot_size / u_total_size, fract(st.y / u_total_size));
vec3 color = u_colors[int(show_offset * 6.0)];
${shader}
fragColor = vec4(color, opacity);
fragColor.rgb *= fragColor.a;
}`}
uniforms={uniforms}
maxFps={60}
/>
2024-10-21 13:57:45 -06:00
)
}
2024-05-16 10:47:34 -07:00
type Uniforms = {
[key: string]: {
2024-10-21 13:57:45 -06:00
value: number[] | number[][] | number
type: string
}
}
2024-05-16 10:47:34 -07:00
const ShaderMaterial = ({
source,
uniforms,
maxFps = 60,
}: {
2024-10-21 13:57:45 -06:00
source: string
hovered?: boolean
maxFps?: number
uniforms: Uniforms
2024-05-16 10:47:34 -07:00
}) => {
2024-10-21 13:57:45 -06:00
const { size } = useThree()
const ref = useRef<THREE.Mesh>()
let lastFrameTime = 0
2024-05-16 10:47:34 -07:00
useFrame(({ clock }) => {
2024-10-21 13:57:45 -06:00
if (!ref.current) return
const timestamp = clock.getElapsedTime()
2024-05-16 10:47:34 -07:00
if (timestamp - lastFrameTime < 1 / maxFps) {
2024-10-21 13:57:45 -06:00
return
2024-05-16 10:47:34 -07:00
}
2024-10-21 13:57:45 -06:00
lastFrameTime = timestamp
2024-05-16 10:47:34 -07:00
2024-10-21 13:57:45 -06:00
const material: any = ref.current.material
const timeLocation = material.uniforms.u_time
timeLocation.value = timestamp
})
2024-05-16 10:47:34 -07:00
const getUniforms = () => {
2024-10-21 13:57:45 -06:00
const preparedUniforms: any = {}
2024-05-16 10:47:34 -07:00
for (const uniformName in uniforms) {
2024-10-21 13:57:45 -06:00
const uniform: any = uniforms[uniformName]
2024-05-16 10:47:34 -07:00
switch (uniform.type) {
case "uniform1f":
2024-10-21 13:57:45 -06:00
preparedUniforms[uniformName] = { value: uniform.value, type: "1f" }
break
2024-05-16 10:47:34 -07:00
case "uniform3f":
preparedUniforms[uniformName] = {
value: new THREE.Vector3().fromArray(uniform.value),
type: "3f",
2024-10-21 13:57:45 -06:00
}
break
2024-05-16 10:47:34 -07:00
case "uniform1fv":
2024-10-21 13:57:45 -06:00
preparedUniforms[uniformName] = { value: uniform.value, type: "1fv" }
break
2024-05-16 10:47:34 -07:00
case "uniform3fv":
preparedUniforms[uniformName] = {
value: uniform.value.map((v: number[]) =>
new THREE.Vector3().fromArray(v)
),
type: "3fv",
2024-10-21 13:57:45 -06:00
}
break
2024-05-16 10:47:34 -07:00
case "uniform2f":
preparedUniforms[uniformName] = {
value: new THREE.Vector2().fromArray(uniform.value),
type: "2f",
2024-10-21 13:57:45 -06:00
}
break
2024-05-16 10:47:34 -07:00
default:
2024-10-21 13:57:45 -06:00
console.error(`Invalid uniform type for '${uniformName}'.`)
break
2024-05-16 10:47:34 -07:00
}
}
2024-10-21 13:57:45 -06:00
preparedUniforms["u_time"] = { value: 0, type: "1f" }
2024-05-16 10:47:34 -07:00
preparedUniforms["u_resolution"] = {
value: new THREE.Vector2(size.width * 2, size.height * 2),
2024-10-21 13:57:45 -06:00
} // Initialize u_resolution
return preparedUniforms
}
2024-05-16 10:47:34 -07:00
// Shader material
const material = useMemo(() => {
const materialObject = new THREE.ShaderMaterial({
vertexShader: `
precision mediump float;
in vec2 coordinates;
uniform vec2 u_resolution;
out vec2 fragCoord;
void main(){
float x = position.x;
float y = position.y;
gl_Position = vec4(x, y, 0.0, 1.0);
fragCoord = (position.xy + vec2(1.0)) * 0.5 * u_resolution;
fragCoord.y = u_resolution.y - fragCoord.y;
}
`,
fragmentShader: source,
uniforms: getUniforms(),
glslVersion: THREE.GLSL3,
blending: THREE.CustomBlending,
blendSrc: THREE.SrcAlphaFactor,
blendDst: THREE.OneFactor,
2024-10-21 13:57:45 -06:00
})
2024-05-16 10:47:34 -07:00
2024-10-21 13:57:45 -06:00
return materialObject
}, [size.width, size.height, source])
2024-05-16 10:47:34 -07:00
return (
<mesh ref={ref as any}>
<planeGeometry args={[2, 2]} />
<primitive object={material} attach="material" />
</mesh>
2024-10-21 13:57:45 -06:00
)
}
2024-05-16 10:47:34 -07:00
const Shader: React.FC<ShaderProps> = ({ source, uniforms, maxFps = 60 }) => {
return (
<Canvas className="absolute inset-0 h-full w-full">
<ShaderMaterial source={source} uniforms={uniforms} maxFps={maxFps} />
</Canvas>
2024-10-21 13:57:45 -06:00
)
}
2024-05-16 10:47:34 -07:00
interface ShaderProps {
2024-10-21 13:57:45 -06:00
source: string
2024-05-16 10:47:34 -07:00
uniforms: {
[key: string]: {
2024-10-21 13:57:45 -06:00
value: number[] | number[][] | number
type: string
}
}
maxFps?: number
2024-05-16 10:47:34 -07:00
}