Accessing `p5.js` instance from Vue file

Hi everyone,

I’m currently working on an app involving Quasar framework (which works in top of Vue) and p5.js. I have a vue where I want to insert a sketch (please, have a look to the code below). Following these recommendations, I use p5.js in instance mode and created a d object for my sketch. It works fine and I have my sketch on screen.

But, when I tried to modified something in the instance d (as mentioned in the article, for example d.background(0)), it does not work. I have an error message in the console of my navigator.

Is anybody has an idea about the good way to access my sketch ?

Thanks in advance,
Sylvain


Error message

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'background')
    at _.s.default.background (p5.min.js?b591:3:1)
    at Proxy.mounted (Nombres.vue?480d:90:1)
    at callWithErrorHandling (runtime-core.esm-bundler.js?9e79:155:1)
    at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?9e79:164:1)
    at Array.hook.__weh.hook.__weh (runtime-core.esm-bundler.js?9e79:2667:1)
    at flushPostFlushCbs (runtime-core.esm-bundler.js?9e79:356:1)
    at flushJobs (runtime-core.esm-bundler.js?9e79:401:1)
s.default.background	@	p5.min.js?b591:3
mounted	@	Nombres.vue?480d:89
...

Code of the project :

<template>
  <q-page padding class="bg-primary">
    <div class="column justify-center" style="min-height: inherit">
      <div class="row justify-center">
        <div id="canvasPlace" class="col">
          <div id="p5Canvas"></div>
        </div>
      </div>
      <div class="row justify-center text-white">
        <div class="col-1">
          <q-btn flat dense round icon="ti-arrow-left" aria-label="Gauche" />
        </div>
        <div class="col-1">
          <q-btn flat dense round icon="ti-reload" aria-label="Retour" />
        </div>
        <div class="col-1">
          <q-btn flat dense round icon="ti-arrow-right" aria-label="Droite" />
        </div>
      </div>
    </div>
  </q-page>
</template>

<script>
const P5 = require("p5");
import { Nombre } from "../js/Nombre.js";

// let p5;
let nb = new Nombre();
const sketch = (s) => {
  // p5 = _p5;
  let container;
  let fontFixed,
    tailleFonte = 160,
    couleurFond = [25, 118, 210],
    xText = 0,
    canvasHeight = 200;
  s.preload = () => {
    fontFixed = s.loadFont("/fonts/manti_fixed.otf");
  };
  s.setup = () => {
    container = document.getElementById("canvasPlace");
    s.createCanvas(container.clientWidth, canvasHeight);
    // Set text characteristics
    s.textFont(fontFixed);
    s.textSize(tailleFonte);
    s.textAlign(s.CENTER, s.CENTER);
    xText = container.clientWidth / 2;
    s.noLoop();
    nb.valeur = "12,7";
  };
  s.draw = () => {
    s.background(couleurFond);
    s.fill(250);
    s.text(nb.toString(), container.clientWidth / 2, canvasHeight / 2);
  };
  s.windowResized = () => {
    s.resizeCanvas(100, canvasHeight);
    s.resizeCanvas(container.clientWidth, canvasHeight);
  };
  s.keyPressed = () => {
    console.log("keyCode" + s.keyCode);
    // p5.clear();
    switch (s.keyCode) {
      case 37: // left arrow
        xText += -10;
        break;
      case 39: // right arrow
        xText += 10;
        break;
      case 84: // 't'
        console.log(nb.valeur);
        console.log(nb.toString());
        break;
    }
    s.text(nb.toString(), xText, 250);
    s.redraw();
  };
};
let d;
export default {
  name: "Nombres",
  mounted() {
    // NOTE: Use p5 as an instance mode
    d = new P5(sketch, "p5Canvas");
    // d.background(0);
  },
};
</script>

1 Like

I haven’t used Vue/Quasar in some time so I don’t know of a proper solution, but fundamentally the problem is that you’re trying to use d before p5 has initiated. It takes a few “paint cycles” for the canvas to get created, the preload method to finish, and for all the methods like background to get added to the instance

For example, this is a bad solution but it demonstrates the point:

mounted() {
    d = new P5(sketch, "p5Canvas");
    setTimeout(() => {
      d.background(0);
    }, 1000);
  }

Maybe if you instantiate p5 in an earlier life cycle stage like beforeMount and then do your d.background() in mounted it might work.

I wish I had a better solution but hopefully this helps you get going in the right direction


Edit: Oh! Actually I’m assuming that you are doing d.background(0) inside mounted just to test if it works? Because otherwise I would think that you would just do it in the sketch callback if it needed to be done immediately

For example, if you used d.background(0) in any of the reactive methods (updated, observable change, etc) then it would totally work as d would have been instantiated by then

OK. So I need to accord p5 instance life cycle to the Vue life cycle… as a newbie in Vue, it’ll be tricky for me… :thinking:

Is there a way to know when the p5 instance is complete and ready to go? I could use async and await is so, don’t you think ?

Here’s a minimal example:

<template>
  <div id="app">
    <div id="p5Canvas"></div>
  </div>
</template>

<script>
import p5 from "p5";

const sketch = (s) => {
  s.setup = () => {
    s.createCanvas(400, 400);
    s.background(0);
  };
};

let d;
export default {
  name: "App",

  beforeMount() {
    d = new p5(sketch, "p5Canvas");
  },

  mounted() {
    d.background("red");
  },
};
</script>

Does not work for me. Black backround is successfully displayed but the red rise an error

TypeError: Cannot read properties of undefined (reading 'background')
    at _.s.default.background (p5.min.js?b591:3:1)
    at Proxy.mounted (Nombres.vue?480d:55:1)
    at callWithErrorHandling (runtime-core.esm-bundler.js?9e79:155:1)
    at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?9e79:164:1)
    at Array.hook.__weh.hook.__weh (runtime-core.esm-bundler.js?9e79:2667:1)
    at flushPostFlushCbs (runtime-core.esm-bundler.js?9e79:356:1)
    at flushJobs (runtime-core.esm-bundler.js?9e79:401:1)

However, if I put the d.background("red") in a setTimeout function, it’s working:

setTimeout(() => {
      d.background("red");
    }, 1000);