Vue.jsでp5.jsを使ってみる

![画面収録 2018-12-09 23.24.48.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/117549/41bf5e76-0cb0-90b0-71dc-7be2f4d1c357.gif)

p5.jsをVueのフレームワークを使って描画するためにリサーチした時のメモです。p5.jsはProcessingから派生したライブラリで、ビジュアル表現やGenerative Designをする時に便利です。[OpenProcessing](https://www.openprocessing.org/)というWeb上で開発できる環境があり、プログラムを書いてすぐにビジュアルを確認できます。今回はここで書いたコードをVueを使って表示する方法を調べました。

## ライブラリをインポート
Vueのプロジェクトを作成したら、p5.jsのライブラリをインストールします。
(私はNuxtで作成しましたが、通常のvue-cliでも同じだと思います。)

```
npm install --save p5
```
これですぐにp5.jsを使うことができるのですが、p5.jsであらかじめ用意されている関数やプロパティはグローバルで展開されてしまうようで、うまく扱うことができません。そこで、p5.jsにinstance modeと呼ばれるモードで、p5.jsを単体のオブジェクトとして生成して、グローバルとの競合を防ぐことができます（[参考](https://qiita.com/turusuke/items/c92a13602523d6378cc0)）。

mountedした時に描画するような記述は以下の通りです。p5.jsの実質的な実装コードを関数内に書いていき、その関数をP5クラスに渡しています。

```javascript:mounted内
mounted() {

  const script = function (p5) {
    var speed = 2;
    var posX = 0;
    p5.setup = _ => {
      p5.createCanvas(500, 500);
      p5.ellipse(p5.width / 2, p5.height / 2, 500, 500);
    }

    p5.draw = _ => {
      p5.background(0);
      const degree = p5.frameCount * 3;
      const y = p5.sin(p5.radians(degree)) * 50;
      p5.push();
      p5.translate(0, p5.height / 2);
      p5.ellipse(posX, y, 50, 50);
      p5.pop();

      posX += speed;
      if (posX > p5.width || posX < 0) {
        speed *= -1;
      }
    }
  }

  const P5 = require('p5')
  new P5(script)
}
```

![画面収録 2018-12-09 21.47.30.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/117549/c9191873-a8d1-9a2f-9c00-6016a3576501.gif)



## 任意のCanvas内に表示
このままだとp5.jsの表示は左上に表示されてしまうので、html内の任意のCanvasの位置に表示させます[(参考)](http://p5aholic.hatenablog.com/entry/2015/05/16/163251)。

```vuejs:template
<template>
  <div class="container border">
    <div class="d-flex justify-content-center" id="p5Canvas"></div>
  </div>
</template>

```
```js:mounted内の修正箇所
// p5.createCanvas(500, 500);
var canvas = p5.createCanvas(500, 500)
canvas.parent("p5Canvas");
```

任意のCanvasにidをつけ、p5.js内で作成したCanvasの親コンテナに割り当てることで任意のCanvas内に表示できます。

<img width="963" alt="スクリーンショット 2018-12-09 21.55.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/117549/7daf8ed3-cbe6-1b67-b3bc-463927294d3a.png">

## 外部リソースにする
本末転倒かもしれませんが、基本的にp5.js部分の記述はJavaScriptなので、Vue内に書かなくても良いと思いました。そこで、p5.jsの記述部分だけ抜き出して別ファイル(Radar.js)にし、それをVue側から呼び出して使用するように変更しました。

```javascript:mounted内の実装
var radar = require('@/js/Radar.js')
const P5 = require('p5')
new P5(radar.main)
```
```javascript:Radar.js
let p5;

export function main(_p5) {
  p5 = _p5

... 以下同じ
}
```

export文でp5.js部分(main関数)にアクセスできるようにして、```new P5(radar.main)```でオブジェクトを生成する時にその関数を渡しています。

## コールバックを受け取る
もう少しVueの特徴を活かせられるように、p5.jsからコールバックを受け取って、その内容を表示させてみます。

```javascript:methodsを追加
methods: {
  callbackOnP5: function(timeStr) {
    this.message = timeStr;
  }
}
```
```html:templateに追加
<div class="message d-flex justify-content-center">
  {{message}}
</div>
```
```javascript:dataを追加
data() {
  return {
    message: ""
  }
},
```
```javascript:mounted
// NOTE: p5.jsからのコールバックを受け取る
radar.setDelegate(this.callbackOnP5);
```

```javascript:Radar.js
let delegate;
let p5;
export function main(_p5) {
 p5 = _p5
 p5.draw = _ => {
   notifyCurrentTime();
 }
}

export function setDelegate(_delegate) {
  delegate = _delegate;
}

function notifyCurrentTime() {
  if (delegate !== undefined) {
    const message = p5.hour() + ":" + p5.minute() + ":" + p5.second();

    delegate(message);
  }
}
```
callback関数をあらかじめp5.js側のソースコードに渡しておき、任意のタイミングでコールバック関数を呼び出し、データバインディングしているmessageプロパティを更新させています。

<br>
<br>
冒頭のデモは[github](https://github.com/mitsuyacider/p5VueDemo)に置いています。
元となっているOpenProcessingでのソースコードは[こちら](https://www.openprocessing.org/sketch/627975)です