seaweed

This sketch displays a seaweed or plant image that sways gently back and forth, simulating underwater movement. The plant rotates rhythmically around its base using trigonometric functions, creating a natural swaying animation.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the seaweed sway faster — Higher swayFrequency values speed up the oscillation—the seaweed will rock back and forth much quicker
  2. Make the seaweed sway more dramatically — Higher swayAmplitude values increase the maximum tilt angle—the seaweed will rock further left and right
  3. Change the background to ocean blue — A blue background creates an underwater atmosphere instead of the default light gray
  4. Move the seaweed to the left side — Changing plantX positions the seaweed's base horizontally—this example moves it to the left third of the screen
  5. Make the seaweed twice as big — Multiplying by 2 instead of 1 doubles the image width and height before drawing
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch animates a seaweed or plant image that rocks side-to-side like it's drifting in ocean currents. It demonstrates how trigonometric functions—specifically sin()—can drive smooth, natural-looking animation. The code combines p5.js image loading, the transformation matrix (translate and rotate), and frame-based timing to create mesmerizing organic motion that feels alive.

The code is organized into three functions: preload() loads the seaweed image, setup() configures the canvas and drawing modes, and draw() recalculates the sway angle every frame and applies the rotation. By studying this sketch, you will learn how to load images, pivot a shape around a custom point using translate and rotate, and use sin() to generate smooth oscillating motion—skills that unlock doors to character animation, object physics, and procedural effects.

⚙️ How It Works

  1. When the sketch loads, preload() fetches the seaweed.png image file and stores it in the plantImage variable
  2. setup() creates a canvas that fills the entire browser window and configures imageMode(CENTER) so images rotate around their center point
  3. Every frame, draw() calculates a new sway angle by multiplying frameCount (the frame number) by swayFrequency, passing it through sin() to get a smooth oscillating value, then multiplying by swayAmplitude to control the range
  4. push() saves the current transformation state before making any changes
  5. translate() moves the origin (0,0) to the plant's base position at the bottom center of the canvas
  6. rotate() tilts the entire coordinate system by the calculated sway angle, making the plant rock left and right
  7. image() draws the seaweed at the new coordinate system, rotated and positioned as if growing from the ocean floor
  8. pop() undoes all transformations, resetting the coordinate system for the next frame

🎓 Concepts You'll Learn

Image loading and displayTransform matrix (translate, rotate, push/pop)Trigonometric animation with sin()Frame-based timingCoordinate system manipulationResponsive canvas sizing

📝 Code Breakdown

preload()

preload() is a special p5.js function that runs once before setup(). Use it to load images, sounds, or other files your sketch needs. This ensures everything is ready before your code tries to use it.

function preload() {
  // === IMPORTANT: Replace 'your_plant_image.png' with the actual URL of your PNG image. ===
  // Make sure your PNG has a transparent background for the best effect.
  // The base of the plant should be at the bottom of the image for natural rotation.
  // Replaced unreachable Wikimedia Commons URL with a working Openclipart SVG
  plantImage = loadImage('seaweed.png');
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

calculation Image Loading plantImage = loadImage('seaweed.png');

Fetches the seaweed image file and stores it in a variable so it can be drawn later

plantImage = loadImage('seaweed.png');
loadImage() fetches the PNG file from your project folder and stores it in the plantImage variable. This must happen in preload() before setup() runs, ensuring the image is ready to use when draw() begins

setup()

setup() runs once when the sketch starts. Use it to configure your canvas size and initialize any settings that need to be locked in before animation begins.

function setup() {
  createCanvas(windowWidth, windowHeight);
  imageMode(CENTER); // Position images from their center
  angleMode(RADIANS); // Ensure rotate() uses radians
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Canvas Creation createCanvas(windowWidth, windowHeight);

Creates a canvas that is as wide and tall as the browser window

calculation Image Mode Configuration imageMode(CENTER);

Makes images draw from their center point instead of their top-left corner, which enables natural rotation around the center

calculation Angle Mode Configuration angleMode(RADIANS);

Tells p5.js to measure angles in radians (not degrees), which pairs perfectly with sin() function

createCanvas(windowWidth, windowHeight);
Creates a drawing canvas as tall and wide as the browser window, allowing the sketch to fill the entire screen
imageMode(CENTER);
By default, image() positions images from their top-left corner. CENTER mode positions them from their center instead, so when we rotate the image, it pivots around its center—essential for natural-looking swaying
angleMode(RADIANS);
p5.js can measure angles in degrees (0-360) or radians (0-2π). RADIANS mode pairs perfectly with sin(), which expects radian input and outputs smooth values between -1 and 1

draw()

draw() runs 60 times per second by default. Each frame, we clear the screen, recalculate the sway angle based on frameCount (which increments automatically), and redraw the seaweed in its new rotated position. The magic is in how sin() converts the frame number into a smooth oscillating value—this is the core technique behind trigonometry-driven animation.

🔬 These lines position the seaweed's base. What happens if you change plantY to just 'height' instead of 'height + 20'? What if you change plantX to 'width / 3' instead of 'width / 2'?

  let plantX = width / 2; // Center horizontally
  let plantY = height + 20; // 50 pixels from the bottom of the canvas

🔬 The sin() function drives the swaying motion. What happens if you multiply frameCount * swayFrequency by 2 (e.g., 'sin(frameCount * swayFrequency * 2)')? Does it sway faster or slower?

  let swayAngle = sin(frameCount * swayFrequency) * swayAmplitude;
  rotate(swayAngle); // Apply the rotation
function draw() {
  background(220); // Light background for visibility

  // Calculate the desired position for the base of the plant
  let plantX = width / 2; // Center horizontally
  let plantY = height + 20; // 50 pixels from the bottom of the canvas

  push(); // Start a new transformation state
  translate(plantX, plantY); // Move the origin to the plant's base

  // Calculate the rotation angle using sin() based on frameCount
  // frameCount * swayFrequency controls the speed of the sway
  // swayAmplitude controls the maximum angle of the sway
  let swayAngle = sin(frameCount * swayFrequency) * swayAmplitude;
  rotate(swayAngle); // Apply the rotation

  // --- 修改部分开始 ---
  // 计算缩小后的宽度和高度
  let newWidth = plantImage.width * 1;
  let newHeight = plantImage.height * 1;

  // 绘制图片。因为 imageMode(CENTER) 是设置的,
  // 我们需要根据新的高度来计算 y 轴上的偏移量,以确保底部对齐。
  image(plantImage, 0, -newHeight / 2, newWidth, newHeight);
  // --- 修改部分结束 ---

  pop(); // Restore the previous transformation state (undo translate and rotate)
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Background Clear background(220);

Erases the previous frame by filling the entire canvas with light gray, preventing motion trails

calculation Plant Base Position let plantX = width / 2; // Center horizontally let plantY = height + 20;

Calculates where the bottom of the seaweed should be positioned (center horizontally, slightly below the canvas bottom)

calculation Sway Angle Calculation let swayAngle = sin(frameCount * swayFrequency) * swayAmplitude;

Uses frameCount (frame number) with sin() to generate a smoothly oscillating angle that drives the rocking motion

conditional Transform State Management push(); // Start a new transformation state translate(plantX, plantY); // Move the origin to the plant's base rotate(swayAngle); // Apply the rotation image(plantImage, 0, -newHeight / 2, newWidth, newHeight); pop();

Saves the transformation state, moves and rotates the coordinate system, draws the image, then restores the original state

background(220);
Fills the entire canvas with light gray (220 on a 0-255 scale). This erases the previous frame so the seaweed doesn't leave trails—it resets the canvas for this frame's drawing
let plantX = width / 2;
Calculates the horizontal center of the canvas and stores it in plantX. The seaweed will always sway around this centerline
let plantY = height + 20;
Sets the plant base slightly below the visible canvas (height + 20 means 20 pixels off-screen). This positions the pivot point at the ocean floor, making the seaweed appear to grow from the bottom
push();
Saves the current coordinate system and transformation settings. Think of it as taking a snapshot of the origin (0,0) and rotation state before we change them
translate(plantX, plantY);
Moves the origin (0,0) from the canvas top-left to the plant's base position. Now any drawing at (0,0) happens at plantX, plantY instead
let swayAngle = sin(frameCount * swayFrequency) * swayAmplitude;
Calculates the rotation angle: frameCount (0, 1, 2, 3...) multiplied by swayFrequency creates a slowly changing input to sin(). sin() oscillates smoothly between -1 and 1. Multiplying by swayAmplitude scales it to the maximum tilt angle. The result is a smooth rocking motion that repeats forever
rotate(swayAngle);
Tilts the entire coordinate system by swayAngle radians. After this line, drawing at (0,0) happens at the rotated position, making the seaweed rock left and right
image(plantImage, 0, -newHeight / 2, newWidth, newHeight);
Draws the seaweed image at position (0,0) in the rotated coordinate system. The -newHeight / 2 vertical offset, combined with imageMode(CENTER), ensures the image's bottom edge stays anchored at the pivot point while it rotates
pop();
Restores the coordinate system and rotation to their state before push(). The origin returns to the canvas top-left and rotation is reset, so the next frame starts fresh

windowResized()

windowResized() is a special p5.js event function that fires whenever the browser window is resized. It's useful for sketches designed to be responsive—filling the entire screen. Without it, the canvas would stay the same size and look cropped or stretched.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Called when preview panel is resized, ensuring canvas fits
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

calculation Canvas Resize resizeCanvas(windowWidth, windowHeight);

Adjusts the canvas dimensions to match the browser window whenever it is resized

resizeCanvas(windowWidth, windowHeight);
When the browser window is resized, windowResized() is automatically called by p5.js. This line updates the canvas to match the new window dimensions, ensuring the sketch always fills the screen and doesn't get cut off

📦 Key Variables

plantImage p5.Image

Stores the loaded seaweed image so it can be drawn repeatedly in the draw() function

let plantImage;
swayFrequency number

Controls how fast the seaweed sways back and forth. Multiplied by frameCount to create the oscillating motion—smaller values = slower sway

let swayFrequency = 0.02;
swayAmplitude number

Controls the maximum angle of the sway in radians. Larger values make the seaweed rock further left and right; smaller values keep it more upright

let swayAmplitude = 0.02;
plantX number

Stores the horizontal position where the seaweed base is anchored (center of canvas)

let plantX = width / 2;
plantY number

Stores the vertical position where the seaweed base is anchored (slightly below canvas bottom)

let plantY = height + 20;
swayAngle number

Stores the calculated rotation angle for this frame, derived from sin() to create smooth oscillation

let swayAngle = sin(frameCount * swayFrequency) * swayAmplitude;
newWidth number

Stores the display width of the seaweed image (currently 100% of the original image width)

let newWidth = plantImage.width * 1;
newHeight number

Stores the display height of the seaweed image (currently 100% of the original image height)

let newHeight = plantImage.height * 1;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

STYLE global variables

newWidth and newHeight are recalculated every frame even though they never change (always 100% of original size). This wastes CPU cycles.

💡 Calculate newWidth and newHeight once in setup() instead of every frame. Store them as global variables initialized to plantImage.width and plantImage.height. This is more efficient.

FEATURE draw()

The seaweed image is hardcoded as 'seaweed.png'. If the file is missing or the path is wrong, the sketch silently fails with nothing visible.

💡 Add error handling: after preload(), check if (plantImage) { /* draw */ } else { text('Image failed to load', 20, 20); }. This makes debugging much easier.

BUG draw() image positioning

The image is drawn at (0, -newHeight / 2) with imageMode(CENTER), which positions the image's center at that point. This can cause the seaweed to visually rotate around its middle rather than its base, especially with tall images.

💡 To anchor rotation at the base, either change imageMode(CORNER) and adjust coordinates, or recalculate the offset to use (0, 0) with proper adjustment to account for the pivot point being at the bottom of the image.

FEATURE global scope

The sketch would be more interactive and educational if users could control swayFrequency and swayAmplitude with the mouse in real time, seeing immediate visual feedback.

💡 Add mouseDragged() or mouseMoved() to map mouseY to swayAmplitude and mouseX to swayFrequency. This teaches real-time interactivity and helps viewers intuit how the variables affect motion.

🔄 Code Flow

Code flow showing preload, setup, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> preload[preload] preload --> loadimage[load-image] loadimage --> createcanvas[create-canvas] createcanvas --> imagemode[image-mode] imagemode --> anglemode[angle-mode] setup --> draw[draw loop] draw --> backgroundclear[background-clear] backgroundclear --> positioncalculation[position-calculation] positioncalculation --> swayanglecalculation[sway-angle-calculation] swayanglecalculation --> transformstack[transform-stack] transformstack --> drawimage[Draw Seaweed Image] drawimage --> transformstackrestore[Restore Transform State] transformstackrestore --> draw draw --> windowresized[windowresized] windowresized --> canvasresize[canvas-resize] canvasresize --> createcanvas click preload href "#fn-preload" click setup href "#fn-setup" click draw href "#fn-draw" click windowresized href "#fn-windowresized" click loadimage href "#sub-load-image" click createcanvas href "#sub-create-canvas" click imagemode href "#sub-image-mode" click anglemode href "#sub-angle-mode" click backgroundclear href "#sub-background-clear" click positioncalculation href "#sub-position-calculation" click swayanglecalculation href "#sub-sway-angle-calculation" click transformstack href "#sub-transform-stack" click canvasresize href "#sub-canvas-resize"

❓ Frequently Asked Questions

What visual effect does the seaweed sketch create?

The sketch visually simulates seaweed swaying gently, using rotation to mimic natural movement as it appears to float in water.

Is there any interactivity in the seaweed sketch for users?

The sketch is not interactive; it runs continuously to create a calming animation of the seaweed swaying.

What creative coding techniques are demonstrated in the seaweed sketch?

The sketch showcases transformation techniques in p5.js, such as translating and rotating images based on sine wave calculations to achieve smooth, lifelike motion.

Preview

seaweed - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of seaweed - Code flow showing preload, setup, draw, windowresized
Code Flow Diagram