🚀 使用 React Three Fiber 构建交互式 3D 火箭复活节彩蛋

2025-06-07

🚀 使用 React Three Fiber 构建交互式 3D 火箭复活节彩蛋

当我们在领英上获得 1000 名粉丝时,我们知道必须好好庆祝一下!受到 Vercel 的精彩博文《使用 React Three Fiber 构建交互式 3D 活动徽章》的启发,我们想借此机会推出一些有趣的东西。那么,还有什么比在我们闪亮的新网站zerodays.dev上放一个 3D 火箭彩蛋更好的庆祝方式呢

这是那种制作起来很有趣的东西,就像指尖陀螺的电子版,但更酷炫。我们制作它的过程非常愉快,今天我们将带你了解幕后花絮。

让我们直接开始吧。

🚀 目录:

  1. 🏁 设置

  2. 🚀 主角:我们的互动火箭

  3. 💥 粒子系统:为火箭排气提供燃料

  4. 🪨SpaceRocks:小行星的物理和分裂

  5. 🌌 太空环境:沉浸式太空背景

  6. 🏎️ 性能提升:优化流畅的火箭体验

  7. 🚀 总结:最后的倒计时

🏁 设置:

🛠️ 构建 3D 火箭的工具

我们保持技术堆栈精简但功能强大,以使这个复活节彩蛋飞起来

🎬 设置 3D 场景

第一步是为我们的火箭设置基本的 3D 场景。我们使用了react-three-fiber来处理 React 组件内部的场景渲染,并使用react-three-drei来处理摄像头和光照等辅助功能。我们还集成了react-three-rapier来实现物理和碰撞检测。

以下是初始设置的简化版本:

'use client';

import { Canvas } from '@react-three/fiber';
import { OrthographicCamera } from '@react-three/drei';
import { Physics } from '@react-three/rapier';
import Rocket from '@/components/three/rocket';
import SpaceEnvironment from '@/components/three/space-environment';
import SpaceRocks from '@/components/three/space-rocks';

const RocketScene = () => {
  return (
    <Canvas>
      <OrthographicCamera makeDefault position={[0, 0, 0]} zoom={1} />
      <ambientLight intensity={0.4} />
      <directionalLight position={[-30, 100, 0]} intensity={0.8} />

      <Physics gravity={[0, 0, 0]}>
        <Rocket />
        <SpaceRocks />
      </Physics>

      <SpaceEnvironment />
    </Canvas>
  );
};

export default RocketScene;
Enter fullscreen mode Exit fullscreen mode

这将建立我们的 3D 世界:

  • 火箭:节目的明星。
  • 太空环境:很酷的太空背景。
  • 太空岩石:漂浮的太空碎片,增添了额外的刺激和危险!火箭可以与这些岩石碰撞,使它们分裂,从而带来更具动感和互动性的体验。
  • 照明:用于宇宙光辉的环境光和定向光。
  • 物理:处理物体之间的碰撞和相互作用。

🎥 相机选择:为什么我们选择正交摄影

我们为场景选择了正交相机,因为无论火箭飞向何处,它都能保持所有元素的比例。由于我们的火箭场景横跨整个页面(高度由可滚动内容决定),正交相机让我们能够平滑地上下飞行火箭,而不会产生任何奇怪的透视变形。它确保了您在滚动页面并与火箭交互时获得一致且友好的体验。

💡 快速提示:使用eventSource

为了确保 3D 火箭场景即使在其他页面内容覆盖其上时仍能响应指针事件(例如鼠标或触摸交互),我们eventSourceCanvas组件中使用了 prop。这会告诉react-three-fiber在哪里监听事件。通过将其设置eventSource为根元素 ( #root),火箭可以在其他内容覆盖其上方时接收事件。

以下是我们的设置方法:

<Canvas
  eventSource={document.getElementById('root')}  // Specifies the DOM element to listen for pointer events
  eventPrefix="page"         // The event prefix that is cast into canvas pointer x/y events
/>
Enter fullscreen mode Exit fullscreen mode

🚀 主角:我们的互动火箭

火箭是整个演示的主角——它能够与环境互动,响应鼠标移动,并在页面上发射。为了让它真正动感十足,我们使用了react-three-fiber进行渲染,并使用 react-three-rapier实现物理效果。让我们来分解一下它的核心功能。

主要特点:

  • 状态机:处理火箭的不同状态——空闲、发射、跟随(跟踪鼠标)和重置。
  • 碰撞与物理:使用CapsuleColliderRoundConeCollider,火箭可以与太空岩石碰撞。物理原理也有助于模拟火箭发射时的运动和冲击力。
  • 悬停和点击交互:火箭通过放大效果响应鼠标悬停,单击可发射火箭或重置其位置。
  • 平滑运动:火箭跟踪鼠标,使用矢量计算距离并施加力,同时在useFrame()中平滑旋转以跟随其飞行方向。
  • 粒子:在火箭运动过程中添加动态排气粒子。

以下是关键片段:

const Rocket = memo(() => {
    // Refs and Hooks
    const rocketHovered = useRef(false);
    ...

    // State machine
    const { value: rocketState, transitionTo } = useStateMachine({
      initial: 'idle',
      states: {
        idle: {},
        launching: { onEnter: () => { setTimeout(() => transitionTo('following'), 500); } },
        following: {},
        resetting: { onEnter: async () => { await resetRocket(); transitionTo('idle'); } },
      },
    });

    // Rocket reset logic
    const resetRocket = useCallback(() => {
      ... // Reset rocket position, velocity, rotation
    }, [viewportSize]);

    // Frame logic (executed every frame)
    useFrame((state, delta) => {
      ...
      // Scale the rocket when hovered
      rocketHovered.current
        ? meshRef.current?.scale.lerp(new Vector3(1.1, 1.1, 1.1), delta * 5)
        : meshRef.current?.scale.lerp(new Vector3(1, 1, 1), delta * 5);

      // Execute state-specific logic
      switch (rocketState) {
        case 'idle': resetRocket(); break;
        case 'launching': rocket.applyImpulse(..., true); break;
        case 'following': { ... } // Move rocket towards pointer, apply rotation
      }
    });

    return (
      <group>
        <RigidBody ref={rigidBodyRef}>
          <CapsuleCollider />
          <RoundConeCollider />
          <group
            ref={meshRef}
            onPointerEnter={() => { rocketHovered.current = true; }}
            onPointerLeave={() => { rocketHovered.current = false; }}
            onClick={() => rocketState === 'following' ? transitionTo('resetting') : transitionTo('launching')}
          >
            <RocketModel />
          </group>
        </RigidBody>

        {/* Our own particle effects component of the rocket exhaust clouds */}
        <Particles
          visible={rocketState === 'launching' || rocketState === 'following'}
          objectRef={exhaustMeshRef}
          config={{ ... }}
        />
      </group>
    );
});
Enter fullscreen mode Exit fullscreen mode

火箭跟随运动:跟踪鼠标

我们的火箭最酷炫的部分之一是它能够在太空中跟随鼠标移动。这项功能由react-three-fiberuseFrame中的钩子实现,它允许我们每帧更新火箭的位置和旋转。以下是我们在跟踪用户鼠标的同时处理火箭平滑移动和旋转的方法。

要点:

  • 鼠标跟踪:我们将指针的标准化设备坐标(NDC)转换为视口坐标,并将火箭移向该点。
  • 距离计算:我们计算火箭当前位置和目标之间的距离,并利用该距离在正确的方向上施加力。
  • 旋转:火箭平稳旋转以面向其移动方向,带来逼真的飞行体验。
以下是我们实现这一目标的简化版本:
useFrame((state, delta) => {
  const rocket = rigidBodyRef.current;
  if (!rocket || !viewportSize) return;

  // Convert pointer coordinates to viewport space
  const x = (state.pointer.x * viewportSize.width) / 2;
  const y = (state.pointer.y * viewportSize.height) / 2;

  // Calculate distance between current rocket position and target
  const currentPos = rocket.translation() as Vector3;
  const targetPos = new Vector3(x, y, currentPos.z);
  const distance = targetPos.distanceTo(currentPos);

  // Apply impulse to move rocket toward target
  direction.current.copy(targetPos).sub(currentPos).normalize().multiplyScalar(delta * 1000 * distance);
  rocket.applyImpulse(direction.current, true);

  // Calculate the rotation angle and smoothly rotate the rocket
  const rotationAngle = Math.atan2(y - currentPos.y, x - currentPos.x);
  targetQuaternion.current.setFromAxisAngle(rotationAxis.current, rotationAngle - Math.PI / 2);
  slerpedQuaternion.current.slerpQuaternions(rocket.rotation(), targetQuaternion.current, 0.08);
  rocket.setRotation(slerpedQuaternion.current, true);
});
Enter fullscreen mode Exit fullscreen mode

💡 快速提示:处理事件<group>

在处理three.js和光线投射时,一个 中通常会包含多个网格或子网格<group>。这会导致每个子网格被光线投射器击中时触发多个onClick或事件。为了避免这种情况,我们可以使用来阻止多个事件触发。onPointerEntere.stopPropagation()

这是一个简单的例子:

<group
  name="rocket"
  onClick={(e) => {
    e.stopPropagation(); // Prevent multiple onClick calls

  }}
>
  <RocketModel />
  <pointLight />
  <mesh ... />
</group>
Enter fullscreen mode Exit fullscreen mode

🚀 摘要 - 火箭:

  • 状态机:控制火箭的不同模式——空闲发射跟随(跟踪鼠标)和重置。这确保了状态和交互之间的平滑过渡。

  • 物理与碰撞:使用CapsuleColliderRoundConeCollider,火箭可以与太空岩石等物体碰撞。基于物理的运动和脉冲处理确保碰撞响应逼真。

  • 鼠标交互:得益于缩放效果,鼠标悬停在火箭上时,火箭会变大。点击即可发射火箭或将其重置为初始状态,为用户增添趣味互动。

  • 流畅的鼠标追踪:火箭会根据鼠标与指针的距离,计算并施加相应的力度,从而流畅地追踪鼠标位置。火箭会沿着移动方向旋转,带来逼真的飞行体验。

  • 粒子:动态排气粒子跟随火箭,在火箭发射和移动时增加视觉效果。

💥 粒子系统:为火箭排气提供燃料

如果没有一些史诗级的排气粒子,我们的火箭就感觉不完整!我们使用three.js构建了一个自定义粒子系统,用于渲染和基于着色器的控制,使其拥有动态外观。该系统高度可定制,让我们可以控制从粒子寿命到速度和湍流的所有内容。

关键概念:

  1. 自定义着色器材质:我们使用ShaderMaterial来控制粒子的外观,例如大小和颜色。片段着色器包含根据粒子年龄淡入淡出的逻辑。

    const ParticleShaderMaterial = new ShaderMaterial({
      uniforms: {
        color: { value: new Color('cyan') },
        pointSize: { value: 1.0 },
        dpr: { value: window.devicePixelRatio },
      },
      vertexShader: `
        attribute float age;
        varying float vAge;
        void main() {
          vAge = age;
          gl_PointSize = ...;
          gl_Position = ...;
        }
      `,
      fragmentShader: `
        varying float vAge;
        void main() {
          float alpha = 1.0 - vAge; // Fade based on age
          gl_FragColor = vec4(color, alpha);
        }
      `,
    });
    
  2. 粒子初始化:粒子以随机位置和速度初始化,从而创建逼真的尾气扩散效果。每个粒子都具有位置、年龄和大小等属性。

    function initializeParticles(...) {
      const positions = [], velocities = [], ages = [], sizes = [];
      for (let i = 0; i < count; i++) {
        positions.push(...); // Set initial position
        velocities.push(new Vector3(...)); // Random velocity
        ages.push(-i / emissionRate); // Stagger particle emission
        sizes.push(size + sizeVariance * (Math.random() - 0.5) * 2);
      }
      return { positions, velocities, ages, sizes };
    }
    
  3. 帧更新:每一帧,我们根据粒子的速度更新其位置,并应用重力和湍流。随着粒子老化,它们会逐渐消失,并在达到其寿命极限时重置。

    useFrame((state, delta) => {
      // Update particle position and age every frame
      for (let i = 0; i < config.maxParticles; i++) {
        if (ages[i] >= 1.0) resetParticle(i); // Reset expired particles
        else updateParticle(i, delta);
      }
    });
    
  4. 动态粒子重置:当粒子的年龄达到其极限时,它会重置为新的位置、速度和年龄,为下一个发射周期做好准备。

    function resetParticle(index) {
      // Reset particle to new random position and velocity
      positions[index * 3] = ...;
      velocities[index] = new Vector3(...);
      ages[index] = 0;
    }
    

组件结构:

粒子系统渲染为<points>网格,其缓冲区属性包括位置、年龄和大小。自定义ShaderMaterial控制粒子的外观和行为。

<points visible={visible} ref={meshRef}>
    <bufferGeometry attach="geometry">
      <bufferAttribute attach="attributes-position" {...positions} />
      <bufferAttribute attach="attributes-age" array={ages} itemSize={1} />
      <bufferAttribute attach="attributes-particleSize" {...sizes} itemSize={1} />
    </bufferGeometry>
    <primitive attach="material" object={ParticleShaderMaterial} transparent />
</points>
Enter fullscreen mode Exit fullscreen mode

💥 摘要 - 粒子:

  • 自定义着色器材质:我们使用ShaderMaterial来管理粒子的外观、尺寸、颜色和淡入淡出效果。粒子会根据其年龄进行淡入淡出,从而提供逼真的排气效果。

  • 粒子初始化:每个粒子的位置、速度、年龄和大小均随机初始化。这种随机性使尾气在火箭移动时能够自然扩散。

  • 帧更新:系统会根据粒子的速度,每一帧更新其位置,并应用重力和湍流等效果。随着粒子老化,它们会逐渐消失,并在其生命周期结束时重置。

  • 动态粒子重置:当粒子过期时,它会使用新的随机属性(位置、速度等)重置,确保连续发射,而无需从头开始生成新粒子。

  • 高效的结构:系统利用<points>具有缓冲属性的网格来高效处理粒子数据。ShaderMaterial负责管理渲染和视觉效果,确保一切性能卓越。

🪨SpaceRocks:小行星的物理和分裂

SpaceRocks组件通过生成类似小行星的岩石,为我们的场景增添了活力。这些岩石漂浮在空中,在碰撞时会分裂开来。使用Rapier的物理引擎,这些岩石会四处弹跳,与火箭相互作用,并在受到足够重的撞击时动态分裂。

主要特点:

  1. 岩石几何:每块岩石都使用由随机顶点组成的凸几何体创建。这会产生不规则的岩石状形状。

    const generateRockGeometry = () => {
      const vertices: Vector3[] = [];
      for (let i = 0; i < 50; i++) {
        vertices.push(new Vector3((Math.random() - 0.5) * 3, ...));
      }
      const geometry = new ConvexGeometry(vertices);
      geometry.scale(5, 5, 5);
      return geometry;
    };
    
  2. 岩石分裂:当一块岩石以足够的力量碰撞时,它会分裂成两块较小的岩石。分裂的过程是通过计算沿特定平面的交点,然后从原始岩石生成两块新的岩石来实现的。

    const splitRock = (rockGeometry: ConvexGeometry) => {
      const verticesA: Vector3[] = [], verticesB: Vector3[] = [];
      // Split the geometry along a plane
      const plane = new Plane(new Vector3(1, 0, 0), 0);
      const geometryA = new ConvexGeometry(verticesA), geometryB = new ConvexGeometry(verticesB);
      return [geometryA, geometryB];
    };
    
  3. 碰撞处理:当岩石与火箭或其他物体发生碰撞时,我们会计算碰撞力。如果碰撞力超过阈值,岩石就会分裂。

    onContactForce={(payload) => {
      const forceMag = payload.totalForceMagnitude / 100000;
      if (forceMag > 80) handleCollision(key, forceVec); // Only split if the force is strong enough
    }}
    

摇滚一代:

  • 岩石在网格中随机定位并被赋予速度。它们被赋予了速度、角速度以及碰撞时分裂的能力等属性。

    for (let i = 0; i < 10; i++) {
      const rockId = `${idPrefix}_rock_${i + 1}`;
      rockMap.set(rockId, {
        ref: createRef<RapierRigidBody>(),
        position: gridCells[i],
        velocity: new Vector3((Math.random() - 0.5) * 10, ...),
        geometry: generateRockGeometry(),
        canSplit: true,
        scale: 3 + Math.random() * 4,
      });
    }
    

组件结构:

每块岩石都是一个刚体 ( RigidBody),具有恢复力(弹性)和摩擦力等属性。这些岩石会动态地相互作用,在环境中弹跳,并在撞击时分裂。

<RigidBody
    ref={rock.ref}
    position={rock.position}
    linearVelocity={rock.velocity.toArray()}
    restitution={0.9} // Bouncy collisions
    friction={0.1}
    onContactForce={(payload) => handleCollision(key, forceVec)} // Handle rock splitting
>
    {/* Hull - Auto-generates mesh collider for convex geometries */}
    <MeshCollider type="hull">
    <mesh geometry={rock.geometry} scale={rock.scale}>
        <primitive attach="material" object={rockMaterial} />
    </mesh>
    </MeshCollider>
</RigidBody>
Enter fullscreen mode Exit fullscreen mode

🪨摘要 - SpaceRocks:

  • 动态几何:使用凸几何随机生成不规则形状的岩石
  • 碰撞和分裂:岩石在高强度碰撞下分裂成更小的岩石,产生动态相互作用。
  • 物理集成:使用Rapier实现逼真的运动、摩擦和弹性碰撞,使岩石在场景中表现得令人信服。

当火箭在太空中航行时,该系统增加了额外的互动性和乐趣!

🌌 太空环境:沉浸式太空背景

SpaceEnvironment组件为我们的火箭场景提供了动态背景。它包含一片星空和散布在太空中的行星集合。以下是我们使用three.jsreact-three-fiber和着色器构建它以实现自定义效果的方法。

主要特点:

  1. 动态星空:星星在一个圆柱形空间(高度 = 页面高度)内随机生成,并通过控制大小和不透明度的自定义着色器使其闪烁。星星闪烁、消逝,模拟出一个生动活泼的太空场景。

    const StarShaderMaterial = new ShaderMaterial({
      uniforms: { color: { value: new Color('white') }, opacity: { value: 0 }, time: { value: 0 }, dpr: { value: 1.0 }},
      vertexShader: `
        varying float vTwinkle; 
        uniform float time;
        void main() {
          vTwinkle = 0.5 + 0.5 * sin(time + position.x * 10.0);
          gl_PointSize = ...;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        varying float vTwinkle;
        void main() {
          gl_FragColor = vec4(color, opacity * vTwinkle);
        }
      `,
      transparent: true,
    });
    
  2. 实例化行星:我们使用实例化技术,高效地渲染空间中随机位置的行星。每颗行星都有随机的颜色、大小和位置,使场景充满变化。

    const planets = useMemo(() => {
      return Array.from({ length: 20 }).map((_, index) => (
        <Instance
          key={index}
          position={getRandomInCylinder(radius, height, innerPadding)}
          scale={Math.random() * 100}
          color={planetColors[index % planetColors.length]}
        />
      ));
    }, [radius, height, innerPadding]);
    
  3. 旋转的太空环境:整个太空环境缓慢旋转,使恒星和行星看起来像在背景中移动。

    useFrame((state, delta) => {
      environmentRef.current.rotation.y += delta * 0.015;
      StarShaderMaterial.uniforms.time.value = state.clock.getElapsedTime();
    });
    

💡 快速提示:使用window.devicePixelRatio一致的着色器渲染

使用涉及点大小(例如星空里的星星)的着色器时,务必考虑不同的屏幕分辨率和像素密度。通过引入window.devicePixelRatio,您可以确保着色器能够适应不同的屏幕,从而在不同设备上保持一致的渲染效果。

以下是我们的应用方法:

const StarShaderMaterial = new ShaderMaterial({
  uniforms: {
    dpr: { value: window.devicePixelRatio }, // Ensures consistent point size across devices
  },
  vertexShader: `
    uniform float dpr;
    void main() {
      gl_PointSize = gl_PointSize * dpr; // Adjust point size by device pixel ratio
    }
  `,
});
Enter fullscreen mode Exit fullscreen mode

提醒:当屏幕发生变化时,请务必更新相关钩子dpr中的值useFrame,例如在不同分辨率的显示器上拖动窗口!

useFrame(() => {
  StarShaderMaterial.uniforms.dpr.value = window.devicePixelRatio;
});
Enter fullscreen mode Exit fullscreen mode

组件结构:

星星和行星在<group>元素内部渲染。星星使用 渲染,而行星则使用实例<points>创建,以提高渲染效率。

<group>
  {/* Instanced stars */}
  <points position={[0, 0, 0]}>
    <bufferGeometry>
      <bufferAttribute attach="attributes-position" {...stars} />
    </bufferGeometry>
    <primitive attach="material" object={StarShaderMaterial} />
  </points>

  {/* Instanced planets */}
  <Instances limit={100} material={PlanetMaterial}>
    <sphereGeometry args={[1, 32, 32]} />
    {planets}
  </Instances>
</group>
Enter fullscreen mode Exit fullscreen mode

🌌 摘要 - 空间环境:

  • 动态星空:圆柱形空间中随机散布的闪烁星星为环境增添了生机。
  • 实例行星:随机定位和缩放的行星为场景提供了多样性和深度。
  • 旋转环境:整个背景的缓慢旋转使空间感觉广阔而生动。
  • 平滑过渡:当环境变得可见或隐藏时,星星和行星会无缝地淡入和淡出。

这个太空环境为火箭的冒险创造了一个身临其境的动态背景,增强了场景的整体运动感和深度感。

🏎️ 性能提升:优化流畅的火箭体验

对于像火箭场景这样的互动体验,保持一流的性能至关重要——尤其是在处理 3D 渲染和物理计算时。让我们来探索一下我们是如何挤出这些额外的帧并保持流畅的:

🚀 1. 帧循环控制:“始终” vs. “按需”

为了防止火箭不在视野范围内时不必要的 GPU 占用,我们在网页内容下方创建了一个触发器元素。此开关会在之间切换帧循环,确保浏览器仅在需要时重新渲染。"always""demand"

  • “始终”:当火箭可见(滚动到视图中)或动画时使用,确保流畅的性能。
  • “需求”:当火箭处于空闲状态且不在视野中时使用,这意味着场景仅在发生特定事件时重新渲染。

该技术有助于减少 GPU 压力并优化移动和笔记本电脑用户的电池使用情况。

<Canvas frameloop={isRocketVisibleOrFlying ? 'always' : 'demand'} />
Enter fullscreen mode Exit fullscreen mode

🚀 2. 自定义useViewportSize钩子

我们选择使用自定义useViewportSize()钩子来跟踪视口变化,而不会触发过多的重新渲染。虽然useThree(){ viewport/size }导致每次滚动时都重新渲染,但我们会限制视口大小检查,以避免性能下降。

const useViewportSize = () => {
  const [size, setSize] = useState(null);

  useFrame((state) => {
    if (state.clock.elapsedTime % throttleTime > 0.01) return;

    const { width, height } = state.size;
    if (!size || size.width !== width || size.height !== height) {
      setSize({ width, height });
    }
  });

  return size;
};
Enter fullscreen mode Exit fullscreen mode

🚀 3. 行星网格实例化

为了提高效率,我们使用网格实例化来处理行星的渲染。实例化允许您渲染几何体的多个副本,同时重复使用相同的材​​质,这大大减少了在场景中绘制每个行星的开销。

<Instances limit={100} material={PlanetMaterial}>
  <sphereGeometry args={[1, 32, 32]} />
  {planets}
</Instances>
Enter fullscreen mode Exit fullscreen mode

🚀 4. 使用 Refs 来获取非渲染值

在处理useFrame()循环内的动态更新时,我们将频繁变化的值(例如位置或速度)存储在Refs中。这样,我们可以直接操作它们,而无需触发组件重新渲染,从而节省 CPU 周期。

🚀 5. React-Three-Fiber 性能通用技巧

最后,我们遵循了React Three Fiber 文档中的最佳实践,以避免性能陷阱。其中包括:

  • 除非绝对必要,否则避免过度使用useFrame()
  • setState()通过在动画循环内谨慎使用来进行批量更新。
  • 尽量减少循环内的新对象创建以防止垃圾收集。

要了解更多高级性能技巧,请务必查看官方的React Three Fiber 陷阱指南性能优化技巧

💡 快速提示:某些设备上的画布尺寸限制

使用大型画布时,请注意某些设备(例如Android Chrome桌面版 Firefox)存在尺寸限制,如果画布高度超过 4096 像素,可能无法正常渲染。请务必考虑这些限制,并考虑为大视口启用动态缩放或禁用相关功能!

if (height > 4096 && browserName === 'Firefox' && isDesktop) {
  setSize(null);
  return;
}
Enter fullscreen mode Exit fullscreen mode

通过实施这些性能调整,我们确保了流畅、身临其境的体验,而不会拖慢用户的设备——无论他们使用的是移动设备还是桌面设备。🖥️📱

🚀 总结:最后的倒计时

为了庆祝领英粉丝数量突破 1000,我们制作了这款互动式 3D 火箭,真是太棒了!从构建动态鼠标控制的火箭,到添加分裂的太空岩石、闪烁的星星,以及性能优化——这个项目真是一段充满乐趣的旅程。

正是这些项目提醒着我们热爱这份事业的原因:将创造力、科技以及一点点火箭科学(好吧,是很多火箭科学)融为一体。希望您喜欢阅读我们如何将这些元素融合在一起,甚至可能从中汲取一些经验,用于您自己的交互式网络项目。

欢迎随时查看zerodays.dev上的实时火箭复活节彩蛋,当然,请留意更多互动乐趣,因为我们将继续构建很酷的东西并突破 Web 开发的可能性界限。

为下一个里程碑——或许,下一枚火箭——干杯!🚀

感谢您的阅读和关注!

文章来源:https://dev.to/zerodays/building-an-interactive-3d-rocket-easter-egg-with-react- Three-Fiber-4pc
PREV
自学程序员的时间管理
NEXT
我设计,你建造! - 前端挑战#4(Supabase 版本)