Deomo Of Ai coding

This sketch creates a mesmerizing swarm of 160 glowing orbs that orbit a shifting center point, each pulsing with color and wobbling in radius. Additive blending and translucent trail overlays combine to produce a hypnotic, glowing particle field that gently follows the mouse.

🧪 Try This!

Experiment with the code by making these changes:

  1. Freeze the trails permanently — Removing the translucent overlay stops old orb positions from fading, letting trails accumulate forever into a dense painted pattern.
  2. Make every orb the same color — Removing the per-orb hueShift makes all 160 orbs share one shifting color instead of spreading across the rainbow.
  3. Supercharge the wobble — Increasing the wobble amplitude range makes orbits pulse in and out much more dramatically.
  4. Remove additive glow — Switching off ADD blend mode makes overlapping orbs look flat and opaque instead of glowing brighter where they overlap.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch fills the screen with 160 colorful orbs that swirl around a central point, each one following its own orbit radius, speed, and wobble pattern. The glowing, trail-like look comes from combining p5.js's ADD blend mode with a semi-transparent black rectangle drawn every frame, while HSB color mode makes it easy to sweep smoothly through the rainbow. Mouse movement subtly shifts the orbit center for a parallax effect, and Perlin noise adds a soft flicker to each orb's brightness.

The code is organized around a single array of orb objects, each storing its own angle, speed, size, and wobble properties, created once in initOrbs() and animated every frame inside draw(). By studying it you'll learn how object arrays drive per-particle animation, how translucent overlays create motion trails instead of erasing them, how blendMode(ADD) makes overlapping colors glow, and how lerp() can smoothly ease a value toward a moving target like the mouse.

⚙️ How It Works

  1. When the sketch loads, setup() creates a fullscreen canvas, switches to HSB color mode, and calls initOrbs() to generate 160 orb objects with randomized orbit radius, speed, size, wobble, and hue, then paints a solid black background.
  2. Every frame, draw() first checks if the animation is paused - if so it just draws a barely-visible black overlay and exits early, freezing the visuals in place.
  3. When not paused, draw() paints a very transparent black rectangle over the whole canvas instead of clearing it completely, which is what creates the soft fading trail effect behind each orb.
  4. The orbit center smoothly eases toward the mouse position using two nested lerp() calls, creating a lagging parallax feel rather than the center snapping directly to the cursor.
  5. draw() then switches to ADD blend mode and loops through every orb, updating its angle and wobble, computing its (x, y) position with cos()/sin(), calculating a shifting hue and noise-based alpha flicker, then drawing a colored circle plus a smaller bright core circle for extra glow.
  6. Pressing Space toggles the paused flag to freeze or resume the animation, and pressing R calls initOrbs() again to regenerate all 160 orbs with fresh random properties, giving an instantly new pattern.

🎓 Concepts You'll Learn

Object arrays for particle systemsHSB color modeAdditive blend mode (blendMode(ADD))Trail effects with translucent overlaysPerlin noise for organic flickerlerp() for smooth easing toward a targetPolar to Cartesian coordinate conversion (cos/sin)

📝 Code Breakdown

setup()

setup() runs once when the sketch starts, making it the right place to configure the canvas, color mode, and initial state before animation begins.

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB, 360, 100, 100, 100); // easier colorful gradients
  noStroke();
  centerX = width / 2;
  centerY = height / 2;

  initOrbs();
  background(0); // start with a clean black background
}
Line-by-line explanation (7 lines)
createCanvas(windowWidth, windowHeight);
Makes the canvas fill the entire browser window.
colorMode(HSB, 360, 100, 100, 100);
Switches color mode to Hue-Saturation-Brightness with ranges 0-360 for hue and 0-100 for saturation, brightness, and alpha, which makes rainbow color cycling much easier than RGB.
noStroke();
Disables outlines on shapes so only filled circles are drawn, keeping the orbs soft and clean.
centerX = width / 2;
Initializes the orbit center's x position to the middle of the canvas.
centerY = height / 2;
Initializes the orbit center's y position to the middle of the canvas.
initOrbs();
Calls the helper function that creates all 160 orb objects with random properties.
background(0);
Paints the canvas solid black once at the start so there's a clean base for the trail effect to build on.

draw()

draw() runs continuously at up to 60 frames per second. Here it demonstrates how a translucent overlay (instead of a solid background clear) creates trailing motion, and how blendMode(ADD) combined with HSB color makes overlapping particles glow like light rather than simply overlapping paint.

🔬 This is the core orbit math. What happens visually if you multiply o.speed by 5 here to make every orb spin much faster?

    o.angle += o.speed;
    const wobble = sin(frameCount * o.wobbleSpeed + o.phase) * o.wobbleAmp;

    const r = o.baseRadius + wobble;
    const x = centerX + cos(o.angle) * r;
    const y = centerY + sin(o.angle) * r;
function draw() {
  if (paused) {
    // Draw a very subtle overlay so paused frame still slowly fades
    fill(0, 0, 0, 2);
    rect(0, 0, width, height);
    return;
  }

  // Translucent black overlay for trails
  fill(0, 0, 0, 8);
  rect(0, 0, width, height);

  // Slightly move the center toward the mouse for parallax effect
  const targetX = lerp(width / 2, mouseX, 0.25);
  const targetY = lerp(height / 2, mouseY, 0.25);
  centerX = lerp(centerX, targetX, 0.05);
  centerY = lerp(centerY, targetY, 0.05);

  // Additive blending makes overlapping colors glow
  blendMode(ADD);

  hueOffset = (hueOffset + 0.3) % 360;

  for (let i = 0; i < orbs.length; i++) {
    const o = orbs[i];

    // Update motion
    o.angle += o.speed;
    const wobble = sin(frameCount * o.wobbleSpeed + o.phase) * o.wobbleAmp;

    const r = o.baseRadius + wobble;
    const x = centerX + cos(o.angle) * r;
    const y = centerY + sin(o.angle) * r;

    // Hue smoothly shifts across particles
    const h = (hueOffset + o.hueShift) % 360;
    const alpha = 60 + 40 * noise(i * 0.1, frameCount * 0.01); // subtle flicker

    fill(h, 80, 100, alpha);
    circle(x, y, o.size);

    // Optionally draw a faint inner core for extra glow
    fill(h, 40, 100, alpha);
    circle(x, y, o.size * 0.4);
  }

  // Reset blend mode for any future drawing
  blendMode(BLEND);
}
Line-by-line explanation (25 lines)

🔧 Subcomponents:

conditional Pause Check if (paused) { fill(0, 0, 0, 2); rect(0, 0, width, height); return; }

When paused, draws a barely-visible overlay and exits early so the animation freezes instead of updating.

calculation Parallax Center Easing centerX = lerp(centerX, targetX, 0.05);

Smoothly eases the orbit center toward a target near the mouse, creating a lagging parallax effect.

for-loop Orb Update & Draw Loop for (let i = 0; i < orbs.length; i++) { ... }

Updates each orb's angle and wobble, computes its position, and draws its glow and core circles.

if (paused) {
Checks the paused flag; if true, skips all the animation logic below.
fill(0, 0, 0, 2);
Sets an almost invisible black fill color used only while paused, so the frame fades extremely slowly instead of freezing perfectly.
rect(0, 0, width, height);
Draws a rectangle covering the whole canvas using the current fill, applying the fade overlay.
return;
Exits draw() early, skipping the rest of the function so nothing moves while paused.
fill(0, 0, 0, 8);
Sets a mostly-transparent black fill used to slightly darken the previous frame instead of erasing it completely.
rect(0, 0, width, height);
Draws that translucent black rectangle over the whole canvas, which is what produces the fading motion trails behind each orb.
const targetX = lerp(width / 2, mouseX, 0.25);
Calculates a point 25% of the way from the canvas center toward the mouse's x position - this is the immediate target, not the final center.
const targetY = lerp(height / 2, mouseY, 0.25);
Same idea for the y position, keeping the target close to the true center but nudged toward the mouse.
centerX = lerp(centerX, targetX, 0.05);
Moves the actual orbit center 5% closer to the target every frame, creating smooth, delayed follow-through rather than instant snapping.
centerY = lerp(centerY, targetY, 0.05);
Same smoothing applied to the vertical center position.
blendMode(ADD);
Switches drawing mode so overlapping colors add their brightness together, making intersections glow brighter instead of just covering each other.
hueOffset = (hueOffset + 0.3) % 360;
Increments a global hue value each frame and wraps it back to 0 after 360, slowly cycling the whole palette through the color wheel.
for (let i = 0; i < orbs.length; i++) {
Loops through every orb object in the array to update and draw it individually.
o.angle += o.speed;
Advances this orb's orbit angle by its own speed, letting some orbs move faster or in the opposite direction than others.
const wobble = sin(frameCount * o.wobbleSpeed + o.phase) * o.wobbleAmp;
Uses a sine wave based on frameCount, a per-orb speed, and a random phase offset to make the orbit radius gently pulse in and out.
const r = o.baseRadius + wobble;
Combines the orb's fixed base orbit radius with the wobble amount to get its actual current distance from the center.
const x = centerX + cos(o.angle) * r;
Converts the orbit angle and radius into an actual x pixel coordinate using cosine (polar to Cartesian conversion).
const y = centerY + sin(o.angle) * r;
Same conversion for the y coordinate using sine, placing the orb on a circle around the center.
const h = (hueOffset + o.hueShift) % 360;
Combines the globally shifting hue with this orb's fixed hue offset so different orbs show different but coordinated colors.
const alpha = 60 + 40 * noise(i * 0.1, frameCount * 0.01);
Uses Perlin noise (smooth pseudo-random values) based on the orb's index and time to make its transparency flicker gently and organically instead of randomly jumping.
fill(h, 80, 100, alpha);
Sets the fill color for the main orb body using the calculated hue, high saturation, full brightness, and flickering alpha.
circle(x, y, o.size);
Draws the main glowing circle for this orb at its computed position and size.
fill(h, 40, 100, alpha);
Sets a lower-saturation (more white/pastel) version of the same hue for a bright inner core.
circle(x, y, o.size * 0.4);
Draws a smaller, brighter circle on top of the main orb to fake an extra glowing core.
blendMode(BLEND);
Restores the default blend mode after the loop so it doesn't affect any other drawing that might happen later.

initOrbs()

initOrbs() is the 'setup' step for the particle system - it builds an array of plain JavaScript objects, one per particle, each carrying its own randomized properties. This pattern (an array of objects with individual state) is the foundation of almost every particle system in creative coding.

🔬 This picks a random spin speed and direction. What happens if you remove the direction flip so ALL orbs spin the same way, by replacing (random() < 0.5 ? 1 : -1) with just 1?

    const speed = random(0.002, 0.01) * (random() < 0.5 ? 1 : -1); // some clockwise, some counter
function initOrbs() {
  orbs = [];
  const maxR = min(width, height) * 0.5;
  const minR = maxR * 0.15;

  for (let i = 0; i < NUM_ORBS; i++) {
    const baseRadius = random(minR, maxR);
    const angle = random(TWO_PI);
    const speed = random(0.002, 0.01) * (random() < 0.5 ? 1 : -1); // some clockwise, some counter
    const size = random(8, 36);
    const wobbleAmp = random(10, 80);
    const wobbleSpeed = random(0.005, 0.03);
    const phase = random(TWO_PI);
    const hueShift = (i / NUM_ORBS) * 360; // spread hues around the wheel

    orbs.push({
      baseRadius,
      angle,
      speed,
      size,
      wobbleAmp,
      wobbleSpeed,
      phase,
      hueShift
    });
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

for-loop Orb Generation Loop for (let i = 0; i < NUM_ORBS; i++) { ... }

Creates NUM_ORBS orb objects, each with randomized orbit radius, speed, size, wobble, and a hue spread evenly around the color wheel.

orbs = [];
Resets the orbs array to empty, clearing out any previous orbs before generating new ones.
const maxR = min(width, height) * 0.5;
Calculates the largest allowed orbit radius as half of the smaller canvas dimension, so orbits fit on screen even on non-square windows.
const minR = maxR * 0.15;
Sets the smallest allowed orbit radius as a fraction of the max, keeping some orbs close to the center and others farther out.
for (let i = 0; i < NUM_ORBS; i++) {
Loops once for each of the 160 orbs to be created.
const baseRadius = random(minR, maxR);
Picks a random orbit distance from the center for this orb, between the min and max calculated above.
const angle = random(TWO_PI);
Picks a random starting angle (in radians, 0 to 2π) so orbs don't all start lined up.
const speed = random(0.002, 0.01) * (random() < 0.5 ? 1 : -1);
Picks a small random rotation speed, then randomly flips its sign so roughly half the orbs spin clockwise and half counter-clockwise.
const size = random(8, 36);
Picks a random diameter for this orb's circle, giving variety in particle size.
const wobbleAmp = random(10, 80);
Picks how far this orb's radius will pulse in and out due to the wobble effect.
const wobbleSpeed = random(0.005, 0.03);
Picks how fast this orb's wobble oscillates over time.
const phase = random(TWO_PI);
Picks a random starting offset for the wobble's sine wave so orbs don't all pulse in sync.
const hueShift = (i / NUM_ORBS) * 360;
Spreads each orb's base hue evenly around the 360-degree color wheel based on its index, so the swarm shows a full rainbow rather than one color.
orbs.push({ ... });
Bundles all these properties into a single object and adds it to the orbs array, ready to be animated in draw().

windowResized()

windowResized() is a special p5.js function that p5 automatically calls whenever the browser window is resized, letting you keep responsive sketches properly scaled and centered.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  centerX = width / 2;
  centerY = height / 2;
  initOrbs();
  background(0);
}
Line-by-line explanation (5 lines)
resizeCanvas(windowWidth, windowHeight);
A built-in p5.js callback that fires automatically whenever the browser window changes size; here it resizes the canvas to match the new window dimensions.
centerX = width / 2;
Recalculates the orbit center's x position for the new canvas size.
centerY = height / 2;
Recalculates the orbit center's y position for the new canvas size.
initOrbs();
Regenerates all orbs so their orbit radii are properly scaled to the new canvas dimensions.
background(0);
Paints the canvas black again to clear out any leftover trail artifacts from the old size.

keyPressed()

keyPressed() is a built-in p5.js event function that automatically runs once every time any key is pressed, making it easy to add keyboard-driven interactivity like pausing or resetting a sketch.

function keyPressed() {
  // Spacebar pauses / unpauses the animation
  if (key === ' ') {
    paused = !paused;
  }

  // 'R' to randomize the system
  if (key === 'r' || key === 'R') {
    initOrbs();
    background(0);
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Pause Toggle if (key === ' ') { paused = !paused; }

Flips the paused boolean between true and false whenever the spacebar is pressed.

conditional Randomize Check if (key === 'r' || key === 'R') { initOrbs(); background(0); }

Regenerates all orbs and clears the canvas when R or r is pressed, checking both cases regardless of Caps Lock or Shift.

if (key === ' ') {
Checks if the key that was just pressed is the spacebar.
paused = !paused;
Flips the paused variable to its opposite value - true becomes false and vice versa - toggling pause on and off.
if (key === 'r' || key === 'R') {
Checks if the pressed key is a lowercase or uppercase R, so it works whether or not Caps Lock is on.
initOrbs();
Rebuilds the entire orbs array with fresh random properties, instantly changing the whole pattern.
background(0);
Clears the canvas to solid black so no old trails linger from before the randomization.

📦 Key Variables

NUM_ORBS number

Constant controlling how many orb particles are created and animated.

const NUM_ORBS = 160;
orbs array

Holds all orb objects, each with its own angle, speed, size, wobble, and hue properties.

let orbs = [];
centerX number

The current x position of the orbit center, smoothly eased toward the mouse for a parallax effect.

let centerX;
centerY number

The current y position of the orbit center, smoothly eased toward the mouse for a parallax effect.

let centerY;
hueOffset number

A globally increasing value used to shift all orb hues together, creating a slow rainbow cycle over time.

let hueOffset = 0;
paused boolean

Tracks whether the animation is currently paused, toggled by the spacebar.

let paused = false;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE draw() orb loop

Every orb draws two circle() calls per frame (320 circles total for 160 orbs), and noise() is called once per orb every frame, which can get expensive on very large NUM_ORBS values or low-end devices.

💡 For very high particle counts, consider using a single p5.Graphics buffer, WEBGL points, or reducing the inner-core circle to only draw for larger orbs where it's visually noticeable.

BUG keyPressed()

keyPressed() fires for every key, including browser shortcuts like Ctrl+R, but there's no check for modifier keys, so R alone always randomizes even if the user is trying to type into a hypothetical input field elsewhere on the page.

💡 If the sketch is ever embedded alongside other UI, add a check like `if (!event.ctrlKey && !event.metaKey)` or verify no input element is focused before responding to keys.

STYLE draw()

The magic numbers 0.25 and 0.05 for the parallax easing, and 8 for the trail alpha, are hardcoded inline with no explanation of why those specific values were chosen.

💡 Pull these into named constants like PARALLAX_STRENGTH and CENTER_EASE near the top of the file so they're easy to find and tune, and add a comment explaining the intended range (0 to 1) for each.

FEATURE keyPressed() / global

There's no on-screen indication of the controls (Space to pause, R to randomize), so first-time viewers may not discover the interactivity.

💡 Draw a small fading instructional text overlay in the corner for the first few seconds, or fade it out after the first keypress.

🔄 Code Flow

Code flow showing setup, draw, initorbs, windowresized, keypressed

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> initorbs[initorbs] initorbs --> draw[draw loop] draw --> pausecheck[pause-check] draw --> centerEasing[center-easing] draw --> orbLoop[orb-loop] draw --> keypressed[keypressed] draw --> windowresized[windowresized] draw --> randomizecheck[randomize-check] pausecheck -->|if paused| draw pausecheck -->|if not paused| centerEasing centerEasing --> orbLoop orbLoop --> orbGenerationLoop[orb-generation-loop] orbGenerationLoop --> orbLoop orbLoop --> draw keypressed --> spaceToggle[space-toggle] spaceToggle --> draw randomizecheck -->|if R or r pressed| initorbs click setup href "#fn-setup" click initorbs href "#fn-initorbs" click draw href "#fn-draw" click pausecheck href "#sub-pause-check" click centerEasing href "#sub-center-easing" click orbLoop href "#sub-orb-loop" click orbGenerationLoop href "#sub-orb-generation-loop" click spaceToggle href "#sub-space-toggle" click randomizecheck href "#sub-randomize-check"

Preview

Deomo Of Ai coding - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Deomo Of Ai coding - Code flow showing setup, draw, initorbs, windowresized, keypressed
Code Flow Diagram