AI Moon Phase Display - Tonight's Lunar Phase

This sketch creates a calming space visualization that displays tonight's accurate moon phase based on the current date. Twinkling stars animate across a black night sky while a cream-colored moon in the center shows its current phase (New Moon through Full Moon) with an animated shadow that waxes and wanes, accompanied by a text label identifying the phase name.

πŸŽ“ Concepts You'll Learn

Animation loopTrigonometric functionsDate/time functionsArray iterationConditional logicMathematical calculationsCanvas responsivenessColor mappingEllipse drawing

πŸ”„ 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 --> backgroundclear[background-clear] draw --> moonposition[moon-position] draw --> datecalculation[date-calculation] draw --> moondrawing[moon-drawing] draw --> shadowcalculation[shadow-calculation] draw --> shadowpositionconditional[shadow-position-conditional] draw --> shadowdrawing[shadow-drawing] draw --> phaselabel[phase-label] draw --> staranimationloop[star-animation-loop] draw --> stargenerationloop[star-generation-loop] backgroundclear --> staranimationloop staranimationloop --> staranimationloop_end[End Star Animation Loop] staranimationloop_end --> draw stargenerationloop --> stargenerationloop_end[End Star Generation Loop] stargenerationloop_end --> setup datecalculation --> moonreposition[moon-reposition] moonreposition --> moonposition shadowpositionconditional --> shadowdrawing click setup href "#fn-setup" click draw href "#fn-draw" click backgroundclear href "#sub-background-clear" click moonposition href "#sub-moon-position" click datecalculation href "#sub-date-calculation" click moondrawing href "#sub-moon-drawing" click shadowcalculation href "#sub-shadow-calculation" click shadowpositionconditional href "#sub-shadow-position-conditional" click shadowdrawing href "#sub-shadow-drawing" click phaselabel href "#sub-phase-label" click staranimationloop href "#sub-star-animation-loop" click stargenerationloop href "#sub-star-generation-loop"

πŸ“ Code Breakdown

setup()

setup() runs once when the sketch starts. It's the perfect place to initialize your canvas, create objects, and set starting positions. The stars array stores objects (not just numbers), which allows each star to have multiple properties like position, size, and animation offset.

function setup() {
  createCanvas(windowWidth, windowHeight);
  for (let i = 0; i < 200; i++) { stars.push({ x: random(width), y: random(height), size: random(1, 3), alphaOffset: random(TWO_PI) }); }
  moonX = width / 2;
  moonY = height / 2;
}

πŸ”§ Subcomponents:

function-call Canvas Creation createCanvas(windowWidth, windowHeight)

Creates a canvas that fills the entire browser window, allowing the sketch to be responsive

for-loop Star Generation Loop for (let i = 0; i < 200; i++) { stars.push({ x: random(width), y: random(height), size: random(1, 3), alphaOffset: random(TWO_PI) }); }

Creates 200 star objects with random positions, sizes, and animation offsets, storing them in the stars array

calculation Moon Position Initialization moonX = width / 2; moonY = height / 2;

Centers the moon in the middle of the canvas by setting its coordinates to the canvas center

Line by Line:

createCanvas(windowWidth, windowHeight)
Creates a canvas that matches the full browser window size, making the visualization responsive to window resizing
for (let i = 0; i < 200; i++)
Loops 200 times to create 200 individual stars in the night sky
stars.push({ x: random(width), y: random(height), size: random(1, 3), alphaOffset: random(TWO_PI) })
Creates a star object with random x/y position anywhere on canvas, random size between 1-3 pixels, and a random phase offset (0 to 2Ο€) for twinkling animation
moonX = width / 2
Sets the moon's horizontal position to the exact center of the canvas
moonY = height / 2
Sets the moon's vertical position to the exact center of the canvas

draw()

draw() runs 60 times per second by default, creating smooth animation. This sketch uses trigonometric functions (sine wave) for twinkling, mathematical calculations for moon phases, and conditional logic to handle different phase states. The key insight is using phaseFraction (0-1) as a normalized value that can be mapped to different visual effects and array indices.

function draw() {
  background(0);
  for (let star of stars) { fill(255, map(sin(frameCount * 0.1 + star.alphaOffset), -1, 1, 100, 255)); noStroke(); circle(star.x, star.y, star.size); }

  let dayOfMonth = day(); // Get current day of month (p5.js function)
  let phaseFraction = (dayOfMonth % 29.53) / 29.53; // ~29.53 days in a synodic month
  let currentPhaseName = phaseNames[floor(phaseFraction * 8)]; // Map fraction to one of 8 phases

  fill(240, 240, 220); // Cream color for moon
  noStroke();
  circle(moonX, moonY, moonDiameter); // Draw full moon

  let shadowWidth = moonDiameter * abs(1 - phaseFraction * 2); // Calculate shadow width (0 to moonDiameter)
  let shadowX;

  if (shadowWidth > 0.1) { // Only draw shadow if it's not effectively zero (Full Moon)
    fill(20, 20, 40); // Dark shadow
    if (phaseFraction <= 0.5) { // Waxing phases (shadow on left)
      shadowX = moonX - moonRadius + shadowWidth / 2;
    } else { // Waning phases (shadow on right)
      shadowX = moonX + moonRadius - shadowWidth / 2;
    }
    ellipse(shadowX, moonY, shadowWidth, moonDiameter); // Draw shadow ellipse
  }

  fill(255);
  textAlign(CENTER, CENTER);
  textSize(16);
  text(currentPhaseName, moonX, moonY + moonRadius + 30); // Display phase name
}

πŸ”§ Subcomponents:

function-call Background Clear background(0)

Clears the canvas to black (0) each frame, erasing the previous frame's drawing

for-loop Star Animation Loop for (let star of stars) { fill(255, map(sin(frameCount * 0.1 + star.alphaOffset), -1, 1, 100, 255)); noStroke(); circle(star.x, star.y, star.size); }

Iterates through all stars and draws each one with a twinkling effect by varying its alpha (opacity) based on a sine wave

calculation Current Date and Phase Calculation let dayOfMonth = day(); let phaseFraction = (dayOfMonth % 29.53) / 29.53; let currentPhaseName = phaseNames[floor(phaseFraction * 8)];

Gets today's date, calculates where we are in the lunar cycle (0-1), and maps it to one of the 8 phase names

function-call Moon Drawing fill(240, 240, 220); noStroke(); circle(moonX, moonY, moonDiameter);

Draws a cream-colored full moon circle at the center of the canvas

calculation Shadow Width Calculation let shadowWidth = moonDiameter * abs(1 - phaseFraction * 2);

Calculates how wide the shadow should be based on the moon phase (0 at full moon, maximum at new moon)

conditional Shadow Position Logic if (phaseFraction <= 0.5) { shadowX = moonX - moonRadius + shadowWidth / 2; } else { shadowX = moonX + moonRadius - shadowWidth / 2; }

Determines whether the shadow should be on the left (waxing) or right (waning) side of the moon

conditional Shadow Drawing Condition if (shadowWidth > 0.1) { ... ellipse(shadowX, moonY, shadowWidth, moonDiameter); }

Only draws the shadow if it's visible (not a full moon), using an ellipse to create the crescent effect

function-call Phase Name Display fill(255); textAlign(CENTER, CENTER); textSize(16); text(currentPhaseName, moonX, moonY + moonRadius + 30);

Displays the phase name in white text below the moon

Line by Line:

background(0)
Clears the entire canvas to black (color value 0) at the start of each frame, erasing everything drawn in the previous frame
for (let star of stars)
Loops through each star object in the stars array using a for-of loop (modern JavaScript syntax)
fill(255, map(sin(frameCount * 0.1 + star.alphaOffset), -1, 1, 100, 255))
Sets the fill color to white (255) with alpha (opacity) that varies over time. The sine wave creates smooth twinkling by oscillating the alpha between 100 and 255
frameCount * 0.1 + star.alphaOffset
Creates the twinkling animation: frameCount increases each frame, multiplied by 0.1 to slow the oscillation, plus each star's unique offset so they don't all twinkle in sync
circle(star.x, star.y, star.size)
Draws a circle at each star's position with its individual size
let dayOfMonth = day()
p5.js function that returns the current day of the month (1-31) from the system date
let phaseFraction = (dayOfMonth % 29.53) / 29.53
Calculates where we are in the lunar cycle: the modulo operator (%) wraps the day around a 29.53-day cycle, then dividing by 29.53 gives a value from 0 to 1 representing the phase progress
let currentPhaseName = phaseNames[floor(phaseFraction * 8)]
Maps the phase fraction (0-1) to one of 8 phase names: multiply by 8 to get 0-8, floor() rounds down to get an index 0-7, then uses that to pick from the phaseNames array
fill(240, 240, 220)
Sets the fill color to a cream/light yellow color (RGB values slightly below 255 to avoid pure white)
circle(moonX, moonY, moonDiameter)
Draws a full cream-colored circle representing the moon's full lit surface
let shadowWidth = moonDiameter * abs(1 - phaseFraction * 2)
Calculates shadow width: when phaseFraction is 0 (new moon) or 1 (full moon), shadowWidth is maximum; at 0.5 (full moon), shadowWidth is 0. The abs() ensures positive values
if (shadowWidth > 0.1)
Only draws the shadow if it's larger than 0.1 pixels, avoiding invisible shadows at full moon
if (phaseFraction <= 0.5)
Checks if we're in the waxing phase (first half of lunar cycle) to position shadow on the left, or waning phase (second half) to position on the right
shadowX = moonX - moonRadius + shadowWidth / 2
For waxing phases: positions the shadow ellipse on the left side of the moon, starting from the left edge and moving right as the phase progresses
shadowX = moonX + moonRadius - shadowWidth / 2
For waning phases: positions the shadow ellipse on the right side of the moon, starting from the right edge and moving left as the phase progresses
ellipse(shadowX, moonY, shadowWidth, moonDiameter)
Draws the shadow as a dark ellipse: shadowWidth controls horizontal width (creating the crescent), moonDiameter keeps vertical height equal to moon diameter
textAlign(CENTER, CENTER)
Sets text alignment so that the text position is centered both horizontally and vertically
text(currentPhaseName, moonX, moonY + moonRadius + 30)
Displays the phase name text 30 pixels below the bottom of the moon (moonY + moonRadius positions the bottom edge, +30 adds spacing)

windowResized()

windowResized() is a special p5.js function that automatically gets called whenever the browser window is resized. Without this function, the canvas would stay its original size and not adapt to window changes. This is important for responsive designβ€”the moon stays centered even when the user resizes their browser.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  moonX = width / 2;
  moonY = height / 2;
}

πŸ”§ Subcomponents:

function-call Canvas Resize resizeCanvas(windowWidth, windowHeight)

Resizes the canvas to match the new browser window dimensions when the user resizes their window

calculation Moon Reposition moonX = width / 2; moonY = height / 2;

Recalculates the moon's center position based on the new canvas dimensions

Line by Line:

resizeCanvas(windowWidth, windowHeight)
p5.js function that resizes the canvas to match the current window dimensions, called automatically when the browser window is resized
moonX = width / 2
Recalculates the moon's horizontal center position based on the new canvas width
moonY = height / 2
Recalculates the moon's vertical center position based on the new canvas height

πŸ“¦ Key Variables

stars array

Stores an array of star objects, each with x, y, size, and alphaOffset properties. This allows the sketch to manage and animate 200 individual stars

let stars = [];
phaseNames array

Stores the 8 lunar phase names in order from new moon to waning crescent. Used to display the correct phase label based on the current date

let phaseNames = ["New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous", "Full Moon", "Waning Gibbous", "Last Quarter", "Waning Crescent"];
moonDiameter number

Stores the diameter of the moon circle in pixels (150). Used to draw the moon and calculate shadow dimensions

let moonDiameter = 150;
moonRadius number

Stores the radius of the moon (half the diameter). Used for positioning the shadow and the phase label below the moon

let moonRadius = moonDiameter / 2;
moonX number

Stores the horizontal (x) position of the moon's center on the canvas. Initialized to canvas center and updated when window resizes

let moonX = width / 2;
moonY number

Stores the vertical (y) position of the moon's center on the canvas. Initialized to canvas center and updated when window resizes

let moonY = height / 2;

πŸ§ͺ Try This!

Experiment with the code by making these changes:

  1. Change moonDiameter from 150 to 250 to make the moon much larger. Remember that moonRadius will automatically update since it's calculated from moonDiameter
  2. Modify the star twinkling speed by changing the 0.1 multiplier in the line 'frameCount * 0.1 + star.alphaOffset' to 0.05 (slower) or 0.2 (faster) and watch how the twinkling rhythm changes
  3. Change the shadow color from fill(20, 20, 40) to fill(50, 30, 100) to give the shadow a purple tint instead of dark blue
  4. Increase the number of stars from 200 to 500 in the setup() loop and see how a more crowded starfield looks
  5. Add a glow effect to the moon by drawing a semi-transparent circle around it before drawing the moon itself: fill(240, 240, 220, 50); circle(moonX, moonY, moonDiameter + 40);
  6. Change the phase label position by modifying the +30 in the text() call to +50 or +10 to move it further or closer to the moon
Open in Editor & Experiment β†’

πŸ”§ Potential Improvements

Here are some ways this code could be enhanced:

BUG draw() - shadow calculation

The shadow calculation uses abs(1 - phaseFraction * 2), which creates maximum shadow width at both new moon (phaseFraction=0) and full moon (phaseFraction=1), but should be zero at full moon. The formula is mathematically incorrect for the intended effect

πŸ’‘ Use let shadowWidth = moonDiameter * abs(sin(phaseFraction * PI)); instead, which creates a smooth sine wave that goes from 0 (full moon) to maximum (new moon) and back to 0

PERFORMANCE draw() - star animation

The sine calculation is performed every frame for every star, even though the visual difference between frames is minimal due to the 0.1 multiplier. This is computationally unnecessary

πŸ’‘ Pre-calculate the sine value once per frame before the loop: let twinkleFactor = sin(frameCount * 0.1); then use it for all stars to reduce calculations

STYLE draw() - variable naming

Variable names like 'shadowX' and 'shadowWidth' are only used within conditional blocks, making the code harder to follow. The shadow position logic is also somewhat confusing

πŸ’‘ Add comments explaining the waxing vs waning logic, or refactor into a helper function like calculateShadowPosition(phaseFraction) to make the code more readable

FEATURE sketch overall

The sketch displays the current moon phase, but doesn't indicate what date's phase is being shown or when the next full/new moon occurs

πŸ’‘ Add text displaying the current date using day(), month(), and year() functions, or add a line showing 'Days until full moon' by calculating the difference between the current phase and the next full moon

BUG draw() - phase name display

When the phase fraction is very close to 1.0, floor(phaseFraction * 8) could equal 8, which would be out of bounds for the phaseNames array (which only has indices 0-7)

πŸ’‘ Use floor(phaseFraction * 8) % 8 to ensure the index wraps around, or use constrain(floor(phaseFraction * 8), 0, 7) to clamp it to valid range

PERFORMANCE setup() - star creation

Creating 200 star objects with random properties is fine, but if you wanted to scale to thousands of stars, this approach could become slow

πŸ’‘ For future scaling, consider using a particle system class or pre-generating star data once rather than in a loop each time

Preview

AI Moon Phase Display - Tonight's Lunar Phase - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Moon Phase Display - Tonight's Lunar Phase - Code flow showing setup, draw, windowresized
Code Flow Diagram