Reputation: 1409
I've been trying to move this codepen.io to a react component, this what I did:
React Custom Hook
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { createNoise4D } from 'simplex-noise';
import { random } from 'chroma-js';
interface Config {
el: string;
fov: number;
cameraZ: number;
xyCoef: number;
zCoef: number;
lightIntensity: number;
ambientColor: number;
light1Color: string;
light2Color: string;
light3Color: string;
light4Color: string;
}
interface UseBackgroundAnimationReturn {
noiseInputRef: React.RefObject<HTMLInputElement>;
heightInputRef: React.RefObject<HTMLInputElement>;
}
const useBackgroundAnimation = (config: Config): UseBackgroundAnimationReturn => {
const defaultConfig: Config = {
fov: 75,
cameraZ: 75,
xyCoef: 70,
zCoef: 20,
lightIntensity: 1,
ambientColor: 0xff0000,
light1Color: '#0E09DC',
light2Color: '#1CD1E1',
light3Color: '#18C02C',
light4Color: '#ee3bcf',
...config,
};
const conf = { ...defaultConfig, ...config };
const rendererRef = useRef<THREE.WebGLRenderer>();
const cameraRef = useRef<THREE.PerspectiveCamera>();
const sceneRef = useRef<THREE.Scene>();
const planeRef = useRef<THREE.Mesh>();
const lightRefs = useRef<THREE.PointLight[]>([]);
const mouseRef = useRef(new THREE.Vector2());
const noiseInputRef = useRef<HTMLInputElement>(null);
const heightInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const init = () => {
const canvas = document.getElementById(conf.el) as HTMLCanvasElement;
if (!canvas) return;
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
const camera = new THREE.PerspectiveCamera(conf.fov);
camera.position.z = conf.cameraZ;
rendererRef.current = renderer;
cameraRef.current = camera;
updateSize();
window.addEventListener('resize', updateSize);
initScene();
initGui();
animate();
};
const initGui = () => {
if (noiseInputRef.current) {
noiseInputRef.current.value = (101 - conf.xyCoef).toString();
noiseInputRef.current.addEventListener('change', (e: Event) => {
conf.xyCoef = 101 - (e.target as HTMLInputElement).valueAsNumber;
});
}
if (heightInputRef.current) {
heightInputRef.current.value = ((conf.zCoef * 100) / 25).toString();
heightInputRef.current.addEventListener('change', (e: Event) => {
conf.zCoef = ((e.target as HTMLInputElement).valueAsNumber * 25) / 100;
});
}
document.getElementById('trigger')?.addEventListener('click', updateLightsColors);
};
const initScene = () => {
const scene = new THREE.Scene();
sceneRef.current = scene;
initLights();
const mat = new THREE.MeshLambertMaterial({ color: 0xff0000, side: THREE.DoubleSide });
// const mat = new THREE.MeshStandardMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const geo = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight, window.innerWidth / 2, window.innerHeight / 2);
const plane = new THREE.Mesh(geo, mat);
planeRef.current = plane;
scene.add(plane);
plane.rotation.x = -Math.PI / 2 - 0.2;
plane.position.y = -25;
cameraRef.current!.position.z = 60;
};
const initLights = () => {
const lightDistance = 500;
// Adding Ambient Light
const ambientLight = new THREE.AmbientLight(0x404040, 1); // soft white light
sceneRef.current!.add(ambientLight);
const light1 = new THREE.PointLight(conf.light1Color, conf.lightIntensity, lightDistance);
const light2 = new THREE.PointLight(conf.light2Color, conf.lightIntensity, lightDistance);
const light3 = new THREE.PointLight(conf.light3Color, conf.lightIntensity, lightDistance);
const light4 = new THREE.PointLight(conf.light4Color, conf.lightIntensity, lightDistance);
light1.position.set(0, 50, 30);
light2.position.set(0, -50, -30);
light3.position.set(50, 50, 0);
light4.position.set(-50, 50, 0);
lightRefs.current = [light1, light2, light3, light4];
sceneRef.current!.add(light1);
sceneRef.current!.add(light2);
sceneRef.current!.add(light3);
sceneRef.current!.add(light4);
};
const animate = () => {
setTimeout(() => {
}, 500);
requestAnimationFrame(animate);
animatePlane();
animateLights();
rendererRef.current!.render(sceneRef.current!, cameraRef.current!);
};
const animatePlane = () => {
const gArray = planeRef.current!.geometry.attributes.position.array;
const time = Date.now() * 0.0001;
const noise4D = createNoise4D();
for (let i = 0; i < gArray.length; i += 3) {
gArray[i + 2] = noise4D(gArray[i] / conf.xyCoef, gArray[i + 1] / conf.xyCoef, time, mouseRef.current.x + mouseRef.current.y) * conf.zCoef;
}
planeRef.current!.geometry.attributes.position.needsUpdate = true;
};
const animateLights = () => {
const time = Date.now() * 0.001;
const d = 50;
lightRefs.current.forEach((light, i) => {
light.position.x = Math.sin(time * 0.1 * (i + 1)) * d;
light.position.z = Math.cos(time * 0.2 * (i + 1)) * d;
});
};
const updateLightsColors = () => {
conf.light1Color = random().hex();
conf.light2Color = random().hex();
conf.light3Color = random().hex();
conf.light4Color = random().hex();
lightRefs.current[0].color = new THREE.Color(conf.light1Color);
lightRefs.current[1].color = new THREE.Color(conf.light2Color);
lightRefs.current[2].color = new THREE.Color(conf.light3Color);
lightRefs.current[3].color = new THREE.Color(conf.light4Color);
};
const updateSize = () => {
const width = window.innerWidth;
const height = window.innerHeight;
rendererRef.current!.setSize(width, height);
cameraRef.current!.aspect = width / height;
cameraRef.current!.updateProjectionMatrix();
};
init();
return () => {
window.removeEventListener('resize', updateSize);
};
}, [conf]);
return {
noiseInputRef,
heightInputRef,
};
};
export default useBackgroundAnimation;
and this is the component where I use it
import useBackgroundAnimation from './useBackgroundAnimation';
const BackgroundComponent: React.FC = () => {
const { noiseInputRef,
heightInputRef,
} = useBackgroundAnimation({
el: 'backgroundCanvas',
});
return (
<div className="text-center min-h-screen flex flex-col justify-center items-center bg-white">
<div id="page" className="max-w-3xl mx-auto">
<header className="mb-6">
<div className="inner">
<h3 className="masthead-brand">Background #1</h3>
<nav className="nav nav-masthead justify-content-center">
<a className="nav-link" href="https://codepen.io/soju22/" target="_blank" rel="noopener noreferrer">Codepen Profile</a>
<a className="nav-link" href="https://codepen.io/collection/AGZywR" target="_blank" rel="noopener noreferrer">ThreeJS Collection</a>
</nav>
</div>
</header>
<main role="main" className="inner cover">
<h2>Interactive Background</h2>
<p className="lead">
This simple interactive background is made with
<a href="https://threejs.org" target="_blank" rel="noopener noreferrer"> ThreeJS</a>
and a
<a href="https://threejs.org/docs/#api/en/geometries/PlaneBufferGeometry" target="_blank" rel="noopener noreferrer"> PlaneBufferGeometry</a>
animated with Simplex noise.
</p>
<form className="w-4/5 mx-auto">
<div className="form-row">
<div className="form-group col-sm-6">
<label htmlFor="noiseInput" className="form-label">Noise Coef</label>
<input ref={noiseInputRef} type="range" min="1" max="100" className="custom-range" id="noiseInput" />
</div>
<div className="form-group col-sm-6">
<label htmlFor="heightInput" className="form-label">Height Coef</label>
<input ref={heightInputRef} type="range" min="1" max="100" className="custom-range" id="heightInput" />
</div>
</div>
<div className="form-group col-md-12">
<button type="button" id="trigger" className="btn btn-sm btn-secondary">Random Colors</button>
</div>
</form>
</main>
<footer className="mastfoot mt-auto"></footer>
</div>
<canvas id="backgroundCanvas"></canvas>
</div>
);
};
export default BackgroundComponent;
And this is what I got:
The problem: I can't replicate the color and the smooth movement.
Upvotes: 1
Views: 45