AI Ocean Waves - Calming Meditation

This sketch paints a tranquil ocean scene using a vertical sky-to-deep-water gradient behind three overlapping sine-wave shapes. Each wave layer moves at its own speed and height, creating a soft parallax effect that mimics water gently lapping near a shore.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the tide — Increasing the time multiplier makes frameCount translate into faster-moving waves across all three layers.
  2. Make the top wave choppier — Raising the amplitude value in the first wave() call makes that layer's ripples much taller and more dramatic.
  3. Recolor the sky and sea — Swapping the gradient's hex codes changes the entire mood of the scene, for example into a warm sunset instead of a cool blue ocean.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch renders a peaceful animated ocean made of three translucent-looking wave bands sitting on top of a smooth blue gradient sky. The illusion of depth comes from layering multiple sine waves that move at different speeds and sit at different heights, a classic parallax trick used constantly in generative art. Under the hood it leans on p5.js's beginShape()/vertex()/endShape() drawing API combined with the raw HTML5 canvas gradient API (drawingContext), plus the sin() function to turn straight lines into rolling curves.

The code is deliberately tiny: a setup() that just creates a full-window canvas, a draw() loop that paints the gradient background and calls a reusable wave() helper three times with different settings, and a windowResized() function to keep the canvas responsive. Studying it teaches you how a single helper function can be reused with different parameters to build a layered scene, and how frameCount plus sin() combine to produce smooth, endless motion without ever storing wave positions in an array.

⚙️ How It Works

  1. When the page loads, setup() creates a canvas that fills the entire browser window so the ocean scene stretches edge to edge.
  2. Every frame, draw() first builds a vertical gradient directly on the canvas's native drawing context, going from a light sky blue at the top to a deep ocean blue at the bottom, and fills the whole canvas with it.
  3. draw() then computes a single time value t from frameCount, which steadily increases every frame and drives the animation.
  4. draw() calls the wave() helper three times, each time with a different vertical position, amplitude, color, and speed multiplier, so the three bands of water move independently and appear layered.
  5. Inside wave(), a loop steps across the width of the canvas in 20-pixel increments, using sin(x * a - t * s) to bend a straight line into a rolling curve, then closes the shape down to the bottom corners so it fills in solidly.
  6. windowResized() keeps everything working if the browser window changes size by resizing the canvas to match, so the wave layout stays proportional.

🎓 Concepts You'll Learn

Animation loop (draw)Sine wave motionCanvas gradients via drawingContextCustom shapes with beginShape/vertex/endShapeFunction parameters for reusable drawingResponsive canvas with windowResized

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts, which makes it the right place to configure the canvas. Using windowWidth and windowHeight instead of fixed numbers is a common pattern for full-screen, responsive sketches.

function setup(){createCanvas(windowWidth,windowHeight);}
Line-by-line explanation (1 lines)
createCanvas(windowWidth,windowHeight);
Creates a canvas that exactly matches the current browser window size, so the ocean scene fills the whole screen instead of a fixed small box.

draw()

draw() runs continuously, about 60 times per second, and is where all animation happens. This sketch shows how to mix p5.js drawing functions (fill, noStroke) with lower-level canvas API calls (drawingContext.createLinearGradient) when p5.js doesn't offer a built-in gradient function.

🔬 These three calls stack waves with increasing amplitude but decreasing speed as they get 'deeper'. What happens if you reverse the speed values so the deepest, darkest wave moves the fastest?

  wave(height*0.7,20,color('#a7d8ff'),0.6,t);
  wave(height*0.73,25,color('#64b5f6'),0.4,t);
  wave(height*0.76,30,color('#1e88e5'),0.25,t);
function draw(){
  let g=drawingContext.createLinearGradient(0,0,0,height);
  g.addColorStop(0,'#87cefa');g.addColorStop(1,'#004f7a');
  drawingContext.fillStyle=g;drawingContext.fillRect(0,0,width,height);
  noStroke();let t=frameCount*0.02;
  wave(height*0.7,20,color('#a7d8ff'),0.6,t);
  wave(height*0.73,25,color('#64b5f6'),0.4,t);
  wave(height*0.76,30,color('#1e88e5'),0.25,t);
}
Line-by-line explanation (8 lines)
let g=drawingContext.createLinearGradient(0,0,0,height);
Reaches past p5.js into the raw HTML5 canvas API to create a gradient object that transitions vertically from y=0 to y=height.
g.addColorStop(0,'#87cefa');g.addColorStop(1,'#004f7a');
Defines the gradient's two colors: a light sky blue at the very top (position 0) and a deep ocean blue at the very bottom (position 1).
drawingContext.fillStyle=g;drawingContext.fillRect(0,0,width,height);
Sets the gradient as the current fill style on the raw canvas context, then draws a rectangle covering the entire canvas, painting the sky-to-water background.
noStroke();
Turns off outlines for every shape drawn after this point, so the wave shapes look like smooth solid color bands with no border.
let t=frameCount*0.02;
Turns the ever-increasing frameCount into a slowly growing 'time' value that will be used to animate the wave's phase.
wave(height*0.7,20,color('#a7d8ff'),0.6,t);
Draws the topmost, lightest wave layer starting 70% down the canvas, with a small 20px amplitude and a relatively fast speed multiplier of 0.6.
wave(height*0.73,25,color('#64b5f6'),0.4,t);
Draws a middle wave layer slightly lower and taller than the first, using a medium blue and a slower speed, so it visually sits 'behind' the first layer.
wave(height*0.76,30,color('#1e88e5'),0.25,t);
Draws the deepest, darkest, tallest wave layer that moves the slowest, reinforcing the illusion that it is the furthest and heaviest body of water.

wave()

wave() is a reusable helper function - instead of writing the wave-drawing code three separate times in draw(), it's written once and called three times with different parameters (yBase, amp, col, s). This is a core programming idea: turning repeated logic into a parameterized function.

🔬 This loop places a new point every 20 pixels along a sine curve. What happens if you change the step to x+=100 (fewer, chunkier points) versus x+=2 (a super smooth curve)?

  for(let x=0;x<=width+20;x+=20){
    let a=0.01,y=yBase+sin(x*a-t*s)*amp;
    vertex(x,y);
  }
function wave(yBase,amp,col,s,t){
  fill(col);beginShape();
  for(let x=0;x<=width+20;x+=20){
    let a=0.01,y=yBase+sin(x*a-t*s)*amp;
    vertex(x,y);
  }
  vertex(width,height);vertex(0,height);endShape(CLOSE);
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

for-loop Sine Curve Vertex Loop for(let x=0;x<=width+20;x+=20){

Steps across the canvas width, calculating a sine-based y position at each x so the top edge of the shape ripples like water.

fill(col);beginShape();
Sets the color for this wave layer, then tells p5.js to start collecting a series of vertex() points that will form one custom shape.
for(let x=0;x<=width+20;x+=20){
Loops across the canvas from left to right in steps of 20 pixels, going slightly past the right edge (width+20) so the wave shape has no visible gap at the edge.
let a=0.01,y=yBase+sin(x*a-t*s)*amp;
Calculates the y position for this point on the curve: yBase is the resting height, and sin(x*a - t*s)*amp bends it up and down. 'a' controls how tightly packed the ripples are, 't*s' shifts the wave sideways over time to animate it, and 'amp' controls how tall the ripples are.
vertex(x,y);
Adds this calculated point to the shape being built, one point per loop iteration.
vertex(width,height);vertex(0,height);endShape(CLOSE);
After the rippling top edge is finished, these two points drop down to the bottom-right and bottom-left corners of the canvas, then CLOSE seals the shape so it fills in solidly below the wavy line like a body of water.

windowResized()

windowResized() is a built-in p5.js callback that fires automatically on browser resize. Pairing it with createCanvas(windowWidth, windowHeight) in setup() is the standard pattern for making a sketch responsive to any screen size.

function windowResized(){resizeCanvas(windowWidth,windowHeight);}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth,windowHeight);
Called automatically by p5.js whenever the browser window changes size; resizes the canvas to match so the ocean scene always fills the window.

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE draw()

A new linear gradient object is created via drawingContext.createLinearGradient every single frame, even though the gradient's colors and size never change unless the window is resized.

💡 Create the gradient once in setup() (and again inside windowResized()) and store it in a variable, then reuse that stored gradient inside draw() instead of rebuilding it 60 times per second.

STYLE wave()

Very short, non-descriptive variable names like a, s, t, g, and col make the code harder to read and easy to confuse with each other, especially since 't' and 's' both influence timing.

💡 Rename them to something descriptive, e.g. frequency instead of a, speedMultiplier instead of s, and timeOffset instead of t, so the sine wave formula reads more like plain English.

PERFORMANCE wave()

The constant 'a=0.01' is redeclared with 'let' on every single iteration of the for loop, which is unnecessary work repeated for every point on every wave, every frame.

💡 Move 'let a=0.01;' outside the for loop (it never changes), and only recalculate 'y' inside the loop.

FEATURE draw() / wave()

The scene is entirely static in terms of interaction - the description mentions 'no interaction needed', but a subtle response to mouse movement could deepen the calming effect without breaking the mood.

💡 Consider gently mapping mouseY to the wave amplitude or speed multiplier so moving the mouse slowly changes the sea's mood, while keeping the animation calm by default when the mouse is idle.

🔄 Code Flow

Code flow showing setup, draw, wave, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> wave1[wave(yBase1, amp1, col1, s1)] draw --> wave2[wave(yBase2, amp2, col2, s2)] draw --> wave3[wave(yBase3, amp3, col3, s3)] wave1 --> wave-vertex-loop1[wave-vertex-loop] wave2 --> wave-vertex-loop2[wave-vertex-loop] wave3 --> wave-vertex-loop3[wave-vertex-loop] wave-vertex-loop1 --> calculateY1[Calculate y position] wave-vertex-loop1 --> drawWave1[Draw wave] wave-vertex-loop2 --> calculateY2[Calculate y position] wave-vertex-loop2 --> drawWave2[Draw wave] wave-vertex-loop3 --> calculateY3[Calculate y position] wave-vertex-loop3 --> drawWave3[Draw wave] draw --> windowresized[windowResized] click setup href "#fn-setup" click draw href "#fn-draw" click wave1 href "#fn-wave" click wave2 href "#fn-wave" click wave3 href "#fn-wave" click wave-vertex-loop1 href "#sub-wave-vertex-loop" click wave-vertex-loop2 href "#sub-wave-vertex-loop" click wave-vertex-loop3 href "#sub-wave-vertex-loop" click calculateY1 href "#sub-calculate-y" click calculateY2 href "#sub-calculate-y" click calculateY3 href "#sub-calculate-y" click drawWave1 href "#sub-draw-wave" click drawWave2 href "#sub-draw-wave" click drawWave3 href "#sub-draw-wave"

❓ Frequently Asked Questions

What visual experience does the AI Ocean Waves sketch provide?

The sketch creates a serene visual of gentle, layered waves in various shades of blue that move in a calming manner across the screen.

Is there any user interaction in the AI Ocean Waves sketch?

No, the sketch is designed for pure relaxation, allowing users to simply watch the waves without any need for interaction.

What creative coding techniques are showcased in the AI Ocean Waves sketch?

This sketch demonstrates the use of sine waves to create fluid motion and gradient backgrounds to enhance the visual depth.

Preview

AI Ocean Waves - Calming Meditation - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Ocean Waves - Calming Meditation - Code flow showing setup, draw, wave, windowresized
Code Flow Diagram