Particle Container
A SolidJS component that renders a PixiJS ParticleContainer.
Please see the PixiJS docs for detailed information.
Food icons by Darina Grant.
Code example
Section titled “Code example”import type * as Pixi from "pixi.js";import { Assets, Particle } from "pixi.js";import { onResize, onTick, ParticleContainer, PixiApplication, PixiCanvas, PixiStage } from "pixi-solid";import { createResource, onMount, Show, Suspense } from "solid-js";import assetUrl from "@/assets/food-icons/fried-egg.png";
export type ParticleContainerProps = Omit<Pixi.ParticleContainerOptions, "children"> & { particleTexture: Pixi.Texture; ref: (instance: Pixi.ParticleContainer) => void;};
export const MyParticleContainerComponent = (props: ParticleContainerProps) => { // Get a ref to the ParticleContainer let particleContainerRef: Pixi.ParticleContainer | undefined;
// Create an array to hold Pixi.Particle instances const particles: Pixi.Particle[] = []; // Create an array of random offset values for each particle const offsets: number[] = [];
// Initialize particles with base properties for (let i = 0; i < 500; i++) { particles.push( new Particle({ texture: props.particleTexture, rotation: 0, anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, }) ); offsets.push(Math.random()); }
let cumulativeTime = 0; // Track time passed to move particles smoothly const baseOrbitRadius = 150; // Average distance from the center const orbitAmplitude = 150; // How much the radius pulsates const orbitSpeed = 0.003; // Speed of the circular motion const scalePulsateSpeed = 0.02; // Speed of scale changes const baseRotationSpeed = 0.05; // Base rotation speed const minScale = 0.25; const maxScale = 1; const scaleRange = maxScale - minScale;
// Update the particles imperatively in a onTick hook onTick((ticker) => { cumulativeTime += ticker.deltaTime;
particles.forEach((particle, i) => { const indexFactor = offsets[i] * 100;
// Smooth circular motion with pulsating radius const currentRadius = baseOrbitRadius + Math.sin(cumulativeTime * orbitSpeed * (1 + indexFactor * 0.1) + indexFactor) * orbitAmplitude; const currentAngle = cumulativeTime * orbitSpeed * (1 + indexFactor * 0.05) + indexFactor * Math.PI;
particle.x = currentRadius * Math.cos(currentAngle); particle.y = currentRadius * Math.sin(currentAngle);
// Calculate pulsating scale directly within the desired range const scaleValue = minScale + scaleRange * 0.5 * (1 + Math.sin(cumulativeTime * scalePulsateSpeed * (1 + indexFactor * 0.02) + indexFactor * 0.5)); particle.scaleX = scaleValue; particle.scaleY = scaleValue;
particle.rotation += baseRotationSpeed * Math.sin(indexFactor) * ticker.deltaTime; }); });
onMount(() => { if (!particleContainerRef) return; // Add the particle to our container on mount particleContainerRef.addParticle(...particles); });
return ( <ParticleContainer // Set the dynamic properties we want to animate dynamicProperties={{ position: true, rotation: true, vertex: true, }} ref={(particleContainer) => { // Bind the ref in this component and forward the ref to our propr.ref as well particleContainerRef = particleContainer; props.ref(particleContainer); }} /> );};
export const DemoApp = () => { // Create a resource to load the sky texture const [textureResource] = createResource(() => Assets.load<Pixi.Texture>(assetUrl)); return ( <PixiApplication> <Suspense fallback={<div>Loading...</div>}> <PixiCanvas style={{ "aspect-ratio": "2/1.5" }}> {/* Show our Stage when the assets are loaded */} <Show when={textureResource()}> {(texture) => ( <PixiStage> <MyParticleContainerComponent particleTexture={texture()} ref={(particleContainer) => { onResize((screen) => { particleContainer.position.x = screen.width * 0.5; particleContainer.position.y = screen.height * 0.5; }); }} /> </PixiStage> )} </Show> </PixiCanvas> </Suspense> </PixiApplication> );};