3 min read

Awesome Demos of React + Three.JS

Paul Henschel has created some mini demos of React + Three.JS based on requests from Reddit.

This is one of the demos where the camera move along with the model:

App.js
import { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import {
  ScrollControls,
  useScroll,
  Cloud,
  Sky,
  Clouds,
  Text,
} from "@react-three/drei";
import { easing } from "maath";
import tunnel from "tunnel-rat";
const t = tunnel();
 
export default function App() {
  return (
    <>
      <Canvas shadows camera={{ position: [0, 2, 10] }}>
        <ambientLight intensity={Math.PI / 2} />
        <spotLight
          position={[10, 3, 10]}
          angle={0.45}
          penumbra={1}
          decay={0}
          intensity={Math.PI * 1.25}
          castShadow
          shadow-bias={-0.00001}
        />
        <directionalLight position={[10, -20, 5]} intensity={Math.PI} />
        <ScrollControls pages={4} infinite>
          <Player position={[0, -0.1, 6.25]} scale={0.35} />
          <World />
          <Annotation />
        </ScrollControls>
        <Sky />
      </Canvas>
      <t.Out />
    </>
  );
}
 
function Player(props) {
  const ref = useRef();
  const scroll = useScroll();
  useFrame((state, delta) => {
    easing.damp3(
      state.camera.position,
      [state.pointer.x, 2 + state.pointer.y, 10],
      0.2,
      delta,
    );
    state.camera.lookAt(0, 0, -2);
    ref.current.rotation.z = -Math.PI * 8 * scroll.offset;
    ref.current.position.y = 1 + Math.sin(-Math.PI * 8 * scroll.offset);
  });
  return (
    <group {...props}>
      <mesh castShadow receiveShadow ref={ref}>
        <torusGeometry args={[0.8, 0.5, 6, 5]} />
        <meshStandardMaterial flatShading color="orange" />
      </mesh>
    </group>
  );
}
 
function World() {
  const ref = useRef();
  const scroll = useScroll();
  useFrame(
    (state, delta) => (ref.current.rotation.y = -Math.PI * 2 * scroll.offset),
  );
  return (
    <group ref={ref}>
      <mesh castShadow receiveShadow position={[0, -5.5, 0]}>
        <cylinderGeometry args={[7, 7, 10, 16]} />
        <meshStandardMaterial flatShading color="seagreen" />
      </mesh>
      <Clouds texture="/cloud.png">
        <Mountain position={[0, 0.5, 2]} height={0.75} />
        <Mountain position={[-1.5, 1, -1]} height={0.85} color="gray" />
        <Cloud
          speed={0.1}
          seed={1}
          opacity={1}
          volume={100}
          bounds={[90, 20, 90]}
          position={[0, 10, 0]}
        />
      </Clouds>
    </group>
  );
}
 
function Mountain({ height: h = 1, color = "peachpuff", ...props }) {
  const clouds = useRef();
  useFrame((state, delta) => (clouds.current.rotation.y -= (delta / 4) * h));
  return (
    <mesh castShadow receiveShadow {...props}>
      <coneGeometry args={[4.5 * h, 6 * h, 16]} />
      <meshStandardMaterial color={color} flatShading />
      <mesh castShadow receiveShadow position={[0, 2.5 * h, 0]} rotation-y={1}>
        <coneGeometry args={[1.1 * h, 1.6 * h, 16]} />
        <meshStandardMaterial flatShading />
      </mesh>
      <group ref={clouds} scale={0.3}>
        <Cloud
          speed={0.2}
          seed={4}
          opacity={2}
          segments={5}
          volume={10 * h}
          bounds={[7 * h, 1, 7 * h]}
          position={[0, 10 * h, 0]}
        />
      </group>
    </mesh>
  );
}
 
function Annotation() {
  const ref = useRef();
  const scroll = useScroll();
  useFrame((state, delta) => {
    easing.damp(
      ref.current.style,
      "opacity",
      scroll.offset > 0.25 && scroll.offset < 0.6 ? 1 : 0,
      0.2,
      delta,
    );
  });
  return (
    <t.In>
      <div
        ref={ref}
        style={{ position: "absolute", top: 30, left: 30, fontSize: "2em" }}
      >
        Hello ๐Ÿ‘‹
      </div>
    </t.In>
  );
}

You can check out all the demos, including the video and CodePen, on this post