Nuxt
ParticleText.js integrates smoothly with Nuxt.js, handling both Nuxt 3 and Nuxt 2 architectures. This guide covers server-side rendering (SSR) considerations and best practices for Nuxt applications.
🎬 Live Demo (Placeholder - Framework-specific demo coming soon!)
Installation
Section titled “Installation”Install the package via npm:
npm install particletext.jsOr using yarn:
yarn add particletext.jsNuxt 3
Section titled “Nuxt 3”The recommended approach for Nuxt 3 using Composition API and client-only rendering.
Basic Client-Only Component
Section titled “Basic Client-Only Component”<template> <canvas ref="canvasRef" :width="800" :height="300" style="width: 100%; height: auto;" /></template>
<script setup>import { ref, onMounted, onBeforeUnmount } from 'vue';
const canvasRef = ref(null);let particleInstance = null;
onMounted(async () => { // Dynamic import for client-side only if (process.client) { const { default: initParticleJS } = await import('particletext.js');
if (canvasRef.value) { particleInstance = initParticleJS(canvasRef.value, { text: 'NUXT.JS', colors: ['#00DC82', '#00C470', '#003C3C'], fontSize: 120, particleRadius: { xxxs: { base: 1, rand: 1 }, sm: { base: 1.5, rand: 1 }, md: { base: 2, rand: 1 }, }, }); } }});
onBeforeUnmount(() => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }});</script>Using Client-Only Wrapper
Section titled “Using Client-Only Wrapper”<template> <div> <h1>Welcome to Nuxt 3</h1> <ClientOnly> <ParticleText text="HELLO" :colors="['#00DC82', '#00C470']" /> </ClientOnly> </div></template>
<script setup>import ParticleText from '~/components/ParticleText.vue';</script>Reusable Component with Props
Section titled “Reusable Component with Props”<template> <canvas ref="canvasRef" :width="width" :height="height" style="width: 100%; height: auto;" /></template>
<script setup>import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
const props = defineProps({ text: { type: String, required: true, }, colors: { type: Array, default: () => ['#000000'], }, width: { type: Number, default: 800, }, height: { type: Number, default: 300, }, fontSize: { type: Number, default: undefined, }, config: { type: Object, default: () => ({}), },});
const canvasRef = ref(null);let particleInstance = null;let initParticleJS = null;
const initParticles = () => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }
if (canvasRef.value && initParticleJS) { particleInstance = initParticleJS(canvasRef.value, { text: props.text, colors: props.colors, fontSize: props.fontSize, ...props.config, }); }};
onMounted(async () => { if (process.client) { const module = await import('particletext.js'); initParticleJS = module.default; initParticles(); }});
watch( () => [props.text, props.colors, props.fontSize], () => { if (process.client) { initParticles(); } }, { deep: true });
onBeforeUnmount(() => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }});</script>Creating a Nuxt Plugin
Section titled “Creating a Nuxt Plugin”export default defineNuxtPlugin(async () => { const { default: initParticleJS } = await import('particletext.js');
return { provide: { particleText: initParticleJS, }, };});Usage with plugin:
<template> <canvas ref="canvasRef" :width="800" :height="300" /></template>
<script setup>import { ref, onMounted, onBeforeUnmount } from 'vue';
const { $particleText } = useNuxtApp();const canvasRef = ref(null);let particleInstance = null;
onMounted(() => { if (canvasRef.value && $particleText) { particleInstance = $particleText(canvasRef.value, { text: 'NUXT PLUGIN', colors: ['#00DC82', '#00C470'], }); }});
onBeforeUnmount(() => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }});</script>Nuxt 2
Section titled “Nuxt 2”For Nuxt 2 projects:
Basic Usage
Section titled “Basic Usage”<template> <canvas ref="canvas" :width="800" :height="300" style="width: 100%; height: auto;" /></template>
<script>export default { name: 'ParticleText', data() { return { particleInstance: null, }; }, mounted() { // Dynamic import for client-side only if (process.client) { import('particletext.js').then((module) => { const initParticleJS = module.default;
if (this.$refs.canvas) { this.particleInstance = initParticleJS(this.$refs.canvas, { text: 'NUXT.JS', colors: ['#00DC82', '#00C470', '#003C3C'], fontSize: 120, }); } }); } }, beforeDestroy() { if (this.particleInstance && this.particleInstance.destroy) { this.particleInstance.destroy(); } },};</script>With Props (Nuxt 2)
Section titled “With Props (Nuxt 2)”<template> <canvas ref="canvas" :width="width" :height="height" style="width: 100%; height: auto;" /></template>
<script>export default { name: 'ParticleText', props: { text: { type: String, required: true, }, colors: { type: Array, default: () => ['#000000'], }, width: { type: Number, default: 800, }, height: { type: Number, default: 300, }, fontSize: { type: Number, default: undefined, }, }, data() { return { particleInstance: null, initParticleJS: null, }; }, watch: { text() { this.initParticles(); }, colors: { handler() { this.initParticles(); }, deep: true, }, }, mounted() { if (process.client) { import('particletext.js').then((module) => { this.initParticleJS = module.default; this.initParticles(); }); } }, methods: { initParticles() { if (this.particleInstance && this.particleInstance.destroy) { this.particleInstance.destroy(); }
if (this.$refs.canvas && this.initParticleJS) { this.particleInstance = this.initParticleJS(this.$refs.canvas, { text: this.text, colors: this.colors, fontSize: this.fontSize, }); } }, }, beforeDestroy() { if (this.particleInstance && this.particleInstance.destroy) { this.particleInstance.destroy(); } },};</script>Client-Only Component (Nuxt 2)
Section titled “Client-Only Component (Nuxt 2)”<template> <div> <h1>Welcome to Nuxt</h1> <client-only> <particle-text text="HELLO" :colors="['#00DC82', '#00C470']" /> </client-only> </div></template>
<script>export default { components: { ParticleText: () => import('~/components/ParticleText.vue'), },};</script>TypeScript Support (Nuxt 3)
Section titled “TypeScript Support (Nuxt 3)”<template> <canvas ref="canvasRef" :width="width" :height="height" style="width: 100%; height: auto;" /></template>
<script setup lang="ts">import { ref, onMounted, onBeforeUnmount, watch, type Ref } from 'vue';
interface ParticleInstance { destroy: () => void; startAnimation: () => void; isAnimating: boolean; particleList: any[];}
interface Props { text: string; colors?: string[]; width?: number; height?: number; fontSize?: number; config?: Record<string, any>;}
const props = withDefaults(defineProps<Props>(), { colors: () => ['#000000'], width: 800, height: 300, config: () => ({}),});
const canvasRef: Ref<HTMLCanvasElement | null> = ref(null);let particleInstance: ParticleInstance | null = null;let initParticleJS: any = null;
const initParticles = () => { if (particleInstance?.destroy) { particleInstance.destroy(); }
if (canvasRef.value && initParticleJS) { particleInstance = initParticleJS(canvasRef.value, { text: props.text, colors: props.colors, fontSize: props.fontSize, ...props.config, }) as ParticleInstance; }};
onMounted(async () => { if (process.client) { const module = await import('particletext.js'); initParticleJS = module.default; initParticles(); }});
watch( () => [props.text, props.colors, props.fontSize], () => { if (process.client) { initParticles(); } }, { deep: true });
onBeforeUnmount(() => { if (particleInstance?.destroy) { particleInstance.destroy(); }});</script>Common Patterns
Section titled “Common Patterns”Responsive Canvas
Section titled “Responsive Canvas”<template> <div ref="containerRef" style="width: 100%;"> <canvas ref="canvasRef" :width="dimensions.width" :height="dimensions.height" style="width: 100%; height: auto;" /> </div></template>
<script setup>import { ref, reactive, onMounted, onBeforeUnmount, watch } from 'vue';
const canvasRef = ref(null);const containerRef = ref(null);const dimensions = reactive({ width: 800, height: 300 });let particleInstance = null;let initParticleJS = null;
const updateDimensions = () => { if (containerRef.value) { const width = containerRef.value.offsetWidth; dimensions.width = width; dimensions.height = Math.floor(width * 0.375); // 16:6 aspect ratio }};
const initParticles = () => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }
if (canvasRef.value && initParticleJS) { particleInstance = initParticleJS(canvasRef.value, { text: 'RESPONSIVE', colors: ['#00DC82'], }); }};
onMounted(async () => { if (process.client) { const module = await import('particletext.js'); initParticleJS = module.default;
updateDimensions(); initParticles(); window.addEventListener('resize', updateDimensions); }});
watch( () => [dimensions.width, dimensions.height], () => { initParticles(); });
onBeforeUnmount(() => { if (process.client) { window.removeEventListener('resize', updateDimensions); } if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }});</script>With Nuxt Composables
Section titled “With Nuxt Composables”Create a reusable composable:
export const useParticleText = (config = {}) => { const canvasRef = ref(null); const particleInstance = ref(null); const isAnimating = ref(false); let initParticleJS = null;
const init = async (customConfig = {}) => { if (process.client) { if (!initParticleJS) { const module = await import('particletext.js'); initParticleJS = module.default; }
if (particleInstance.value && particleInstance.value.destroy) { particleInstance.value.destroy(); }
if (canvasRef.value) { particleInstance.value = initParticleJS(canvasRef.value, { ...config, ...customConfig, }); isAnimating.value = particleInstance.value.isAnimating; } } };
const startAnimation = () => { if (particleInstance.value && particleInstance.value.startAnimation) { particleInstance.value.startAnimation(); isAnimating.value = true; } };
const destroy = () => { if (particleInstance.value && particleInstance.value.destroy) { particleInstance.value.destroy(); isAnimating.value = false; } };
onMounted(() => { init(); });
onBeforeUnmount(() => { destroy(); });
return { canvasRef, particleInstance, isAnimating, init, startAnimation, destroy, };};Usage:
<template> <canvas ref="canvasRef" :width="800" :height="300" /></template>
<script setup>const { canvasRef } = useParticleText({ text: 'COMPOSABLE', colors: ['#00DC82', '#00C470'],});</script>Multiple Instances
Section titled “Multiple Instances”<template> <div> <canvas ref="canvas1" :width="800" :height="200" /> <canvas ref="canvas2" :width="800" :height="200" /> </div></template>
<script setup>import { ref, onMounted, onBeforeUnmount } from 'vue';
const canvas1 = ref(null);const canvas2 = ref(null);let particle1 = null;let particle2 = null;
onMounted(async () => { if (process.client) { const { default: initParticleJS } = await import('particletext.js');
if (canvas1.value) { particle1 = initParticleJS(canvas1.value, { text: 'FIRST', colors: ['#00DC82'], trackCursorOnlyInsideCanvas: true, }); }
if (canvas2.value) { particle2 = initParticleJS(canvas2.value, { text: 'SECOND', colors: ['#00C470'], trackCursorOnlyInsideCanvas: true, }); } }});
onBeforeUnmount(() => { if (particle1) particle1.destroy(); if (particle2) particle2.destroy();});</script>With Vuex Store (Nuxt 2)
Section titled “With Vuex Store (Nuxt 2)”export const state = () => ({ text: 'HELLO', colors: ['#00DC82', '#00C470'],});
export const mutations = { setText(state, text) { state.text = text; }, setColors(state, colors) { state.colors = colors; },};<template> <canvas ref="canvas" :width="800" :height="300" /></template>
<script>import { mapState } from 'vuex';
export default { data() { return { particleInstance: null, initParticleJS: null, }; }, computed: { ...mapState('particle', ['text', 'colors']), }, watch: { text() { this.initParticles(); }, colors: { handler() { this.initParticles(); }, deep: true, }, }, mounted() { if (process.client) { import('particletext.js').then((module) => { this.initParticleJS = module.default; this.initParticles(); }); } }, methods: { initParticles() { if (this.particleInstance && this.particleInstance.destroy) { this.particleInstance.destroy(); }
if (this.$refs.canvas && this.initParticleJS) { this.particleInstance = this.initParticleJS(this.$refs.canvas, { text: this.text, colors: this.colors, }); } }, }, beforeDestroy() { if (this.particleInstance && this.particleInstance.destroy) { this.particleInstance.destroy(); } },};</script>With Pinia Store (Nuxt 3)
Section titled “With Pinia Store (Nuxt 3)”import { defineStore } from 'pinia';
export const useParticleStore = defineStore('particle', { state: () => ({ text: 'HELLO', colors: ['#00DC82', '#00C470'], }), actions: { setText(text) { this.text = text; }, setColors(colors) { this.colors = colors; }, },});<template> <canvas ref="canvasRef" :width="800" :height="300" /></template>
<script setup>import { ref, onMounted, onBeforeUnmount, watch } from 'vue';import { useParticleStore } from '~/stores/particle';
const store = useParticleStore();const canvasRef = ref(null);let particleInstance = null;let initParticleJS = null;
const initParticles = () => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }
if (canvasRef.value && initParticleJS) { particleInstance = initParticleJS(canvasRef.value, { text: store.text, colors: store.colors, }); }};
onMounted(async () => { if (process.client) { const module = await import('particletext.js'); initParticleJS = module.default; initParticles(); }});
watch( () => [store.text, store.colors], () => { if (process.client) { initParticles(); } }, { deep: true });
onBeforeUnmount(() => { if (particleInstance && particleInstance.destroy) { particleInstance.destroy(); }});</script>Best Practices
Section titled “Best Practices”- Client-Only Rendering: Always use
process.clientchecks or<ClientOnly>wrapper - Dynamic Imports: Use dynamic imports to prevent SSR issues
- Cleanup: Always call
destroy()inonBeforeUnmountorbeforeDestroy - Plugins: Consider creating a Nuxt plugin for global access
- Composables: Use composables for reusable logic (Nuxt 3)
- Multiple Instances: Set
trackCursorOnlyInsideCanvas: truefor multiple canvases - Type Safety: Use TypeScript for better developer experience
Common Issues
Section titled “Common Issues”Window is not defined error
Section titled “Window is not defined error”This occurs during SSR. Solutions:
- Use
process.clientchecks - Use
<ClientOnly>or<client-only>wrapper - Use dynamic imports in
mountedoronMounted
Canvas appears blurry
Section titled “Canvas appears blurry”Set :width and :height as attributes using Vue bindings, not CSS.
Animation doesn’t reinitialize on navigation
Section titled “Animation doesn’t reinitialize on navigation”Use route watchers or page lifecycle hooks to reinitialize the animation.
Memory leaks between page transitions
Section titled “Memory leaks between page transitions”Always call destroy() in the appropriate cleanup lifecycle hook.
Configuration
Section titled “Configuration”Nuxt 3 Config
Section titled “Nuxt 3 Config”export default defineNuxtConfig({ // If you need to transpile the library build: { transpile: ['particletext.js'], },});Nuxt 2 Config
Section titled “Nuxt 2 Config”export default { // If you need to transpile the library build: { transpile: ['particletext.js'], },};Next Steps
Section titled “Next Steps”- Explore configuration options
- See interactive examples
- Learn about performance optimization
- Check out Vue integration for more patterns