update home page
This commit is contained in:
214
components/aiero/HeroImageScene.tsx
Normal file
214
components/aiero/HeroImageScene.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Mesh, Plane, Program, Renderer, Texture } from "ogl";
|
||||
|
||||
const vertex = `
|
||||
attribute vec2 uv;
|
||||
attribute vec2 position;
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fragment = `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tMap;
|
||||
uniform vec2 uResolution;
|
||||
uniform vec2 uImageResolution;
|
||||
uniform vec2 uPointer;
|
||||
uniform vec2 uVelocity;
|
||||
uniform float uStrength;
|
||||
varying vec2 vUv;
|
||||
|
||||
vec2 coverUv(vec2 uv, vec2 screen, vec2 image) {
|
||||
float screenRatio = screen.x / screen.y;
|
||||
float imageRatio = image.x / image.y;
|
||||
vec2 scale = vec2(1.0);
|
||||
|
||||
if (screenRatio > imageRatio) {
|
||||
scale.y = imageRatio / screenRatio;
|
||||
} else {
|
||||
scale.x = screenRatio / imageRatio;
|
||||
}
|
||||
|
||||
return (uv - 0.5) * scale + 0.5;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = coverUv(vUv, uResolution, uImageResolution);
|
||||
|
||||
vec2 pointerDelta = uv - uPointer;
|
||||
float distanceFromPointer = length(pointerDelta);
|
||||
float cursorMask = smoothstep(0.36, 0.0, distanceFromPointer);
|
||||
float innerMask = smoothstep(0.16, 0.0, distanceFromPointer);
|
||||
vec2 pull = normalize(pointerDelta + 0.0001) * innerMask * 0.0035;
|
||||
vec2 distortion = ((uVelocity * 0.075) - pull) * cursorMask * uStrength;
|
||||
|
||||
uv += distortion;
|
||||
|
||||
float red = texture2D(tMap, uv + distortion * 0.12).r;
|
||||
float green = texture2D(tMap, uv).g;
|
||||
float blue = texture2D(tMap, uv - distortion * 0.1).b;
|
||||
gl_FragColor = vec4(red, green, blue, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
interface HeroImageSceneProps {
|
||||
imageSrc: string;
|
||||
}
|
||||
|
||||
export function HeroImageScene({ imageSrc }: HeroImageSceneProps) {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const pointerRef = useRef({ x: 0.5, y: 0.5 });
|
||||
const velocityRef = useRef({ x: 0, y: 0 });
|
||||
const strengthRef = useRef(0);
|
||||
const lastMoveTimeRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
if (!root) return;
|
||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
if (reduceMotion.matches) return;
|
||||
|
||||
const renderer = new Renderer({
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
dpr: Math.min(window.devicePixelRatio, 2),
|
||||
});
|
||||
const gl = renderer.gl;
|
||||
root.appendChild(gl.canvas);
|
||||
|
||||
gl.canvas.className = "absolute inset-0 h-full w-full";
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
const texture = new Texture(gl, {
|
||||
generateMipmaps: false,
|
||||
minFilter: gl.LINEAR,
|
||||
magFilter: gl.LINEAR,
|
||||
});
|
||||
|
||||
const image = new Image();
|
||||
image.src = imageSrc;
|
||||
image.onload = () => {
|
||||
texture.image = image;
|
||||
texture.needsUpdate = true;
|
||||
};
|
||||
|
||||
const program = new Program(gl, {
|
||||
vertex,
|
||||
fragment,
|
||||
uniforms: {
|
||||
tMap: { value: texture },
|
||||
uResolution: { value: [1, 1] },
|
||||
uImageResolution: { value: [1883, 855] },
|
||||
uPointer: { value: [0.5, 0.5] },
|
||||
uVelocity: { value: [0, 0] },
|
||||
uStrength: { value: 0 },
|
||||
},
|
||||
transparent: true,
|
||||
});
|
||||
|
||||
const mesh = new Mesh(gl, {
|
||||
geometry: new Plane(gl, { width: 2, height: 2 }),
|
||||
program,
|
||||
});
|
||||
|
||||
let animationFrame = 0;
|
||||
let width = 1;
|
||||
let height = 1;
|
||||
|
||||
const resize = () => {
|
||||
const rect = root.getBoundingClientRect();
|
||||
width = Math.max(1, rect.width);
|
||||
height = Math.max(1, rect.height);
|
||||
renderer.setSize(width, height);
|
||||
program.uniforms.uResolution.value = [width, height];
|
||||
};
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
const rect = root.getBoundingClientRect();
|
||||
const isInside =
|
||||
event.clientX >= rect.left &&
|
||||
event.clientX <= rect.right &&
|
||||
event.clientY >= rect.top &&
|
||||
event.clientY <= rect.bottom;
|
||||
|
||||
if (!isInside) {
|
||||
velocityRef.current = { x: 0, y: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
const nextPointer = {
|
||||
x: (event.clientX - rect.left) / rect.width,
|
||||
y: 1 - (event.clientY - rect.top) / rect.height,
|
||||
};
|
||||
const now = performance.now();
|
||||
const elapsed = Math.max(16, now - lastMoveTimeRef.current);
|
||||
lastMoveTimeRef.current = now;
|
||||
velocityRef.current = {
|
||||
x: ((nextPointer.x - pointerRef.current.x) / elapsed) * 16.67,
|
||||
y: ((nextPointer.y - pointerRef.current.y) / elapsed) * 16.67,
|
||||
};
|
||||
pointerRef.current = nextPointer;
|
||||
strengthRef.current = Math.min(1, strengthRef.current + 0.72);
|
||||
};
|
||||
|
||||
const onPointerLeave = () => {
|
||||
velocityRef.current = { x: 0, y: 0 };
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
strengthRef.current *= 0.86;
|
||||
velocityRef.current = {
|
||||
x: velocityRef.current.x * 0.74,
|
||||
y: velocityRef.current.y * 0.74,
|
||||
};
|
||||
|
||||
program.uniforms.uPointer.value = [
|
||||
pointerRef.current.x,
|
||||
pointerRef.current.y,
|
||||
];
|
||||
program.uniforms.uVelocity.value = [
|
||||
velocityRef.current.x,
|
||||
velocityRef.current.y,
|
||||
];
|
||||
program.uniforms.uStrength.value = strengthRef.current;
|
||||
renderer.render({ scene: mesh });
|
||||
animationFrame = requestAnimationFrame(render);
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(resize);
|
||||
observer.observe(root);
|
||||
window.addEventListener("pointermove", onPointerMove);
|
||||
root.addEventListener("pointerleave", onPointerLeave);
|
||||
|
||||
resize();
|
||||
animationFrame = requestAnimationFrame(render);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
observer.disconnect();
|
||||
window.removeEventListener("pointermove", onPointerMove);
|
||||
root.removeEventListener("pointerleave", onPointerLeave);
|
||||
gl.canvas.remove();
|
||||
};
|
||||
}, [imageSrc]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={rootRef}
|
||||
aria-hidden="true"
|
||||
className="hero-ogl-scene absolute inset-[-18px]"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center"
|
||||
style={{ backgroundImage: `url(${imageSrc})` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user