AI Sunrise Animation - Dawn Breaks with Stars

This sketch paints a slow, continuous sunrise: stars twinkle in a deep purple sky, the sky gradient shifts through dawn pinks into daytime blue, a glowing sun climbs from the horizon, wispy clouds catch the light, and a black mountain silhouette frames the whole scene.

🧪 Try This!

Experiment with the code by making these changes:

  1. Slow down the sunrise — Increasing the divisor makes progress climb more slowly, stretching the whole sunrise over a longer time.
  2. Add way more stars — Raising the loop count fills the night sky with a much denser field of twinkling points.
  3. Make a bigger, glowier sun — Shrinking the size divisor and boosting the glow divisor makes the sun larger and gives it a hazier halo.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch renders a slowly unfolding sunrise entirely inside the draw() loop, using frameCount as a timer to drive every visual change. A pixel-row gradient sweeps from night purple through dawn pink to daytime blue, scattered stars fade out as the sky brightens, a sun glides upward while glowing via the canvas's native shadowBlur property, and a hand-built mountain silhouette anchors the composition using beginShape()/vertex(). It's a great sketch for learning color interpolation with lerpColor(), timing animations with map() and constrain(), and mixing p5.js drawing calls with raw HTML5 canvas context features.

The code is intentionally compact - nearly everything happens inside a single draw() function, with comma-separated variable declarations doing a lot of work per line. Studying it teaches you how to build a multi-stage animation (night -> dawn -> day) from just one progress variable, how nested lerpColor() calls can blend between three color stages, and how small canvas-context tweaks like shadowBlur add polish that pure p5.js shapes can't achieve alone.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, turns off shape outlines with noStroke(), and locks the frame rate to 30fps for consistent timing.
  2. Every frame, draw() computes a single 'progress' value from frameCount (0 at the start, 1 after 600 frames) and uses it to calculate the sun's height and to blend between night, dawn, and day sky colors.
  3. A for-loop draws the sky one horizontal line at a time, interpolating between the current top and bottom colors to create a smooth vertical gradient.
  4. Fifty stars are drawn at random positions each frame with an opacity that fades from fully visible to invisible as progress moves through the first half of the animation, mimicking stars disappearing at dawn.
  5. Once progress is between 0.2 and 0.7, two semi-transparent rectangles are drawn as clouds, colored partway between dawn and day tones; meanwhile the sun (drawn with a soft glow via drawingContext.shadowBlur) rises from the bottom of the screen toward the upper quarter.
  6. Finally a black polygon built from seven vertex() points is drawn across the bottom of the canvas as a mountain silhouette, completing the scene every frame.

🎓 Concepts You'll Learn

Color interpolation with lerpColor()Pixel-row gradients using nested loopsTiming animation with frameCount and constrain/mapAlpha transparency for fadesNative canvas shadowBlur/shadowColor effectsCustom polygons with beginShape()/vertex()/endShape()

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. Here it's used to size the canvas to the window, disable strokes globally, and set a consistent frame rate - all foundational choices that shape everything draw() does afterward.

function setup() { createCanvas(windowWidth, windowHeight); noStroke(); frameRate(30); }
Line-by-line explanation (3 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, so the sunrise scene spans the whole screen.
noStroke();
Turns off outlines for all shapes drawn afterward, so the gradient lines, stars, sun, and mountains all look solid and clean.
frameRate(30);
Locks the sketch to 30 frames per second, giving predictable timing so the 600-frame sunrise always takes the same real-world time regardless of the viewer's hardware.

draw()

draw() runs continuously (30 times per second here), and this sketch packs an entire multi-stage animation into it by deriving everything - sky color, star opacity, cloud visibility, sun height, mountain silhouette - from one master 'progress' variable tied to frameCount. This is a powerful pattern: instead of tracking many independent animation states, you compute one timeline value and remap it wherever you need different timing behavior.

🔬 Stars currently fade out over the first half of the animation (map's 0.5 endpoint). What happens if you change that 0.5 to 1, so stars fade out gradually across the ENTIRE sunrise instead of disappearing quickly?

let sa = map(progress, 0, 0.5, 255, 0, true); fill(255, sa); noStroke();
  for (let i = 0; i < 50; i++) circle(random(width), random(height / 2), 2);

🔬 Each vertex's y-value (like height * 0.6) sets a mountain peak's height. What happens if you change height * 0.55 to height * 0.2 to create one dramatically tall peak in the middle?

vertex(0, height); vertex(width * 0.2, height * 0.6); vertex(width * 0.4, height * 0.8); vertex(width * 0.6, height * 0.55); vertex(width * 0.8, height * 0.7); vertex(width, height * 0.9); vertex(width, height);
function draw() {
  let progress = constrain(frameCount / 600, 0, 1), sunY = lerp(height, height / 4, progress);

  let nightTop = color(20, 0, 50), nightBottom = color(50, 0, 100), dawnTop = color(255, 100, 100), dawnBottom = color(255, 200, 100), dayTop = color(100, 150, 255), dayBottom = color(200, 220, 255);

  let dawnP = constrain(progress * 2, 0, 1), dayP = constrain((progress - 0.5) * 2, 0, 1);
  let topC = lerpColor(lerpColor(nightTop, dawnTop, dawnP), dayTop, dayP), bottomC = lerpColor(lerpColor(nightBottom, dawnBottom, dawnP), dayBottom, dayP);

  for (let i = 0; i < height; i++) { stroke(lerpColor(topC, bottomC, i / height)); line(0, i, width, i); }

  let sa = map(progress, 0, 0.5, 255, 0, true); fill(255, sa); noStroke();
  for (let i = 0; i < 50; i++) circle(random(width), random(height / 2), 2);

  if (progress > 0.2 && progress < 0.7) { fill(lerpColor(dawnTop, dayTop, map(progress, 0.2, 0.7, 0, 1, true)), 100); rect(0, height * 0.3, width, height * 0.05); rect(width*0.1, height*0.45, width*0.8, height*0.03); }

  fill(255, 160, 0); drawingContext.shadowBlur = width / 20; drawingContext.shadowColor = color(255, 200, 0, 150); circle(width / 2, sunY, width / 10); drawingContext.shadowBlur = 0;

  fill(0); beginShape();
  vertex(0, height); vertex(width * 0.2, height * 0.6); vertex(width * 0.4, height * 0.8); vertex(width * 0.6, height * 0.55); vertex(width * 0.8, height * 0.7); vertex(width, height * 0.9); vertex(width, height);
  endShape(CLOSE);
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

for-loop Sky Gradient Loop for (let i = 0; i < height; i++) { stroke(lerpColor(topC, bottomC, i / height)); line(0, i, width, i); }

Draws one horizontal line per pixel row, blending from the top sky color to the bottom sky color to create a smooth vertical gradient.

for-loop Star Field Loop for (let i = 0; i < 50; i++) circle(random(width), random(height / 2), 2);

Scatters 50 tiny circles in the upper half of the sky each frame, faded out over time to simulate stars disappearing at dawn.

conditional Dawn Cloud Window if (progress > 0.2 && progress < 0.7) { fill(lerpColor(dawnTop, dayTop, map(progress, 0.2, 0.7, 0, 1, true)), 100); rect(0, height * 0.3, width, height * 0.05); rect(width*0.1, height*0.45, width*0.8, height*0.03); }

Only draws the two cloud bands during the middle portion of the animation, color-shifting them from dawn to day tones.

calculation Glowing Sun fill(255, 160, 0); drawingContext.shadowBlur = width / 20; drawingContext.shadowColor = color(255, 200, 0, 150); circle(width / 2, sunY, width / 10); drawingContext.shadowBlur = 0;

Draws the sun with a soft glow using the raw canvas shadowBlur/shadowColor properties, then resets the blur so it doesn't leak into later drawing calls.

calculation Mountain Silhouette vertex(0, height); vertex(width * 0.2, height * 0.6); vertex(width * 0.4, height * 0.8); vertex(width * 0.6, height * 0.55); vertex(width * 0.8, height * 0.7); vertex(width, height * 0.9); vertex(width, height);

Builds a custom jagged polygon that fills the bottom of the canvas with a solid black mountain range silhouette.

let progress = constrain(frameCount / 600, 0, 1), sunY = lerp(height, height / 4, progress);
progress goes from 0 to 1 over 600 frames and drives the entire animation; sunY uses it to move the sun from the bottom of the screen (height) up to a quarter of the way down (height / 4).
let nightTop = color(20, 0, 50), nightBottom = color(50, 0, 100), dawnTop = color(255, 100, 100), dawnBottom = color(255, 200, 100), dayTop = color(100, 150, 255), dayBottom = color(200, 220, 255);
Defines six fixed colors representing the top and bottom of the sky at three stages: night, dawn, and day.
let dawnP = constrain(progress * 2, 0, 1), dayP = constrain((progress - 0.5) * 2, 0, 1);
Splits the single progress value into two separate 0-to-1 timers: dawnP finishes during the first half of the animation, dayP only starts (and finishes) during the second half.
let topC = lerpColor(lerpColor(nightTop, dawnTop, dawnP), dayTop, dayP), bottomC = lerpColor(lerpColor(nightBottom, dawnBottom, dawnP), dayBottom, dayP);
Blends colors in two steps: first night into dawn, then that result into day - giving a smooth three-stage color transition for both the top and bottom of the sky.
for (let i = 0; i < height; i++) { stroke(lerpColor(topC, bottomC, i / height)); line(0, i, width, i); }
Loops over every vertical pixel row and draws a full-width line, blending between topC and bottomC based on how far down the screen that row is - this is what creates the smooth sky gradient.
let sa = map(progress, 0, 0.5, 255, 0, true); fill(255, sa); noStroke();
Maps progress from the range 0-0.5 to an opacity of 255-0, so stars are fully visible at the start and completely faded out by the halfway point.
for (let i = 0; i < 50; i++) circle(random(width), random(height / 2), 2);
Draws 50 small circles (stars) at random x/y positions in the top half of the screen every single frame - because positions are re-randomized each frame, this creates a flickering, twinkling effect rather than fixed stars.
if (progress > 0.2 && progress < 0.7) { fill(lerpColor(dawnTop, dayTop, map(progress, 0.2, 0.7, 0, 1, true)), 100); rect(0, height * 0.3, width, height * 0.05); rect(width*0.1, height*0.45, width*0.8, height*0.03); }
Only during the middle window of the animation, draws two semi-transparent rectangles as clouds, with their color shifting from dawn pink to day blue as progress advances.
fill(255, 160, 0); drawingContext.shadowBlur = width / 20; drawingContext.shadowColor = color(255, 200, 0, 150); circle(width / 2, sunY, width / 10); drawingContext.shadowBlur = 0;
Sets the sun's fill color, then reaches into the raw HTML5 canvas context (drawingContext) to add a soft glow before drawing the sun circle, and immediately resets shadowBlur to 0 so later shapes aren't accidentally blurred too.
fill(0); beginShape();
Sets the fill color to solid black and starts defining a custom multi-point shape for the mountains.
vertex(0, height); vertex(width * 0.2, height * 0.6); vertex(width * 0.4, height * 0.8); vertex(width * 0.6, height * 0.55); vertex(width * 0.8, height * 0.7); vertex(width, height * 0.9); vertex(width, height);
Places seven points that trace a jagged mountain skyline across the width of the canvas, each x/y position calculated as a fraction of width and height so it scales with the window.
endShape(CLOSE);
Closes the shape by connecting the last vertex back to the first, then fills the resulting polygon with the black color set earlier.

windowResized()

windowResized() is a special p5.js function that's automatically called whenever the browser window is resized. Pairing it with resizeCanvas() is the standard way to build responsive, full-window sketches.

function windowResized() { resizeCanvas(windowWidth, windowHeight); }
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Whenever the browser window changes size, this resizes the canvas to match, so the sunrise scene always fills the full window and mountain/sun proportions (which use width/height fractions) adjust automatically.

📦 Key Variables

progress number

The master animation timer, recalculated every frame from frameCount and constrained to 0-1; it drives the sun's height, sky colors, star opacity, and cloud visibility. It is declared fresh inside draw() rather than as a persistent global.

let progress = constrain(frameCount / 600, 0, 1);
sunY number

The sun's current vertical position, interpolated between the bottom of the screen and the upper quarter based on progress.

let sunY = lerp(height, height / 4, progress);
topC / bottomC object (p5.Color)

The current top and bottom colors of the sky gradient for this frame, blended between night, dawn, and day palettes using progress.

let topC = lerpColor(lerpColor(nightTop, dawnTop, dawnP), dayTop, dayP);
dawnP / dayP number

Two derived 0-1 timers that split the single progress value so the night-to-dawn blend completes in the first half of the animation and the dawn-to-day blend completes in the second half.

let dawnP = constrain(progress * 2, 0, 1), dayP = constrain((progress - 0.5) * 2, 0, 1);

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG draw() star loop

Star positions are re-randomized every single frame instead of being fixed, so stars don't twinkle in place - they jump around chaotically, which can look more like static noise than a starry sky.

💡 Generate an array of star positions once in setup() (e.g. stars.push({x: random(width), y: random(height/2)})) and loop over that fixed array in draw(), optionally varying only each star's opacity or size for a true twinkle effect.

PERFORMANCE draw() gradient loop

The sky gradient is redrawn by calling lerpColor() and line() once per pixel row, every single frame (potentially 1000+ line() calls at 30fps), which is expensive and unnecessary since the gradient only changes gradually.

💡 Render the gradient into an offscreen buffer with createGraphics() and only regenerate it every few frames (or whenever the color noticeably changes), then just image() it onto the main canvas each frame.

STYLE draw()

Many unrelated variables are declared together in single comma-separated lines (e.g. nightTop, nightBottom, dawnTop... all in one statement), which makes the code hard to read and debug.

💡 Split multi-variable declarations onto separate lines with blank lines between logical sections (sky colors, star settings, sun settings) for much better readability.

FEATURE draw()

Once progress reaches 1 (frameCount > 600 at 30fps, so after 20 seconds), the sun stops at height/4 and the scene stays static forever with no way to replay the sunrise.

💡 Add a loop-back by resetting frameCount to 0 once progress hits 1 (e.g. if (progress >= 1) frameCount = 0;), or add a mouse/key handler that restarts the animation on click.

🔄 Code Flow

Code flow showing setup, draw, 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] draw --> starloop[star-loop] draw --> cloudconditional[cloud-conditional] draw --> sundrawing[sun-drawing] draw --> mountainshape[mountain-shape] gradientloop --> draw starloop --> draw cloudconditional --> draw sundrawing --> draw mountainshape --> draw click setup href "#fn-setup" click draw href "#fn-draw" click gradientloop href "#sub-gradient-loop" click starloop href "#sub-star-loop" click cloudconditional href "#sub-cloud-conditional" click sundrawing href "#sub-sun-drawing" click mountainshape href "#sub-mountain-shape"

❓ Frequently Asked Questions

What visual experience does the AI Sunrise Animation create?

The sketch showcases a mesmerizing sunrise, transitioning the sky from deep purple to vibrant pink and orange, while stars twinkle and mountain silhouettes frame the scene.

Can users interact with the AI Sunrise Animation in any way?

This sketch is not interactive; it automatically animates the sunrise over a set duration, creating a soothing visual experience.

What creative coding techniques are demonstrated in this p5.js sketch?

The sketch utilizes color interpolation and linear interpolation to create smooth transitions between night and dawn, as well as dynamic shapes to represent the sun and mountains.

Preview

AI Sunrise Animation - Dawn Breaks with Stars - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Sunrise Animation - Dawn Breaks with Stars - Code flow showing setup, draw, windowresized
Code Flow Diagram