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.jsimport { 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> ); }copy You can check out all the demos, including the video and CodePen, on this post