You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Codepen指定链接的JavaScript流体模拟导入React组件?

把流体模拟导入React组件的实现方案

嘿,我帮你把那个流体模拟整合到React组件里啦!核心思路就是把原生JS的逻辑适配React的组件生命周期,用Hooks来处理DOM引用和副作用就行,下面是完整的实现步骤和代码示例:

核心步骤拆解

  • 用useRef获取canvas元素的DOM引用,因为我们需要直接操作canvas上下文
  • 在useEffect里初始化流体模拟的核心逻辑,同时处理组件卸载时的清理工作(防止内存泄漏)
  • 把原模拟逻辑里的原生DOM事件替换成React的合成事件,更符合React的开发习惯

完整代码示例

import { useRef, useEffect } from 'react';

const FluidSimulation = () => {
  const canvasRef = useRef(null);
  const simulationRef = useRef({}); // 保存模拟相关变量,避免每次渲染重置

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // 流体模拟核心逻辑(从原示例迁移适配)
    const particles = [];
    const particleCount = 200;
    const mouse = { x: 0, y: 0, down: false };

    class Particle {
      constructor() {
        this.x = Math.random() * canvas.width;
        this.y = Math.random() * canvas.height;
        this.vx = (Math.random() - 0.5) * 2;
        this.vy = (Math.random() - 0.5) * 2;
        this.size = Math.random() * 3 + 1;
      }

      update() {
        if (mouse.down) {
          const dx = this.x - mouse.x;
          const dy = this.y - mouse.y;
          const dist = Math.sqrt(dx * dx + dy * dy);
          if (dist < 100) {
            const force = (100 - dist) / 100;
            this.vx -= dx * force * 0.05;
            this.vy -= dy * force * 0.05;
          }
        }

        this.x += this.vx;
        this.y += this.vy;

        // 边界反弹处理
        if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
        if (this.y < 0 || this.y > canvas.height) this.vy *= -1;

        // 速度衰减
        this.vx *= 0.99;
        this.vy *= 0.99;
      }

      draw() {
        ctx.fillStyle = 'rgba(0, 150, 255, 0.8)';
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
        ctx.fill();
      }
    }

    // 初始化粒子群
    for (let i = 0; i < particleCount; i++) {
      particles.push(new Particle());
    }

    // 动画循环
    let animationId;
    const animate = () => {
      ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      particles.forEach(particle => {
        particle.update();
        particle.draw();
      });

      animationId = requestAnimationFrame(animate);
    };
    animate();

    // 保存鼠标状态到ref,供事件处理函数访问
    simulationRef.current = { mouse };

    // 组件卸载时清理动画
    return () => {
      cancelAnimationFrame(animationId);
    };
  }, []);

  // React合成事件处理鼠标交互
  const handleMouseDown = (e) => {
    const { mouse } = simulationRef.current;
    mouse.x = e.clientX;
    mouse.y = e.clientY;
    mouse.down = true;
  };

  const handleMouseMove = (e) => {
    const { mouse } = simulationRef.current;
    mouse.x = e.clientX;
    mouse.y = e.clientY;
  };

  const handleMouseUp = () => {
    const { mouse } = simulationRef.current;
    mouse.down = false;
  };

  return (
    <div style={{ width: '100vw', height: '100vh', overflow: 'hidden' }}>
      <h1 style={{ position: 'absolute', top: '20px', left: '20px', margin: 0, zIndex: 10, color: '#333' }}>Click and drag!</h1>
      <canvas
        ref={canvasRef}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
        onMouseLeave={handleMouseUp}
        style={{ position: 'absolute', top: 0, left: 0 }}
      />
    </div>
  );
};

export default FluidSimulation;

关键细节说明

  • useRef的作用:canvasRef获取真实DOM节点,simulationRef保存鼠标状态等变量,避免React重新渲染时丢失这些状态
  • useEffect清理:组件卸载时停止动画帧请求,防止内存泄漏
  • 事件适配:用React合成事件替代原生addEventListener,更贴合React的事件模型
  • 窗口适配:初始化时将canvas设为窗口大小,如果需要支持窗口 resize,可以在useEffect中添加resize事件监听

内容的提问来源于stack exchange,提问作者PS1TD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 16:32:41