AI Vine Writer - Type to Grow Organic Vines

This sketch turns your keyboard into a gardening tool: every key you press spawns a curving, noise-driven vine that grows upward from your cursor. Vowels blossom into soft pink or gold flowers while consonants sprout leafy green stems, turning typed words into a living, glowing garden on a dark background.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up vine growth — Increasing the growth increment makes vines shoot upward almost instantly instead of unfurling slowly.
  2. Make vines sway wildly — Boosting the noise multiplier turns gentle curves into dramatic, wide zig-zagging vines.
  3. Change the flower palette — Swapping the color arrays instantly changes what colors vowel keys bloom into.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch reimagines typing as gardening: each keystroke plants a vine at your mouse position that curls upward using Perlin noise, with vowels blossoming into flowers and consonants sprouting leaves. It's a great study in generative motion because it combines noise(), object-based particle systems, array management, and the keyTyped() event handler - core techniques for any interactive p5.js project.

The code keeps a single array of vine objects, each storing its own position, random seed, growth timer, and color. On every frame, draw() ages each vine forward and re-renders it segment by segment using noise() for organic sway, then removes vines once they've fully grown and lingered a while. By studying this sketch you'll learn how to spawn, animate, and clean up many independent objects over time, and how a tiny bit of randomness turns simple ellipses into something that feels alive.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and paints it with a dark green background - no vines exist yet.
  2. Every time you press a character key, keyTyped() creates a new vine object at the current mouse position, giving it a random noise seed, a random maximum height, and - if the key was a vowel - a random flower color.
  3. On every frame, draw() paints a semi-transparent dark rectangle over the whole canvas (instead of a solid background) which creates a soft fading-trail effect, then loops through every vine in the vines array.
  4. Each vine's growth timer (t) is incremented, and drawVine() renders it segment by segment from the base up to its current growth height, using noise() to offset each segment sideways so the vine curves organically.
  5. Vowel vines draw glowing petal-colored circles with a bright highlight near the growing tip; consonant vines draw green stem dots and occasionally sprout a small leaf triangle when noise crosses a threshold.
  6. Once a vine's timer passes its maximum height plus a lingering buffer, it is removed from the vines array with splice() so the array doesn't grow forever.

🎓 Concepts You'll Learn

Perlin noise for organic motionkeyTyped() event handlingArray-based particle/object systemsAdditive fading trails via low-alpha backgroundmap() for value scalingObject literals as lightweight particle classes

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. Here it's used to size the canvas to the full window and set drawing defaults like noStroke() that apply to everything drawn afterward.

function setup(){
  createCanvas(windowWidth,windowHeight);
  background(5,30,10);
  noStroke();
}
Line-by-line explanation (3 lines)
createCanvas(windowWidth,windowHeight);
Creates a canvas that fills the entire browser window.
background(5,30,10);
Paints the canvas a very dark green, setting the 'soil' color the vines will grow against.
noStroke();
Turns off shape outlines so every ellipse and triangle drawn later is a solid, borderless blob of color.

draw()

draw() is p5's animation loop, running ~60 times per second. Here it drives two responsibilities: fading the background for a trail effect, and updating/removing/rendering every vine currently alive in the vines array.

🔬 Each vine grows by 1.8 per frame and is deleted once it passes v.max+60. What happens if you change 1.8 to 6 so vines grow almost instantly? What if you change +60 to +300 so finished vines linger on screen much longer before disappearing?

    v.t+=1.8;
    if(v.t>v.max+60){vines.splice(i,1);continue;}
    drawVine(v);
function draw(){
  background(5,30,10,25);
  for(let i=vines.length-1;i>=0;i--){
    let v=vines[i];
    v.t+=1.8;
    if(v.t>v.max+60){vines.splice(i,1);continue;}
    drawVine(v);
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Semi-Transparent Fade background(5,30,10,25);

Draws a low-alpha rectangle over the whole canvas instead of clearing it fully, which leaves a soft fading trail from previous frames.

for-loop Backward Vine Loop for(let i=vines.length-1;i>=0;i--){

Iterates through all active vines in reverse order so items can be safely removed with splice() mid-loop without skipping elements.

conditional Remove Finished Vines if(v.t>v.max+60){vines.splice(i,1);continue;}

Deletes a vine from the array once it has finished growing and lingered on screen for a while, preventing the array from growing forever.

background(5,30,10,25);
Paints a dark green rectangle with low opacity (alpha 25) over everything, which fades old drawing slightly instead of erasing it completely - this creates the soft trailing/glow effect.
for(let i=vines.length-1;i>=0;i--){
Loops through the vines array backwards, which is the safe way to remove items from an array while iterating over it.
let v=vines[i];
Grabs the current vine object so its properties can be read and updated.
v.t+=1.8;
Advances this vine's growth timer, making it grow a little taller every frame.
if(v.t>v.max+60){vines.splice(i,1);continue;}
If the vine has grown past its maximum height plus a 60-frame grace period, remove it from the array entirely and skip drawing it.
drawVine(v);
Calls the helper function that actually renders this vine's segments, flowers, or leaves.

drawVine()

This helper function is where all visual style lives - it walks along one vine's length and decides, segment by segment, what shape and color to draw based on whether the vine came from a vowel or consonant, and based on noise values for organic variation.

🔬 This highlight only appears within the last 14 units of the vine's growth, simulating a glowing bud at the tip. What happens if you change 14 to 60, so a much bigger portion of the flower glows? What if you change the highlight color to fill(100,200,255,220) for a cool blue center instead?

      if(i>v.t-14){
        fill(255,240,200,220);
        ellipse(xx,yy,r+4,r+4);
      }
function drawVine(v){
  for(let i=0;i<v.t&&i<v.max;i+=4){
    let off=i*0.03;
    let yy=v.y-i,xx=v.x+(noise(v.seed+off)-0.5)*60;
    let r=map(i,0,v.max,7,1);
    if(v.isVowel){
      fill(v.col[0],v.col[1],v.col[2],210);
      ellipse(xx,yy,r+1,r+1);
      if(i>v.t-14){
        fill(255,240,200,220);
        ellipse(xx,yy,r+4,r+4);
      }
    }else{
      fill(40,130,70,220);
      ellipse(xx,yy,r,r);
      if(noise(v.seed*2+off)>0.78){
        fill(70,170,90,220);
        let s=r*2;
        triangle(xx,yy-s,xx-s,yy,xx+s,yy);
      }
    }
  }
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

for-loop Vine Segment Loop for(let i=0;i<v.t&&i<v.max;i+=4){

Walks up the vine in fixed steps, drawing one segment at a time from the base up to however far the vine has currently grown.

conditional Flower Rendering if(v.isVowel){

Draws a colored petal-like ellipse for vowel vines, with an extra bright highlight ellipse near the growing tip.

conditional Tip Glow Check if(i>v.t-14){

Only draws the bright highlight on segments within the last 14 units of growth, making the flower's growing tip glow.

conditional Leaf/Stem Rendering }else{

Draws green stem dots for consonant vines, occasionally adding a leaf triangle based on noise.

conditional Leaf Spawn Check if(noise(v.seed*2+off)>0.78){

Uses noise to randomly, but consistently per-vine, decide whether a leaf triangle grows at this segment.

for(let i=0;i<v.t&&i<v.max;i+=4){
Steps up the vine in increments of 4, but never beyond the vine's current growth (v.t) or its maximum height (v.max).
let off=i*0.03;
Converts the segment index into a small offset used to sample noise smoothly along the vine's length.
let yy=v.y-i,xx=v.x+(noise(v.seed+off)-0.5)*60;
Calculates this segment's position: yy moves upward as i increases, while xx wobbles left/right based on Perlin noise, creating the organic curve.
let r=map(i,0,v.max,7,1);
Maps the segment's position along the vine to a radius that shrinks from 7 near the base to 1 near the tip, so vines taper as they grow.
if(v.isVowel){
Checks whether this vine was created from a vowel key press, branching into flower rendering.
fill(v.col[0],v.col[1],v.col[2],210);
Sets the fill color to this vine's randomly chosen flower color (pink or gold) with slight transparency.
ellipse(xx,yy,r+1,r+1);
Draws a small colored circle at this segment's position, slightly larger than the base radius.
if(i>v.t-14){
Checks if this segment is within the last 14 units of the vine's current growth - i.e., near the very tip.
fill(255,240,200,220);
Switches to a warm, bright cream color for the highlight.
ellipse(xx,yy,r+4,r+4);
Draws a larger, brighter circle on top to simulate a glowing flower center at the growing tip.
fill(40,130,70,220);
For consonant vines, sets the fill to a muted green for the stem.
ellipse(xx,yy,r,r);
Draws the stem segment as a small green circle.
if(noise(v.seed*2+off)>0.78){
Uses a different noise sample (scaled seed) to decide, segment by segment, whether a leaf should sprout here.
fill(70,170,90,220);
Sets a slightly lighter green for the leaf color.
let s=r*2;
Calculates a leaf size based on the current segment radius.
triangle(xx,yy-s,xx-s,yy,xx+s,yy);
Draws a simple triangle shape to represent a leaf branching out from the stem.

keyTyped()

keyTyped() is a p5.js event function that fires automatically whenever a character key is pressed. It's the perfect place to spawn new objects in response to user input, here creating one vine object per keystroke and storing it in the global vines array for draw() to animate.

🔬 Every new vine spawns at the mouse position with a random max height between 80 and 180. What happens if you replace mouseX,mouseY with width/2,height/2 so all vines grow from the center of the screen regardless of where your mouse is?

  let v={
    x:mouseX,y:mouseY,seed:random(1000),
    isVowel:vowels.indexOf(key)>-1,
    t:0,max:random(80,180),col:[0,0,0]
  };
function keyTyped(){
  if(key.length!==1)return;
  let v={
    x:mouseX,y:mouseY,seed:random(1000),
    isVowel:vowels.indexOf(key)>-1,
    t:0,max:random(80,180),col:[0,0,0]
  };
  if(v.isVowel)v.col=random([[255,150,200],[255,220,120]]);
  vines.push(v);
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Single-Character Filter if(key.length!==1)return;

Ignores special keys like Shift, Enter, or arrow keys (which have longer key names) so only actual typed characters spawn vines.

conditional Vowel Color Assignment if(v.isVowel)v.col=random([[255,150,200],[255,220,120]]);

If the typed key was a vowel, randomly picks either a pink or gold color for the resulting flower vine.

if(key.length!==1)return;
p5's 'key' variable holds the name of the pressed key; special keys like 'Shift' have names longer than one character, so this skips them and only reacts to normal typed characters.
let v={
Creates a new plain JavaScript object to represent this new vine's data - a lightweight alternative to writing a full class.
x:mouseX,y:mouseY,seed:random(1000),
Stores the vine's starting position at the current mouse location, and gives it a random noise seed so every vine curves differently.
isVowel:vowels.indexOf(key)>-1,
Checks if the typed character exists in the vowels string, storing true or false to decide how this vine should be drawn later.
t:0,max:random(80,180),col:[0,0,0]
Sets the growth timer to zero, picks a random maximum height between 80 and 180 pixels, and defaults the color to black (overwritten below for vowels).
if(v.isVowel)v.col=random([[255,150,200],[255,220,120]]);
If this vine is a vowel, randomly assigns it a pink or golden color from a small palette.
vines.push(v);
Adds the new vine object to the end of the vines array so draw() will start animating and rendering it.

windowResized()

windowResized() is a p5.js event function that automatically runs whenever the browser window is resized, letting you keep the canvas responsive to different screen sizes.

function windowResized(){
  resizeCanvas(windowWidth,windowHeight);
  background(5,30,10);
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth,windowHeight);
Resizes the canvas to match the browser window whenever it changes size, keeping the sketch full-screen.
background(5,30,10);
Repaints the background solid dark green after resizing, clearing whatever was drawn before at the old canvas size.

📦 Key Variables

vines array

Holds every active vine object currently growing or lingering on screen; draw() loops through this array each frame to update and render them, and removes finished ones.

let vines=[];
vowels string

A constant string of vowel characters (both cases) used to check whether a typed key should spawn a flower vine or a leafy vine.

let vowels="aeiouAEIOU";

🔧 Potential Improvements (3)

Here are some ways this code could be enhanced:

PERFORMANCE keyTyped() and vines array

Every character typed pushes a new object into the vines array with no upper limit, so a fast typist or someone holding down a key can build up hundreds of active vines before older ones are removed, slowing down draw().

💡 Cap the array with something like 'if(vines.length>150) vines.shift();' after pushing, or skip spawning a new vine if the array is already very large.

STYLE drawVine()

Magic numbers like 60 (sway), 14 (highlight range), 0.78 (leaf threshold), and 4 (step size) are hardcoded inline, making the sketch harder to tune and understand at a glance.

💡 Pull these into named constants near the top of the file, e.g. 'const SWAY=60, TIP_GLOW=14, LEAF_CHANCE=0.78;' and reference them in drawVine() for clearer, more tunable code.

FEATURE keyTyped()

Every vine currently spawns at the mouse position, so typing without moving the mouse stacks many identical vines on top of each other, reducing visual variety.

💡 Add a small random offset to the spawn position (e.g. x:mouseX+random(-20,20)) or chain new vines from the tip of the previously typed vine so words visually grow into connected clusters.

🔄 Code Flow

Code flow showing setup, draw, drawvine, keytyped, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> fadebackground[fade-background] draw --> vineloop[vine-loop] vineloop --> vinecleanup[vine-cleanup] vinecleanup --> draw draw --> segmentloop[segment-loop] segmentloop --> vowelbranch[vowel-branch] vowelbranch --> tiphighlight[tip-highlight] segmentloop --> consonantbranch[consonant-branch] consonantbranch --> leafspawn[leaf-spawn] click setup href "#fn-setup" click draw href "#fn-draw" click fadebackground href "#sub-fade-background" click vineloop href "#sub-vine-loop" click vinecleanup href "#sub-vine-cleanup" click segmentloop href "#sub-segment-loop" click vowelbranch href "#sub-vowel-branch" click tiphighlight href "#sub-tip-highlight" click consonantbranch href "#sub-consonant-branch" click leafspawn href "#sub-leaf-spawn" draw --> keytyped[keytyped] keytyped --> keyfilter[key-filter] keyfilter --> vowelcolorcheck[vowel-color-check] click keytyped href "#fn-keytyped" click keyfilter href "#sub-key-filter" click vowelcolorcheck href "#sub-vowel-color-check" draw --> windowresized[windowresized] click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual elements are produced by the AI Vine Writer sketch?

The sketch visually creates organic vines that grow upward with each keystroke, featuring colorful flowers for vowels and green leaves for consonants.

How can users engage with the AI Vine Writer sketch?

Users can interact by typing on their keyboard, which causes the vines to sprout and grow in response to their input.

What creative coding technique is showcased in this p5.js sketch?

This sketch demonstrates noise-based movement and dynamic visual growth patterns, allowing for an organic representation of typed characters.

Preview

AI Vine Writer - Type to Grow Organic Vines - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Vine Writer - Type to Grow Organic Vines - Code flow showing setup, draw, drawvine, keytyped, windowresized
Code Flow Diagram