😊

[Nuxt3] using p5.js with Typescript

2024/07/19に公開

Recently, I wrote about the example of using p5.js on Nuxt3.

But this example didn't work well with typescript.
Because, typescript didn't recognize the library imported by the "Dynamic imports".
Now I solve this problem by using the <ClientOnly> component.

[Environment]
node: v20.15.1
nuxt: 3.12.3
os: windows11


I assume that you know the basic usage of Nuxt3 and p5.js.
If you're not, check my recent post.

In this example, we create a vue file(drawing2_updated.vue) in the pages directory, and a vue file(Canvas_updated.vue) in the components directory.

components\Canvas_updated.vue
pages\drawing2_updated.vue

The drawing2_updated.vue calls the component(Canvas_updated.vue) within the "<ClientOnly>" tag.

That makes us be able to use p5.js with typescript in the component(Canvas_updated.vue)


note: updated at 2024/7/21

I noticed that this example(Canvas_updated.vue) has a problem.
It seems that "getElementById("canvas")" didn't work.

Then,I solved this problem. And I wrote about the solution at the last part of this post, "3. modify component(Canvas_updated.vue) for Bug Fix"


1. create components

Let's create "Canvas_updated.vue" into the "components directory".

components\Canvas_updated.vue
<template>
    <div id="canvas"></div>
</template>

<script lang="ts">
import P5 from "p5";

const script = (p5: P5) => {
    // These are your typical setup() and draw() methods
    p5.setup = () => {
        p5.createCanvas(window.innerWidth, window.innerHeight);
    };
    p5.draw = () => {
        p5.clear();
        p5.background("aqua");
        p5.ellipse(p5.mouseX, p5.mouseY, 40, 40);

        //when mouse button is pressed, circles turn black
        if (p5.mouseIsPressed === true) {
            p5.fill(0);
        } else {
            p5.fill(255);
        }
    };
}
// Attach the canvas to the div
const p5canvas = new P5(script, document.getElementById("canvas") ?? undefined);

</script>

In this file, you can write the "p5.js" with Typescript.

But you can't call this component directly from "pages file".
If you did, you might be able to see the page through "Nuxt dev tool's Link",
but when you reload the page, "500 (Internal Server Error)" will happen.

2. create page

Let's create "drawing2_updated.vue" into the "pages directory".

pages\drawing2_updated.vue
<template>
    <div id="app">
        <ClientOnly fallback-tag="span" fallback="Loading comments...">
            <Canvas_updated />
        </ClientOnly>
    </div>
</template>

Then you can access to "http://localhost:3000/drawing2_updated".

In this example, I didn't care about the attribute-setting in the "ClientOnly" tag.
(attribute: fallback-tag and fallback)
If you are interested in it, see this manual below.


Note:
I also found this manual about "server-side and client-side rendering".
I think that this might be useful later.
Hybrid Rendering

Hybrid rendering allows different caching rules per route using Route Rules

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Homepage pre-rendered at build time
    '/': { prerender: true },
    // Products page generated on demand, revalidates in background, cached until API response changes
    '/products': { swr: true },
    // Product page generated on demand, revalidates in background, cached for 1 hour (3600 seconds)
    '/products/**': { swr: 3600 },
    // Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds)
    '/blog': { isr: 3600 },
    // Blog post page generated on demand once until next deployment, cached on CDN
    '/blog/**': { isr: true },
    // Admin dashboard renders only on client-side
    '/admin/**': { ssr: false },
    // Add cors headers on API routes
    '/api/**': { cors: true },
    // Redirects legacy urls
    '/old-page': { redirect: '/new-page' }
  }
})

3. modify component(Canvas_updated.vue) for Bug Fix

When I drew the canvas of "p5.js" in the <script> tag, I noticed it didn't work well.
To see details, I modified the <template> part in the component(Canvas_updated.vue) , like this.

components\Canvas_updated.vue
<template>
    <p>components\Canvas_updated.vue START</p>
    <div id="canvas"></div>
    <p>components\Canvas_updated.vue END</p>
</template>

<script lang="ts">
import P5 from "p5";
...
new P5(script, document.getElementById("canvas") ?? undefined);
</script>

It seems that "getElementById("canvas")" didn't work,
and the canvas of "p5.js" was rendered at the bottom of HTML.

I looked for the solution, and I found that we need to use the "onMounted()" function.

I'm not really sure that why we need "onMounted()", although I used the <ClientOnly> tag to call this component(components\Canvas_updated.vue).

Then I changed "Canvas_updated.vue" into this below.
I also added it "console.log()".

components\Canvas_updated.vue
<template>
    <p>components\Canvas_updated.vue START</p>
    <div id="canvas"></div>
    <p>components\Canvas_updated.vue END</p>
</template>


<script lang="ts">
import P5 from "p5";
console.log('<< <script> START >>');

const script = (p5: P5) => {
    // These are your typical setup() and draw() methods
    p5.setup = () => {
        p5.createCanvas(300, 200);
    };
    p5.draw = () => {
        p5.clear();
        p5.background("blue");
        p5.ellipse(p5.mouseX, p5.mouseY, 40, 40);

        //when mouse button is pressed, circles turn black
        if (p5.mouseIsPressed === true) {
            p5.fill(0);
        } else {
            p5.fill(255);
        }
    };
}
</script>


<script setup lang="ts">
console.log('<< <script setup> START >>');

onMounted(() => {
    console.log('<< onMounted() START >>');

    new P5(script, document.getElementById("canvas") ?? undefined);
})
</script>

Now it works well.

Then I checked "console log". we can see like this.
This means that

  • <script> part was executed at first
  • <script setup> part was executed at second
  • "onMounted()" function was executed at last

I'm not really sure why this executed-order.

But I noticed that
When we have imported the "p5.js" library in the <script> part, we don't need to import that library in the <script setup> part.

components\Canvas_updated.vue
<script lang="ts">
import P5 from "p5";
...
</script>


<script setup lang="ts">
// I didn't import "p5.js" in this part.
...
onMounted(() => {
...
    new P5(script, document.getElementById("canvas") ?? undefined);
})
</script>

Discussion