[Nuxt3] using p5.js with Typescript
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".
<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".
<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
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.
<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()".
<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.
<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