Help us understand the problem. What is going on with this article?

React+TypeScriptではじめるp5.jsチュートリアル

この記事ではp5.jsをReactと一緒に使えるようプロジェクトの構築方法をまとめた記事です。記事前半ではTypeScriptやPercelについても言及しているので、p5を使わない場合でも環境構築の参考にしていただけたら嬉しいです。

p5.jsとは

p5.jsとはクリエイティブコーディングで使われるJavaScriptライブラリです。以下のような表現豊かな描写が可能になります。

Untitled_ Dec 1 2019 1_04 AM - Edited.gif

確かにp5はかっこいいけどp5だけで使いたいわけじゃない

公式チュートリアルのページを覗くと以下のような画像とともにコーディングの初め方が丁寧に解説されています。

image.png

上記をご覧いただくと分かるように、既にsetup()draw()と行った関数が指定されていて、そこに以下のように記述すると描画することができる仕組みとなっています。

sketch.js
function setup() {

}

function draw() {
  ellipse(50, 50, 80, 80);
}
index.html
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/p5@0.10.2/lib/p5.js"></script>
    <script src="sketch.js"></script>
  </head>
  <body>
  </body>
</html>

ただ、通常のプロジェクトに組み込む場合、htmlやjsファイルを以下のように使うことは稀で、今だとVueやReact等のフレームワークと一緒に使用することが大半かと思います。

そこで今回はReactをベースとしてp5.jsを組み込んだ場合の構築方法をチュートリアル形式でまとめていきます。

1. プロジェクト作成

まずはプロジェクトの雛形を作るために以下のコマンドを入力します。今回はnpm initではなくyarn initを使っていきます。

$ brew install yarn
$ yarn init

また、WebpackではなくPercelを使ってプロジェクトをビルドしていきます。
- :pencil: Percelとは

$ yarn global add parcel-bundler

最低限であるReactとp5をプロジェクトに追加します。

$ yarn add @types/p5 @types/react @types/react-dom

ライブラリを追加したので、ここからはファイルの中身を作成していきましょう。
ここで必要になるのは骨格となるファイル3つとTypeScript用の設定ファイル1つで計4つのみです。どれもシンプルな最小限の記述にしています。

index.html
<html>
<head>
</head>
<body>
    <div id="root"></div>
    <script src="./index.tsx"></script>
</body>
</html>
index.tsx
import * as React from "react"
import { render } from "react-dom"
import App from './App'

const Root = () => {
    return <App/>
}
const rootElement = document.getElementById("root")
render(<Root />, rootElement)
App/index.ts
import React from 'react'

const App = () => {
    return (
        <p>Hello World!</p>
    )
}
export default App
tsconfig.json
{
    "compilerOptions": {
        "typeRoots": [
            "./node_modules/@types",
            "./@types"
        ],
        "traceResolution": false,
        "module": "esnext",
        "target": "es5",
        "lib": [
            "es2015",
            "es2017",
            "es6",
            "es7",
            "es5",
            "dom",
            "scripthost"
        ],
        "jsx": "react",
        "experimentalDecorators": true,
        "moduleResolution": "node",
        "baseUrl": "./",
        "paths": {
            "~*": ["./*"]
        },
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "esModuleInterop": true,
        "importHelpers": true,
        "strict": true,
        "sourceMap": true,
        "resolveJsonModule": true
    },
    "include": [
        "**/*",
    ]
}

ここまででプロジェクトは以下のようになっているはずです。

image.png

それでは実行してみましょう。以下のコマンドをプロジェクトのルートから打ち込むと画面(http://localhost:1234)に文字が表示されるはずです。

$ parcel ./index.html

image.png

2. p5.js用キャンバスの追加

ここからp5を使って単純なサークルを画面に描いていきます。公式チュートリアルにある関数ellipseを使って以下のファイルを追加しましょう。

sketch/circle.ts
import p5 from 'p5'

const circle = (p: p5) => {
    p.setup = () => {
    }
    p.draw = () => {
        p.ellipse(50, 50, 80, 80)
    }
}
export default circle
App/Canvas.tsx
import React, { useEffect } from "react"
import p5 from 'p5'

const Canvas = (props: any) => {
    useEffect(() => {
        new p5(props.sketch)
    }, [props.sketch])
    return (<></>)
}
export default Canvas

ここまででプロジェクト構成は以下のようになります。node_modules等一部のディレクトリは自動的に追加されているものですので手動での操作は不要です。

image.png

index.tsxは以下のように修正します。

index.tsx
import React from 'react'
import sketch from '~sketch/circle'
import Canvas from './Canvas'

const App = () => {
    return (
        <>
            <p>Hello World!</p>
            <Canvas sketch={sketch} />
        </>
    )
}
export default App

以下のように画面が更新されていることが確認できるかと思います。

Screen Shot 2019-11-28 at 18.18.59.png

3. Headerの追加

画面にヘッダーを追加して構成を整えます。以下のファイルを追加してください。

App/Header.tsx
import React from "react"
import { AppBar, Typography, Toolbar, IconButton } from '@material-ui/core'
import MenuIcon from '@material-ui/icons/Menu'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        root: {
            flexGrow: 1,
        },
        menuButton: {
            marginRight: theme.spacing(2),
        },
        title: {
            flexGrow: 1,
        },
    }),
);

const Header = () => {
    const classes = useStyles({})
    return (
        <AppBar>
            <Toolbar>
                <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
                    <MenuIcon />
                </IconButton>
                <Typography variant="h6" className={classes.title}>
                    Tutorial
                </Typography>
            </Toolbar>
        </AppBar>
    )
}

export default Header

HeaderをApp/index.tsxに追加します。

App/index.tsx
import React from 'react'
import sketch from '~sketch/circle'
import Canvas from './Canvas'
import Header from './Header'

const App = () => {
    return (
        <>
            <Header/>
            <Canvas sketch={sketch} />
        </>
    )
}
export default App

せっかくなので描画もインタラクティブなものへアップデートしましょう。

sketch/circle.ts
import p5 from 'p5'

const circle = (p: p5) => {
    p.setup = () => {
        p.createCanvas(640, 480);
    }
    p.draw = () => {
        if (p.mouseIsPressed) {
            p.fill(0);
        } else {
            p.fill(255);
        }
        p.ellipse(p.mouseX, p.mouseY, 80, 80)
    }
}

export default circle

ここまでで、ヘッダーを含めたWebアプリケーションの一部としてp5.jsを組み込んできました。

マウスを画面上で動かしてみましょう。以下のような描画が可能になります。

image.png

おまけ:3Dグラフィックスの追加

p5.jsを使った3D描画を行うため、以下のファイルを追加して試してみましょう。

sketch/sincos.ts
import p5 from 'p5'

const sincos = (p: p5) => {
    p.setup = () => {
        p.createCanvas(
            p.windowWidth,  // 画面いっぱいまで拡大
            p.windowHeight, // 画面いっぱいまで拡大
            p.WEBGL)        // WebGL
    }
    p.draw = () => {
        p.background(0)     // 背景を黒
        for (let i = 0; i < 3; i++) {
            p.translate(100, 100, 100)
            p.sphere(20, 10, 5)
        }

    }
}

export default sincos

sphereは球体を表示します。今回は3つループを回しているので3つの球体が表示されていますが、translateを使用することでx軸y軸z軸それぞれを少しずつずらしています。3つ目の球体は他に比べて手前に表示されているのが確認できます。

Screen Shot 2019-12-01 at 1.15.48.png

次に、フレームごとにX軸を中心とした回転を加えてみましょう。

sketch/sincos.ts
...
    p.draw = () => {
        p.background(0)
        for (let i = 0; i < 3; i++) {
            p.translate(100, 100, 100)
            p.sphere(20, 10, 5)
            p.rotateX(p.frameCount * 0.01) // 追加
        }

    }
...

1つ前の球体を中心とした軸をもち、X軸に対して回転していることがわかります。

Untitled_ Dec 1 2019 1_27 AM - Edited.gif

続いてsinとcosを使用した運動の描画です。translateで指定した定数を以下のように書きかえてみます。

sketch/sincos.ts
...
        p.background(0)
        for (let i = 0; i < 3; i++) {
            p.translate(
                10 * p.sin(p.frameCount * 0.1 + i),
                10 * p.cos(p.frameCount * 0.1 + i), 
                50)
            p.sphere(20, 10, 5)
        }
...

EXILEのチューチュートレインはsinとcosによって再現できることがわかります。

Untitled_ Dec 1 2019 1_35 AM - Edited.gif

最後に、ここまでの要素を組み合わせていきましょう。X軸回転に加えてY軸Z軸にも回転を加えていきます。さらに、よりインパクトのある表現となるようオブジェクトの個数を80まで増やしていきます。色も白から艶やかな色へと変更してみます。

sketch/sincos.ts
...
import p5 from 'p5'

const sincos = (p: p5) => {
    p.setup = () => {
        p.createCanvas(
            p.windowWidth,
            p.windowHeight,
            p.WEBGL)
        p.colorMode(p.HSB, 100) // 色の追加
    }
    p.draw = () => {
        p.background(0)
        for (let i = 0; i < 80; i++) {
            p.fill(50, 55, 100) // 色の追加
            p.translate(
                10 * p.sin(p.frameCount * 0.1 + i), 
                10 * p.cos(p.frameCount * 0.1 + i), 
                50)
            p.sphere(20, 10, 5)
            p.rotateX(p.frameCount * 0.01)
            p.rotateY(p.frameCount * 0.01)
            p.rotateZ(p.frameCount * 0.01)
        }
    }
}

export default sincos
...

Untitled_ Dec 1 2019 1_43 AM - Edited.gif

さらなる拡張

今回のチュートリアルではプロジェクトの骨格となるファイルを作成していきました。
ヘッダーを追加した要領でサイドバーやポップアップを追加することも可能です。また、sketchディレクトリの下にcircle.ts以外のファイル(例えばtriangle.tsなど)を作成し、画面の描画を動的に変えることも可能です。以下にソースコードを置いておくのでぜひ独自の拡張を加えてみてください。

https://github.com/shunp/p5_react

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away