この記事ではp5.jsをReactと一緒に使えるようプロジェクトの構築方法をまとめた記事です。記事前半ではTypeScriptやPercelについても言及しているので、p5を使わない場合でも環境構築の参考にしていただけたら嬉しいです。
p5.jsとは
p5.jsとはクリエイティブコーディングで使われるJavaScriptライブラリです。以下のような表現豊かな描写が可能になります。
確かにp5はかっこいいけどp5だけで使いたいわけじゃない
公式チュートリアルのページを覗くと以下のような画像とともにコーディングの初め方が丁寧に解説されています。
上記をご覧いただくと分かるように、既にsetup()
やdraw()
と行った関数が指定されていて、そこに以下のように記述すると描画することができる仕組みとなっています。
function setup() {
}
function draw() {
ellipse(50, 50, 80, 80);
}
<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
を使ってプロジェクトをビルドしていきます。
$ yarn global add parcel-bundler
最低限であるReactとp5をプロジェクトに追加します。
$ yarn add @types/p5 @types/react @types/react-dom
ライブラリを追加したので、ここからはファイルの中身を作成していきましょう。
ここで必要になるのは骨格となるファイル3つとTypeScript用の設定ファイル1つで計4つのみです。どれもシンプルな最小限の記述にしています。
<html>
<head>
</head>
<body>
<div id="root"></div>
<script src="./index.tsx"></script>
</body>
</html>
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)
import React from 'react'
const App = () => {
return (
<p>Hello World!</p>
)
}
export default App
{
"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": [
"**/*",
]
}
ここまででプロジェクトは以下のようになっているはずです。
それでは実行してみましょう。以下のコマンドをプロジェクトのルートから打ち込むと画面(http://localhost:1234
)に文字が表示されるはずです。
$ parcel ./index.html
2. p5.js用キャンバスの追加
ここからp5を使って単純なサークルを画面に描いていきます。公式チュートリアルにある関数ellipse
を使って以下のファイルを追加しましょう。
import p5 from 'p5'
const circle = (p: p5) => {
p.setup = () => {
}
p.draw = () => {
p.ellipse(50, 50, 80, 80)
}
}
export default circle
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
等一部のディレクトリは自動的に追加されているものですので手動での操作は不要です。
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
以下のように画面が更新されていることが確認できるかと思います。
3. Headerの追加
画面にヘッダーを追加して構成を整えます。以下のファイルを追加してください。
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
に追加します。
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
せっかくなので描画もインタラクティブなものへアップデートしましょう。
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を組み込んできました。
マウスを画面上で動かしてみましょう。以下のような描画が可能になります。
おまけ:3Dグラフィックスの追加
p5.jsを使った3D描画を行うため、以下のファイルを追加して試してみましょう。
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つ目の球体は他に比べて手前に表示されているのが確認できます。
次に、フレームごとにX軸を中心とした回転を加えてみましょう。
...
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軸に対して回転していることがわかります。
続いてsinとcosを使用した運動の描画です。translate
で指定した定数を以下のように書きかえてみます。
...
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によって再現できることがわかります。
最後に、ここまでの要素を組み合わせていきましょう。X軸回転に加えてY軸Z軸にも回転を加えていきます。さらに、よりインパクトのある表現となるようオブジェクトの個数を80まで増やしていきます。色も白から艶やかな色へと変更してみます。
...
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
...
さらなる拡張
今回のチュートリアルではプロジェクトの骨格となるファイルを作成していきました。
ヘッダーを追加した要領でサイドバーやポップアップを追加することも可能です。また、sketch
ディレクトリの下にcircle.ts
以外のファイル(例えばtriangle.ts
など)を作成し、画面の描画を動的に変えることも可能です。以下にソースコードを置いておくのでぜひ独自の拡張を加えてみてください。