发布于 2026-01-05 8 阅读
0

将 P5.js 与 React 集成

将 P5.js 与 React 集成

在 Flatiron School 软件工程专业的毕业设计项目中,我非常渴望突破自己的舒适区,挑战更高难度的技能。我们只有三周时间用 Rails 和 React 开发一个全栈应用,所以我一直在寻找一个能够结合过去 15 周所学知识,并且是我真正想尝试的项目。我之前试用过 P5.js,一个简洁流畅的浏览器动画库(参见之前的博文),也看过一些开发者制作的非常酷炫的音频可视化工具

幸好,通过一番网络搜索,我最终决定将 p5.js 和 WebSocket 集成起来,使用户能够在同一画布上协作创建音频可视化效果。您可以在我的个人网站上阅读相关内容(不久后即可看到已部署的项目) 。

不过,就本文而言(另见一篇博文),我想简单谈谈我是如何协调各种不同的技术,使我的项目成为可能的。

把所有图书馆都给我

过去我参与的项目都是在一周内构思、规划并完成的,因此我习惯于依赖库来完成大部分繁重的工作。所以,为了让 P5.js 和用于 WebSocket 的 ActionCable 协同工作,我的第一反应是寻找一个能将两者与我的 React 前端集成起来的开源库。虽然项目确实能带来诸多便利,让工作轻松许多,但我很快意识到,最好的选择是真正学习技术本身,而不是把它当作一个黑盒子。

以 P5 为例,已经有一些开发者编写了不错的库,可以让你的草图与 React 更好地协同工作,例如P5​​-ReactP5-React-Wrapper。这些库对于非常浅层的实现来说非常实用,比如添加一些带有动画效果的精美背景图,或者其他一些美化网站的功能。

然而,你并不了解这些预编写组件的底层工作原理,因此在 P5 中任何更复杂的操作都可能导致代码崩溃或出现一些令人费解的 bug。就我而言,我需要让程序响应通过 WebSocket 传入的数据,并实时修改程序。本质上,我需要在 P5.js 程序中嵌入一个 WebSocket 连接线。我假设你已经对 P5 的程序编写方式有了基本的了解,但我想表达的重点是,库的功能有限,无法满足你正在进行的项目。我遇到的问题非常特殊,但它需要你完全控制 React、WebSocket 和 P5 程序。总而言之,我了解到,当时间紧迫时,使用别人预先编写的库、组件或其他任何东西可能是一个好主意。但如果时间允许,或者你的问题需要你完全控制你的项目,那么你就必须走更深入的路,学习你正在做的事情。

现在来说说具体操作方法。

移除上述组件后,您只需将 P5 创建一个 Node 包依赖项,在终端中运行以下命令:

npm i p5
Enter fullscreen mode Exit fullscreen mode

同时位于 React 项目的相关目录中。

这让我们能够使用 p5 的所有强大功能。接下来,p5 可以创建为全局变量或实例。区别在于,我们是否希望 p5 在 Node 环境中运行,还是直接与 DOM 或window 对象交互。考虑到 React 有自己的虚拟 DOM,并且为了让 p5 与之兼容,我们建议使用实例模式。

本质上,我们只是创建了一个 P5 对象或实例,其中包含了其中声明的所有变量,这样就不会稀释全局命名空间。代码如下:

const s = ( sketch ) => {

  let x = 100;
  let y = 100;

  sketch.setup = () => {
    sketch.createCanvas(200, 200);
  };

  sketch.draw = () => {
    sketch.background(0);
    sketch.fill(255);
    sketch.rect(x,y,50,50);
  };
};

let myp5 = new p5(s);
Enter fullscreen mode Exit fullscreen mode

这段代码来自processing/p5.js 的 GitHub 项目。关于实例模式的更多细节,请参阅此链接。简而言之,我们将草图封装在一个函数中,该函数接收一个 P5 草图对象作为参数,该对象可以访问我们想要的所有 P5 函数(例如 setup、draw、background 等)。这意味着,任何我们想要使用的 P5 内容都必须按照 [草图名称].[我们需要使用的 P5 函数名称] 的模式访问。除此之外的任何内容,例如变量或其他内容,都不会改变。

这意味着 P5 直接操作 DOM 和 P5 在实例模式下之间没有区别,我们只是需要在草图对象中声明函数或 P5 变量,这很麻烦。

现在开始 React

在将草图集成到所需组件的方式上,你有一定的灵活性。我需要创建一个画布,用户可以在上面添加、编辑或删除形状,所以我的草图放在了一个名为“画布”的组件中。看起来是这样的:

class App extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }

  Sketch = (p) => {

     p.setup = () => {
     ...
     }

     p.draw = () => {
     ...
     }
  }
...
Enter fullscreen mode Exit fullscreen mode

为了更清晰地展示我使用 React 实现此功能的方法,我简化了草图中的操作,只保留了组件内部语法和 P5 函数的工作原理。但 setup 和 draw 函数的工作方式与常规 P5 完全相同。命名方式与上面显示的实例对象不同,但名称本身并不重要。“s”已改为“Sketch”,P5 草图对象已命名为“p”,这样我就不必反复输入同一个长单词来引用 P5 函数和变量了。

最后,为了让我们的实例 P5 附加到 React 的 DOM 中,我们需要为其指定一个引用节点作为其父节点。幸运的是,React 已经提供了实现此功能的能力,正如我们所看到的。

this.myRef = React.createRef()
Enter fullscreen mode Exit fullscreen mode

在组件的构造函数中,我们将使用这个引用节点来附加 P5 草图所需的任何内容。我在 componentDidMount 生命周期方法中声明了新 P5 对象和引用节点的创建,以便挂载操作能够触发 P5 草图的附加。总而言之,代码如下所示:

class App extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }

  Sketch = (p) => {

     p.setup = () => {
     ...
     }

     p.draw = () => {
     ...
     }
  }

  componentDidMount() {
    this.myP5 = new p5(this.Sketch, this.myRef.current)
  }

  render() {
    return (
      <div ref={this.myRef}>

      </div>
    )
  }
}

Enter fullscreen mode Exit fullscreen mode

瞧!我们在构造函数中创建了一个名为“this.myRef”的引用节点。我们将草图保存在一个接收 P5 草图对象的函数中。我们声明创建一个新的 P5 对象,并将我们的函数和引用节点(第二个参数保留给这样的节点实例)传递给它。然后在渲染函数中,我们使用一个 div 元素,它就是我们在构造函数中声明的引用节点。瞧,P5 草图就附加到了渲染函数中的 div 元素上,P5 也完全集成到了 React 中。无需任何预制组件。这使得我们可以在 React 中完全控制草图。这一切都得益于 P5 的实例模式以及在 React 中创建引用节点。

你可以在我的 GitHub 上看到我的具体实现,我将在以后的博客文章中详细介绍我是如何使用 ActionCable 在我的草图中挂载 WebSocket 连接的。

文章来源:https://dev.to/christiankastner/integrating-p5-js-with-react-i0d