AI Emoji Rain - Colorful Emojis Falling from the Sky

This sketch fills the screen with a soft gradient sky and rains colorful emoji characters that fall under gravity, bounce once when they hit the bottom, and then fade away. Clicking anywhere on the canvas triggers a burst of ten new emojis at the cursor, and the whole scene resizes to fill the browser window.

🧪 Try This!

Experiment with the code by making these changes:

  1. Heavier gravity — Increasing g makes emojis accelerate downward much faster, so they fall and bounce more violently.
  2. Heavier rain — Raising the spawn probability each frame creates a much denser shower of falling emojis.
  3. Super bouncy emojis — Changing the bounce multiplier closer to -1 makes emojis rebound almost as fast as they fell, like rubber balls.
  4. Bigger click bursts — Clicking currently spawns 10 emojis at once - raising this number creates a much bigger explosion of emojis per click.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch simulates a gentle rain of emoji characters - hearts, stars, suns, moons and more - that drop from the top of the screen, accelerate downward under gravity, bounce once when they hit the floor, and gradually fade out. It layers several classic p5.js techniques: a per-pixel gradient background made with lerpColor(), an array-of-objects particle system, simple velocity-and-gravity physics, alpha transparency for fading, and mouse interaction that spawns a burst of emojis on click.

The code is compact - just five small functions - with setup() preparing the canvas and colors, spawn() creating a single emoji object with randomized properties, draw() running the physics and rendering loop every frame, and mousePressed()/windowResized() handling interaction and responsiveness. Studying it teaches how a handful of numeric properties on a plain JavaScript object (position, velocity, size, opacity) can drive a convincing bouncing, fading animation without any physics library.

⚙️ How It Works

  1. When the page loads, setup() creates a full-window canvas, centers text alignment, and defines two sky-blue colors that will be blended into a gradient.
  2. Every frame, draw() first paints the background by looping over every row of pixels and drawing a horizontal line whose color is interpolated between the two sky colors, creating a smooth gradient.
  3. There's roughly a 4% chance each frame that a brand-new emoji is spawned just above the top of the screen at a random x position, so emojis keep appearing over time.
  4. For every emoji currently on screen, gravity is added to its vertical speed, then its position is updated - this is the classic velocity-plus-gravity motion pattern.
  5. When an emoji's bottom edge reaches the floor, its position is clamped to the ground; the first time this happens it bounces back up at reduced speed, and the second time it simply stops.
  6. Once an emoji has bounced, its opacity steadily decreases every frame until it reaches zero, at which point it's removed from the array entirely - meanwhile, clicking anywhere spawns ten more emojis at once, and resizing the browser window keeps the canvas full-screen.

🎓 Concepts You'll Learn

Gradient backgrounds with lerpColorGravity and velocity-based motionArray-based particle systemsCollision detection and bounce responseAlpha transparency fade-outMouse interaction eventsResponsive canvas resizing

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's the right place to create the canvas and initialize any variables (like colors) that don't need to change every frame.

function setup(){
  createCanvas(windowWidth,windowHeight);
  textAlign(CENTER,CENTER);
  c1=color(135,206,250);c2=color(180,228,255);
}
Line-by-line explanation (3 lines)
createCanvas(windowWidth,windowHeight);
Creates a canvas that fills the entire browser window, using the browser's current width and height.
textAlign(CENTER,CENTER);
Tells p5.js to center text both horizontally and vertically around the x,y coordinates you give to text(), so each emoji is drawn centered on its position instead of offset.
c1=color(135,206,250);c2=color(180,228,255);
Defines two light sky-blue colors that will later be blended together to paint the gradient background.

spawn(x, y)

spawn() is a factory function - it creates one plain JavaScript object representing a single emoji and pushes it into the shared emojis array. Using object literals like this is a common lightweight alternative to defining a full class for simple particle systems.

function spawn(x,y){
  emojis.push({x,y,vy:random(1,3),vx:random(-0.5,0.5),s:random(32,56),ch:random(chars),b:false,a:255});
}
Line-by-line explanation (1 lines)
emojis.push({x,y,vy:random(1,3),vx:random(-0.5,0.5),s:random(32,56),ch:random(chars),b:false,a:255});
Builds a brand-new emoji object using the x,y position passed in, plus a random falling speed (vy) between 1 and 3, a slight random horizontal drift (vx) between -0.5 and 0.5, a random size (s) between 32 and 56 pixels, a random emoji character (ch) picked from the chars array, a 'has it bounced yet' flag (b) starting false, and full opacity (a=255) - then adds this object to the end of the emojis array so draw() will start animating it.

draw()

draw() runs continuously at roughly 60 frames per second, and this is where the whole simulation happens: painting the background, occasionally spawning new emojis, and looping through the emojis array to update physics and render each one. Looping backwards (from emojis.length-1 down to 0) is a common trick that lets you safely remove items from the array mid-loop with splice() without skipping the next element.

🔬 This block makes each emoji bounce exactly once before stopping. What happens if you change -0.6 to -0.95 for a much bouncier landing, or to -0.15 for a heavy thud?

    if(e.y+e.s/2>height){
      e.y=height-e.s/2;
      if(!e.b){e.vy*=-0.6;e.b=true;}else e.vy=0;
    }

🔬 This one line paints the entire gradient sky. What happens if you swap the order to lerpColor(c2,c1,y/height) - which color ends up at the top of the screen now, and which at the bottom?

  for(let y=0;y<height;y++)stroke(lerpColor(c1,c2,y/height)),line(0,y,width,y);
function draw(){
  for(let y=0;y<height;y++)stroke(lerpColor(c1,c2,y/height)),line(0,y,width,y);
  if(random()<0.04)spawn(random(width),-20);
  noStroke();
  for(let i=emojis.length-1;i>=0;i--){
    let e=emojis[i];
    e.vy+=g;e.x+=e.vx;e.y+=e.vy;
    if(e.y+e.s/2>height){
      e.y=height-e.s/2;
      if(!e.b){e.vy*=-0.6;e.b=true;}else e.vy=0;
    }
    if(e.b)e.a-=4;
    if(e.a<=0){emojis.splice(i,1);continue;}
    fill(0,0,0,e.a);textSize(e.s);text(e.ch,e.x,e.y);
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

for-loop Gradient Sky Background for(let y=0;y<height;y++)stroke(lerpColor(c1,c2,y/height)),line(0,y,width,y);

Loops through every row of the canvas and draws a horizontal line colored by blending c1 and c2, painting a smooth vertical gradient sky each frame.

conditional Random Emoji Spawner if(random()<0.04)spawn(random(width),-20);

Gives roughly a 4% chance per frame of spawning a fresh emoji just above the top edge at a random x position.

for-loop Emoji Physics & Rendering Loop for(let i=emojis.length-1;i>=0;i--){

Iterates backwards through every active emoji to update its physics, handle ground collisions, fade it out, remove it when invisible, or draw it.

conditional Ground Collision Bounce if(e.y+e.s/2>height){

Detects when an emoji's bottom edge reaches the floor, clamps its position, and either bounces it once or stops it if it already bounced.

conditional Fade After Bounce if(e.b)e.a-=4;

Once an emoji has bounced, steadily reduces its opacity every frame so it fades away.

conditional Remove Faded Emoji if(e.a<=0){emojis.splice(i,1);continue;}

Deletes an emoji from the array once it has fully faded out, freeing memory and skipping the draw call for it.

for(let y=0;y<height;y++)stroke(lerpColor(c1,c2,y/height)),line(0,y,width,y);
For every horizontal row of pixels, calculates a blended color between c1 (top) and c2 (bottom) based on how far down the screen the row is, then draws a full-width line in that color - together these rows create a smooth gradient sky.
if(random()<0.04)spawn(random(width),-20);
Rolls a random number between 0 and 1; if it's less than 0.04 (a 4% chance), spawns a new emoji at a random x position just above the top of the canvas (y=-20).
noStroke();
Turns off outlines for anything drawn after this point, since the emoji text doesn't need a border.
let e=emojis[i];
Grabs a reference to the current emoji object in the loop so the code below can refer to it as 'e' instead of 'emojis[i]' every time.
e.vy+=g;e.x+=e.vx;e.y+=e.vy;
Applies gravity by adding the constant g to the emoji's vertical speed, then moves the emoji horizontally by vx and vertically by the newly updated vy - this is the standard gravity-velocity-position update pattern.
if(e.y+e.s/2>height){
Checks if the emoji's bottom edge (its center y plus half its size) has passed the bottom of the canvas, meaning it has hit the ground.
e.y=height-e.s/2;
Snaps the emoji's y position so its bottom edge sits exactly at the floor, preventing it from sinking below the visible canvas.
if(!e.b){e.vy*=-0.6;e.b=true;}else e.vy=0;
If this is the first time the emoji has touched the ground (b is false), reverse its vertical velocity and shrink it to 60% strength to create a bounce, then mark it as bounced; otherwise, if it has already bounced once, just stop its vertical movement entirely.
if(e.b)e.a-=4;
For any emoji that has already bounced, reduce its alpha (opacity) by 4 every frame so it gradually fades away.
if(e.a<=0){emojis.splice(i,1);continue;}
Once an emoji's opacity drops to zero or below, remove it from the emojis array with splice() and skip the rest of this loop iteration with continue, since there's nothing left to draw.
fill(0,0,0,e.a);textSize(e.s);text(e.ch,e.x,e.y);
Sets the fill color to black at the emoji's current opacity, sets the text size to the emoji's stored size, then draws the emoji character at its current x,y position.

mousePressed()

mousePressed() is a p5.js event function that automatically runs once whenever the mouse button is clicked, giving you an easy hook for interactive effects like this click-triggered burst.

🔬 Clicking currently spawns 10 emojis at once. What happens visually and to performance if you change 10 to 100?

function mousePressed(){for(let i=0;i<10;i++)spawn(mouseX,mouseY);}
function mousePressed(){for(let i=0;i<10;i++)spawn(mouseX,mouseY);}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

for-loop Click Burst Spawner for(let i=0;i<10;i++)spawn(mouseX,mouseY);

Calls spawn() ten times at the current mouse position, creating a burst of emojis wherever you click.

for(let i=0;i<10;i++)spawn(mouseX,mouseY);
Runs a loop 10 times, each time calling spawn() at the current mouseX and mouseY coordinates, so a single click produces a burst of ten emojis at once.

windowResized()

windowResized() is a p5.js event function that automatically fires whenever the browser window changes size, letting you keep a canvas responsive without any extra event-listener setup.

function windowResized(){resizeCanvas(windowWidth,windowHeight);}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth,windowHeight);
Resizes the canvas to match the browser window's new width and height whenever the window is resized, keeping the sketch filling the screen.

📦 Key Variables

emojis array

Holds every emoji object currently falling, bouncing, or fading on screen; new ones are pushed in by spawn() and old ones are removed with splice() once fully faded.

let emojis=[];
chars array

The pool of emoji characters (hearts, stars, suns, etc.) that spawn() randomly picks from when creating a new emoji.

let chars=['❤️','⭐','☀️','🌙','🔥','🌈','💧','🍀','🌸','😃'];
g number

The gravity constant added to each emoji's vertical velocity every frame, controlling how fast falling speed builds up.

let g=0.3;
c1 object

The p5.Color used at the top of the sky gradient, set once in setup().

let c1;
c2 object

The p5.Color used at the bottom of the sky gradient, blended with c1 to create the background.

let c2;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE draw() gradient loop

The background gradient is redrawn every single frame by looping over every pixel row and calling stroke() plus line() for each one - on a tall window this can mean hundreds of draw calls per frame just for the background.

💡 Draw the gradient once into an offscreen buffer with createGraphics() (or use the canvas 2D context's native createLinearGradient), then just image() that buffer each frame instead of redrawing it pixel-row by pixel-row.

STYLE entire sketch.js

The code packs multiple statements onto single lines with minimal whitespace (e.g. 'e.vy+=g;e.x+=e.vx;e.y+=e.vy;'), which makes it harder to read, debug, and modify.

💡 Spread statements across multiple lines and add short comments explaining each step - especially around the gravity, collision, and fade logic - to make the sketch easier for beginners to follow.

FEATURE draw() physics loop

Emojis only have gravity and a floor collision - there's no check for the left or right edges, so an emoji with enough horizontal drift (vx) could theoretically continue off-screen forever without being removed.

💡 Add a check that removes or wraps emojis whose x position goes far outside 0 to width, so the emojis array doesn't accumulate off-screen objects that still get processed every frame.

BUG draw() text rendering

fill(0,0,0,e.a) sets a black fill with fading alpha before drawing the emoji, but most browsers render emoji glyphs using their own built-in colors regardless of the fill color, so this fill call has little to no visible effect on the emoji's color.

💡 If the intent was to fade the emoji itself, consider using p5's tint()-style approach with an offscreen graphics buffer, or simply accept that only shapes/text (not color emoji) respond to fill() and remove the misleading fill call.

🔄 Code Flow

Code flow showing setup, spawn, draw, mousepressed, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> gradientloop[Gradient Loop] gradientloop --> randomspawn[Random Emoji Spawner] randomspawn --> physicsloop[Emoji Physics & Rendering Loop] physicsloop --> groundcollision[Ground Collision Bounce] groundcollision --> fadeafterbounce[Fade After Bounce] fadeafterbounce --> removefaded[Remove Faded Emoji] removefaded --> physicsloop physicsloop --> draw draw --> mousepressed[mousePressed] mousepressed --> clickburst[Click Burst Spawner] clickburst --> spawn clickburst --> draw draw --> windowresized[windowResized] click setup href "#fn-setup" click draw href "#fn-draw" click gradientloop href "#sub-gradient-loop" click randomspawn href "#sub-random-spawn" click physicsloop href "#sub-physics-loop" click groundcollision href "#sub-ground-collision" click fadeafterbounce href "#sub-fade-after-bounce" click removefaded href "#sub-remove-faded" click mousepressed href "#fn-mousepressed" click clickburst href "#sub-click-burst"

❓ Frequently Asked Questions

What visual effect does the AI Emoji Rain sketch create?

The sketch visually displays a cheerful shower of colorful emojis, such as hearts and stars, falling from the sky with realistic physics, creating a delightful animation.

How can users interact with the AI Emoji Rain sketch?

Users can interact with the sketch by clicking anywhere on the canvas, which spawns a burst of emojis that rain down from that location.

What creative coding concepts are showcased in this emoji animation?

The sketch demonstrates concepts such as particle systems, physics-based motion, and dynamic graphics rendering using p5.js.

Preview

AI Emoji Rain - Colorful Emojis Falling from the Sky - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Emoji Rain - Colorful Emojis Falling from the Sky - Code flow showing setup, spawn, draw, mousepressed, windowresized
Code Flow Diagram