blob: 32b13accfb8e7ff2b22c6d3c92ca6e15bb656c08 [file] [log] [blame]
import type { FC } from 'react';
import React, { useRef, useEffect } from 'react';
import {
Vector2,
Color,
ShaderMaterial,
Points,
PlaneBufferGeometry,
Mesh,
MeshBasicMaterial,
WebGLRenderer,
PerspectiveCamera,
BufferAttribute,
Scene,
DoubleSide,
Clock,
} from 'three';
import { gsap } from 'gsap/gsap-core';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// eslint-disable-next-line import/no-unresolved, import/no-webpack-loader-syntax, import/extensions
import fragment from '!!raw-loader!../shaders/fragment.frag';
// eslint-disable-next-line import/no-unresolved, import/no-webpack-loader-syntax, import/extensions
import vertex from '!!raw-loader!../shaders/vertex.vert';
const HeroCanvas: FC = () => {
const canvasRef = useRef<HTMLCanvasElement>();
const overlayRef = useRef<HTMLDivElement>();
let canvasHeight: number;
let canvasWidth: number;
let screenWidth: number;
let screenHeight: number;
let controls: OrbitControls;
let isLoaded = false;
let isRendering = false;
let animationFrame: number;
const clock = new Clock();
let delta = 0;
const interval = 1 / 30;
useEffect(() => {
screenWidth = window.innerWidth;
screenHeight = window.innerHeight;
const mouse = { x: 0.5, y: 0.5 };
const fragMouse = { x: 0.5, y: 0.5 };
let camera; let mesh; let scene; let renderer; let material; let
geometry;
window.addEventListener('resize', onWindowResize, false);
if (screenWidth > 1100) {
canvasHeight = screenHeight;
canvasWidth = screenWidth / 2;
} else {
canvasHeight = screenHeight / 2;
canvasWidth = screenWidth;
}
canvasRef.current.width = canvasWidth;
canvasRef.current.height = canvasHeight;
canvasRef.current.addEventListener('mousemove', (event) => {
const ctx = {
x: (event.clientX),
y: (event.clientY),
};
const canvasOffset = {
left: canvasRef.current.getBoundingClientRect().x,
top: canvasRef.current.getBoundingClientRect().y,
};
ctx.x = ((ctx.x - canvasOffset.left) / canvasWidth);
ctx.y = ((ctx.y - canvasOffset.top) / canvasHeight);
gsap.to(mouse, {
duration: 2,
x: ctx.x * (canvasWidth / canvasHeight) - (canvasWidth / canvasHeight) / 2,
y: (1.0 - ctx.y) - 0.5,
onUpdate: () => {
material.uniforms.u_mouse.value.x = mouse.x;
material.uniforms.u_mouse.value.y = mouse.y;
},
});
gsap.to(fragMouse, {
duration: 2,
x: ctx.x,
y: (1.0 - ctx.y),
onUpdate: () => {
material.uniforms.u_fragMouse.value.x = fragMouse.x;
material.uniforms.u_fragMouse.value.y = fragMouse.y;
},
});
});
function getRandom(a, b) {
return a + (b - a) * Math.random();
}
const canvasObserver = new IntersectionObserver(onCanvasIntersection, {
root: null,
threshold: 0.1,
});
init(canvasWidth, canvasHeight);
function onCanvasIntersection(entries) {
entries.forEach((entry) => {
if (entry.isIntersecting && isLoaded) {
if (isLoaded && !isRendering) {
animate();
} else {
console.log('Loading');
}
} else {
if (animationFrame) {
cancelAnimationFrame(animationFrame);
isRendering = false;
}
}
});
}
canvasObserver.observe(canvasRef.current);
function init(width: number, height: number) {
const ctx = canvasRef.current;
renderer = new WebGLRenderer({ canvas: ctx });
renderer.autoClearColor = false;
camera = new PerspectiveCamera(45, width / height, 0.1, 100);
controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = false;
controls.enablePan = false;
controls.enabled = false;
geometry = new PlaneBufferGeometry(width / height, 1, 250, 250);
const { count } = geometry.attributes.position;
const arrSize = new BufferAttribute(new Float32Array(count), 1);
for (let i = 0; i < arrSize.count; i += 1) {
arrSize.array[i] = getRandom(0, 1);
}
geometry.setAttribute('aSize', arrSize, 1);
geometry.scale(2.0, 1.0, 1.0);
scene = new Scene();
renderer.setSize(canvasWidth, canvasHeight);
const uniforms = {
u_time: {
type: 'f',
value: 1.0,
},
u_resolution: {
type: 'v2',
value: new Vector2(),
},
u_mouse: {
type: 'v2',
value: new Vector2(0.5, 0.5),
},
u_fragMouse: {
type: 'v2',
value: new Vector2(0.5, 0.5),
},
};
scene.background = new Color('red');
camera.position.z = 5;
controls.update();
material = new ShaderMaterial({
uniforms,
vertexShader: vertex,
fragmentShader: fragment,
wireframe: true,
side: DoubleSide,
});
mesh = new Points(geometry, material);
const backGeometry = new PlaneBufferGeometry(width / height, 1, 200, 200);
const bgMaterial = new MeshBasicMaterial({ color: 0x121212, wireframe: false });
const background = new Mesh(backGeometry, bgMaterial);
backGeometry.scale(50, 50, 1);
background.position.set(10, 10, -10);
background.rotation.set(Math.PI / 2, 0, 0);
scene.add(mesh);
scene.add(background);
// Tested and checked
camera.position.set(
0.16430412417444037,
-1.5202138879420155,
0.20892968987792318,
);
controls.update();
renderer.setPixelRatio(window.devicePixelRatio);
onWindowResize();
isLoaded = true;
}
function onWindowResize() {
screenHeight = window.innerHeight;
screenWidth = window.innerWidth;
if (screenWidth > 1100) {
canvasHeight = screenHeight;
canvasWidth = screenWidth / 2;
} else {
canvasHeight = screenHeight / 2;
canvasWidth = screenWidth;
}
renderer.setSize(canvasWidth, canvasHeight);
material.uniforms.u_resolution.value.x = renderer.domElement.width;
material.uniforms.u_resolution.value.y = renderer.domElement.height;
}
function animate() {
animationFrame = requestAnimationFrame(animate);
delta += clock.getDelta();
if (delta > interval) {
// The draw or time dependent code are here
material.uniforms.u_time.value += 0.05;
controls.update();
renderer.render(scene, camera);
isRendering = true;
delta %= interval;
}
}
return () => {
// eslint-disable-next-line prefer-spread
scene.remove.apply(scene, scene.children);
canvasObserver.disconnect();
};
}, []);
useEffect(() => {
gsap.to(overlayRef.current, {
height: 0,
duration: 2,
ease: 'Expo.easeInOut',
});
}, []);
return (
<div className="hero-infograph" style={{ position: 'relative' }}>
<canvas ref={canvasRef} className="homeCanvas" />
<div ref={overlayRef} className="homeCanvas-overlay" />
</div>
);
};
export default HeroCanvas;