🌸

NextとReactでp5jsを使ってみる

2022/05/20に公開1

はじめに

前々からp5jsを使ってみたいと思っていましたが、なかなか手が出せないでいるうちに1年くらい経ってしまいました。
p5jsで調べてみるとjs単体で使用する方法は多くヒットしますが、webアプリケーションに組み込んだ技術記事は多くありません。
なので今回はNext上でp5jsを使用して表示させる方法について記載します。
(Next上でReactコンポーネントとしてp5jsを利用できれば、より表現の幅が広がりそうですしね。)

最終的にできること

p5jsのExampleに書かれているコードをNext上で動かすことを目標にします。
Nextの環境構築については省略しますが、create-next-appで作成すれば、ほぼ同様な環境が出来上がります。
最終的には以下のような表示ができることを目指します。

そもそもp5jsとは

これ系の記事で散々説明されてるので簡単に説明します。
必要なければスキップしてください。

p5jsの元になったProcessingというJavaベースのプログラミング言語があります。
Processingは「プログラミングによる視覚的な表現」ができるように2001年にMITの学生が開発しました。
そのProcessingをJavaScript上で動かすことのできるライブラリがp5jsです。
https://p5js.org/

文章で説明するよりも以下のサイトで実際に触った方が体感でどのようなことができるかを理解できます。
公式にもExampleはありますが、こちらのサイトの方がジェネレーティブアートにフォーカスした使い方をしておりみていて楽しいです。
http://www.bnn.co.jp/support/generativedesign_p5js/

Nextで使ってみる

ここから実際にNext上で使っていきます。
基本的にはこちらの記事を参考にさせていただきました。
https://qiita.com/touyoubuntu/items/f1051ad66520a13274a1

手順

  • p5jsのインストール
  • Nextで使用する際のワンポイント
  • 雛形のコンポーネント作成
  • p5jsのサンプルを表示させる
  • 完成

p5jsのインストール

まずはp5jsをインストールします。
Reactでコンポーネント的に使用したいため、react-p5をインストールします。

yarn add react-p5

Nextで使用する際のワンポイント

参考元の記事にも記述されていますが、このままp5jsを使用すると window is not defined エラーが発生します。
p5js内部で window を使用していますが、windowはクライアント側でしか実行できないことが原因です。
Nextのnext/dynamicでssrをfalseに設定することでエラーを回避できます。

index.tsx
import dynamic from 'next/dynamic'
import p5Types from 'p5'

const Sketch = dynamic(import('react-p5'), {
  loading: () => <></>,
  ssr: false
})

これでp5jsが使えるようになりました。

雛形のコンポーネント作成

こちらも元記事に記載されているんですが、コンポーネントの雛形を最初に作成することで、p5jsのどの関数をどこに記載するかを明確にできます。

index.tsx
export const SketchComponent = () => {
  const preload = (p5: p5Types) => {
    // 画像などのロードを行う
  };

  const setup = (p5: p5Types, canvasParentRef: Element) => {
    p5.createCanvas(p5.windowWidth, p5.windowHeight).parent(canvasParentRef);
    // p5でいうsetupの処理を書く
  };

  const draw = (p5: p5Types) => {
    // p5でいうdrawの処理を書く
  };

  const windowResized = (p5: p5Types) => {
    // コンポーネントのレスポンシブ化
    p5.resizeCanvas(p5.windowWidth, p5.windowHeight);
  };

  return (
    <Sketch
      preload={preload}
      setup={setup}
      draw={draw}
      windowResized={windowResized}
    />
  );
};

export default SketchComponent;

これで準備が整いました。
ここからはp5jsのサンプルを表示させます。

p5jsのサンプルを表示させる

今回はこちらのサンプルを表示させます。
https://p5js.org/examples/color-saturation.html

上記のexampleサイトにはコードが記載されています。

const barWidth = 20;
let lastBar = -1;

function setup() {
  createCanvas(720, 400);
  colorMode(HSB, width, height, 100);
  noStroke();
}

function draw() {
  let whichBar = mouseX / barWidth;
  if (whichBar !== lastBar) {
    let barX = whichBar * barWidth;
    fill(barX, mouseY, 66);
    rect(barX, 0, barWidth, height);
    lastBar = whichBar;
  }
}

これを先ほどの雛形コンポーネントで解釈すると以下のようになります。

index.tsx
import dynamic from "next/dynamic";
import p5Types from "p5";

const Sketch = dynamic(import("react-p5"), {
  loading: () => <></>,
  ssr: false,
});

export const SketchComponent = () => {
  const preload = (p5: p5Types) => {
    // ここは今回使わない
  };

  const setup = (p5: p5Types, canvasParentRef: Element) => {
    p5.createCanvas(p5.windowWidth, p5.windowHeight).parent(canvasParentRef);
    p5.colorMode(p5.HSB, p5.width, p5.height, 100);
    p5.noStroke();
  };

  const barWidth = 20;
  let lastBar = -1;

  const draw = (p5: p5Types) => {
    let whichBar = p5.mouseX / barWidth;
    if (whichBar !== lastBar) {
      let barX = whichBar * barWidth;
      p5.fill(barX, p5.mouseY, 66);
      p5.rect(barX, 0, barWidth, p5.height);
      lastBar = whichBar;
    }
  };

  const windowResized = (p5: p5Types) => {
    p5.resizeCanvas(p5.windowWidth, p5.windowHeight);
  };

  return (
    <Sketch
      preload={preload}
      setup={setup}
      draw={draw}
      windowResized={windowResized}
    />
  );
};

export default SketchComponent;

これで記述は完了です。
実際に画面を見てみましょう。

完成

できました。
同様にp5jsのExampleのコードをコンポーネント側で解釈することで、p5jsをNext上で動かせそうです。

余談

p5jsを使用したようなアート作品は一般的にジェネレーティブアートと言われており、その界隈の活動は活発です。

PCD TOKYO が開催されていたり、Twitterでは つぶやきprocessing というハッシュタグで各々が作品を公開しています。

ジェネレーティブアートはNFTとの相性も良く、NEORTGenerativeMasksなどの日本発信のサイトもあります。

この界隈の書籍を執筆されている田所さんのサイトを辿るとprocessingなどの書籍紹介がありアツいです。

そのうち、この辺りの知識や発信者についてまとめた記事を書きたいですね。

おわりに

今回はp5jsをNext上で表示できました。
初期は環境づくりや関数の使い方が辛いイメージでしたが、コンポーネントの雛形を作ることで簡単に動作確認でき、モチベーションも上がりますね。
また、VercelでNextをデプロイできるので、自分の作品群をポートフォリオとして公開することも簡単にできそうです。

参考記事

https://qiita.com/touyoubuntu/items/f1051ad66520a13274a1
https://zenn.dev/nszknao/articles/d94060914046e7f40e07#完成形
https://p5js.org/examples/color-saturation.html
https://nimil.jp/blog/p5js-react

Discussion

yahaoyahao

まずこちらの記事とても参考になりました。ありがとうございます。

以下、問題が発生し、その後修正方法が分かったため情報の共有になります。

以下のコードで実行したところ、 nextjs の strict mode がオンの場合に SketchComponent コンポーネント内の setup 関数が二度実行され、canvas が二つ描画されるという現象が起きました。

const Sketch = dynamic(import("react-p5"), {
  loading: () => <></>,
  ssr: false,
});

react-p5 の公式で紹介されているコードに変更したところその問題は発生しませんでした。
https://www.npmjs.com/package/react-p5?activeTab=readme

// Will only import `react-p5` on client-side
const Sketch = dynamic(() => import('react-p5').then((mod) => mod.default), {
  ssr: false,
})

最低限の確認はしたのですが、こちらの環境特有の問題の可能性もあるため、お時間あれば確認して頂ければと思います。よろしくお願いします。