This chapter teaches techniques for making MicroSims adapt to different screen sizes and container widths. You will learn to use windowResized() and updateCanvasSize() functions to handle browser resizing, work with container width for responsive calculations, and implement relative positioning instead of absolute coordinates. The chapter covers the push(), pop(), and translate() functions for element grouping and coordinate translation, enabling you to create MicroSims that work well on both narrow textbook pages and fullscreen displays.
Concepts Covered
This chapter covers the following 11 concepts from the learning graph:
Want to know one of the quickest ways to tell a real professional from a rookie? Just resize the browser window.
Watch what happens to the interface. Do the elements move gracefully together, like dancers on a stage, flowing smoothly as the space contracts and expands? Or do they remain frozen like statues, rigidly pinned to the left edge of a vast, empty landscape?
That simple test reveals everything about how much care went into the design. Amateur MicroSims look acceptable at one specific window size—the size they were designed on—but fall apart the moment the user resizes their browser or views them on a different device. Professional MicroSims adapt fluidly to any container, maintaining proportions, keeping elements aligned, and ensuring the experience remains excellent regardless of screen dimensions.
The Responsive Mindset
Every time you place an element on the canvas, ask yourself: "Will this still look right if the window is twice as wide? Half as wide?" If the answer is no, you're using the wrong positioning strategy.
The key to this elegant dance lies in implementing responsive rules—patterns where components intelligently change their positions, sizes, and relationships as the window is resized. This chapter will teach you to think responsively and create MicroSims that work beautifully everywhere.
Understanding Responsive Width
Responsive width is the principle that your MicroSim's horizontal dimension should adapt to its container rather than remaining fixed. In the context of educational content, your MicroSim might appear in:
A narrow sidebar (300-400 pixels)
A textbook page with margins (500-700 pixels)
A fullscreen presentation (1920+ pixels)
Display Context
Typical Width
Challenge
Mobile phone
320-480px
Extreme compression
Textbook column
500-700px
Standard educational context
Tablet
768-1024px
Medium flexibility
Desktop sidebar
300-400px
Narrow constraint
Fullscreen
1200-1920px
Expanded space
A truly responsive MicroSim handles all of these gracefully. The core technique involves calculating positions and sizes relative to the current canvas width rather than using fixed pixel values.
Fixed vs. Responsive Positioning
Consider placing a title centered at the top of your canvas:
Fixed positioning (rookie approach):
1
text("Simulation Title",200,30);// Works only if canvas is ~400px wide
Responsive positioning (professional approach):
1
text("Simulation Title",width/2,margin);// Centered at any width
The difference seems small, but it cascades through every element in your simulation. Use fixed values, and your entire layout breaks when conditions change. Use relative values, and the layout maintains its relationships at any size.
Container Width: Knowing Your Boundaries
Container width refers to the actual width available to your MicroSim in its embedding context. When a MicroSim is embedded in an iframe within a textbook page, the container might be narrower than you expect. Your code must detect this and adapt.
Detecting Container Width
1 2 3 4 5 6 7 8 9101112
functionupdateCanvasSize(){// Get the parent container's widthletcontainer=document.querySelector('main');if(container){canvasWidth=container.offsetWidth;}else{canvasWidth=windowWidth;// Fallback to full window}// Enforce minimum and maximum widthscanvasWidth=constrain(canvasWidth,300,1200);}
The Container Detection Pattern
1 2 3 4 5 6 7 8 9101112131415161718
letcanvasWidth=400;// Default widthletdrawHeight=350;letcontrolHeight=50;functionsetup(){updateCanvasSize();createCanvas(canvasWidth,drawHeight+controlHeight);// Position canvas in the documentletcanvas=document.querySelector('canvas');canvas.parentElement.style.display='flex';canvas.parentElement.style.justifyContent='center';}functionupdateCanvasSize(){letcontainer=document.querySelector('main');canvasWidth=container?container.offsetWidth:windowWidth;canvasWidth=constrain(canvasWidth,300,1200);}
This pattern ensures your MicroSim never exceeds its container and never shrinks below a usable minimum.
The windowResized() Function
The windowResized() function is a p5.js event handler that fires automatically whenever the browser window changes size. This is your hook for triggering responsive adjustments.
p5.js catches it and calls your windowResized() function
You recalculate the canvas size
You call resizeCanvas() to update the canvas dimensions
You reposition any controls (sliders, buttons)
You trigger a redraw() to render with new dimensions
Step
Function
Purpose
1
windowResized()
Event handler triggered
2
updateCanvasSize()
Calculate new dimensions
3
resizeCanvas()
Update canvas element
4
repositionControls()
Move sliders and buttons
5
redraw()
Render the updated layout
Debouncing Resize Events
During active resizing, the browser fires many resize events in rapid succession. To avoid performance issues, you can debounce—wait until resizing stops before recalculating:
1 2 3 4 5 6 7 8 91011
letresizeTimer;functionwindowResized(){clearTimeout(resizeTimer);resizeTimer=setTimeout(function(){updateCanvasSize();resizeCanvas(canvasWidth,drawHeight+controlHeight);repositionControls();redraw();},100);// Wait 100ms after last resize event}
The updateCanvasSize() Function
The updateCanvasSize() function is a custom helper that centralizes all canvas dimension calculations. This pattern keeps your resize logic organized and maintainable.
Complete updateCanvasSize() Implementation
1 2 3 4 5 6 7 8 91011121314151617181920212223
functionupdateCanvasSize(){// Get container widthletcontainer=document.querySelector('main');canvasWidth=container?container.offsetWidth:windowWidth;// Apply constraintscanvasWidth=constrain(canvasWidth,300,1200);// Calculate dependent valuescanvasHeight=drawHeight+controlHeight;// Update margins relative to widthmargin=canvasWidth*0.05;// 5% margins// Update font sizes relative to widthtitleSize=constrain(canvasWidth/20,14,32);labelSize=constrain(canvasWidth/30,10,16);// Calculate drawing area boundariesleftBound=margin;rightBound=canvasWidth-margin;drawableWidth=rightBound-leftBound;}
Calculating Relative Sizes
The key insight is that many visual properties should scale with canvas width:
1 2 3 4 5 6 7 8 910
// Font sizeslettitleSize=map(canvasWidth,300,1200,18,36);letlabelSize=map(canvasWidth,300,1200,10,16);// Element sizesletbuttonWidth=canvasWidth*0.15;// 15% of canvas widthletcircleRadius=min(canvasWidth,drawHeight)*0.1;// 10% of smaller dimension// SpacingletelementGap=canvasWidth*0.02;// 2% spacing between elements
<summary>Responsive Calculation Flow</summary>
Type: diagram
Purpose: Show how window resize triggers cascading recalculations (Bloom: Understand)
Layout: Top-down flowchart
Components:
1. Trigger (top):
- "Browser Resize Event" (rounded rectangle, blue)
2. Event Handler:
- "windowResized()" function box
3. Calculations (middle section):
- "updateCanvasSize()" box containing:
- "Get container width"
- "Apply constraints (min/max)"
- "Calculate margins"
- "Calculate font sizes"
- "Calculate positions"
4. Updates (bottom section):
- "resizeCanvas()" - update HTML canvas element
- "repositionControls()" - move DOM elements
- "redraw()" - render new layout
Flow arrows:
- Browser Event → windowResized()
- windowResized() → updateCanvasSize()
- updateCanvasSize() → resizeCanvas()
- resizeCanvas() → repositionControls()
- repositionControls() → redraw()
Annotations:
- "Fires many times during active resize" near trigger
- "Consider debouncing for performance" as side note
- "All visual elements use updated values" near redraw
Color scheme:
- Event trigger: blue
- Calculation functions: orange
- Update functions: green
- Arrows: gray
Implementation: Mermaid.js or static SVG
Relative Positioning
Relative positioning means placing elements based on proportional relationships rather than fixed coordinates. Every x and y value should be expressed as a function of canvas dimensions or other elements.
The Relative Positioning Mindset
Instead of thinking "place this at (200, 150)", think:
"Place this at 50% of the width, 40% of the height"
"Place this 20 pixels from the right edge"
"Place this below the previous element with 10% spacing"
Common Relative Positioning Patterns
1 2 3 4 5 6 7 8 9101112131415161718
// Center horizontallyletcenterX=width/2;// Center vertically in drawing arealetcenterY=drawHeight/2;// Right-aligned with marginletrightX=width-margin;// Bottom of drawing arealetbottomY=drawHeight-margin;// Percentage positioningletquarterX=width*0.25;letthreeQuarterY=drawHeight*0.75;// Relative to another elementletlabel2Y=label1Y+lineHeight*1.5;
Positioning Controls Responsively
Sliders and buttons need special attention because they're DOM elements with their own positioning:
1 2 3 4 5 6 7 8 91011121314
functionrepositionControls(){// Position slider to fill available spaceletsliderLeft=sliderLabelWidth+margin;letsliderWidth=canvasWidth-sliderLeft-valueDisplayWidth-margin;speedSlider.position(sliderLeft,drawHeight+15);speedSlider.size(sliderWidth);// Position buttons evenly spacedletbuttonSpacing=canvasWidth/4;startButton.position(buttonSpacing*1-30,drawHeight+10);pauseButton.position(buttonSpacing*2-30,drawHeight+10);resetButton.position(buttonSpacing*3-30,drawHeight+10);}
<summary>Responsive vs Fixed Positioning Comparison</summary>
Type: microsim
Learning objective: Demonstrate visually how responsive positioning adapts to different widths while fixed positioning fails (Bloom: Understand, Analyze)
Canvas layout:
- Drawing area: responsive width × 300 pixels
- Control area: 50 pixels
Visual elements:
- Split view showing two layouts side by side:
- Left half: "Fixed Positioning" example
- Right half: "Responsive Positioning" example
- Both halves show identical elements:
- Title text
- Three circles in a row
- Labels below circles
- Value display in corner
Fixed side behavior:
- Title at position (200, 30) - absolute
- Circles at (100, 150), (200, 150), (300, 150) - absolute
- Labels at fixed positions
- As width changes: elements clip, overlap, or have huge gaps
Responsive side behavior:
- Title at (width/4, 30) - centered in half
- Circles at (width*0.1, 150), (width*0.25, 150), (width*0.4, 150) - proportional
- Labels relative to circles
- As width changes: elements maintain proportional spacing
Interactive controls:
- Slider: "Canvas Width" (400-1000px)
- Live width display showing current value
- "Reset to 600px" button
Behavior:
- Dragging slider resizes canvas width
- Fixed side becomes increasingly broken at extreme widths
- Responsive side maintains proportions throughout
- Annotations highlight problems as they occur
Educational annotations:
- When width < 500: "Fixed elements overlap!" on left side
- When width > 800: "Fixed elements lost in empty space!" on left side
- Continuous: "Responsive elements maintain relationships" on right side
Color scheme:
- Fixed side: red-tinted background (warning)
- Responsive side: green-tinted background (success)
- Elements: blue circles, black labels
Implementation: p5.js with slider controlling canvas width
Dynamic Scaling
Dynamic scaling extends relative positioning to element sizes. Not only should positions adapt, but the visual elements themselves should scale appropriately.
When to Scale Elements
Element Type
Scaling Approach
Text (titles)
Scale with canvas width
Text (labels)
Scale with canvas width, with min/max constraints
Shapes (decorative)
Scale proportionally
Shapes (functional)
May have minimum sizes for usability
Line weights
Scale subtly or remain constant
Margins/padding
Scale with canvas width
Scaling Calculations
1 2 3 4 5 6 7 8 910111213141516
functioncalculateScaledSizes(){// Scale factor based on canvas widthletscaleFactor=canvasWidth/600;// 600 is "reference" width// Title scales from 18px (at 300px) to 36px (at 1200px)titleSize=map(canvasWidth,300,1200,18,36);// Circles scale but maintain minimum sizecircleRadius=max(20,canvasWidth*0.08);// Stroke weight scales subtlystrokeWeight(max(1,scaleFactor*2));// Text sizes with constraintslabelSize=constrain(canvasWidth/30,12,18);}
The Reference Width Pattern
Design your MicroSim at a "reference width" (commonly 600px), then scale all values from that baseline:
The translate() function shifts the entire coordinate system to a new origin. Instead of calculating complex offsets for every element, you translate once and then draw using simple local coordinates.
How translate() Works
1 2 3 4 5 6 7 8 910
// Without translate - calculating positions manuallyrect(200+10,150+10,80,60);ellipse(200+50,150+40,20,20);text("Label",200+10,150+80);// With translate - cleaner codetranslate(200,150);rect(10,10,80,60);ellipse(50,40,20,20);text("Label",10,80);
After translate(200, 150), the point (0, 0) is now at what was (200, 150). All subsequent drawing commands use coordinates relative to this new origin.
translate() for Responsive Groups
The real power emerges when moving entire groups of elements:
1 2 3 4 5 6 7 8 91011121314151617181920
// Draw a control panel that can be positioned anywherefunctiondrawControlPanel(x,y){translate(x,y);// All elements drawn relative to (0, 0) which is now at (x, y)fill(240);rect(0,0,150,100);fill(0);textSize(14);text("Controls",10,20);// Button at local positionfill(100,150,255);rect(10,40,60,25);// Labelfill(255);text("Start",25,58);}
Element Grouping with push() and pop()
The push() and pop() functions create a transformation stack that isolates coordinate changes and style settings. This is essential for clean, modular responsive code.
The push()/pop() Pattern
1 2 3 4 5 6 7 8 9101112131415161718192021
functiondraw(){background(240);// Draw first grouppush();translate(50,50);fill(255,0,0);rect(0,0,100,100);// Red square at (50, 50)pop();// Coordinate system restored// Draw second group - unaffected by previous translatepush();translate(200,50);fill(0,0,255);rect(0,0,100,100);// Blue square at (200, 50)pop();// We're back at original coordinatesfill(0);text("Original origin",10,200);}
What push() and pop() Save
The push() function saves the current:
Transformation matrix (translate, rotate, scale)
Fill color
Stroke color
Stroke weight
Text size, alignment, font
Tint
Blend mode
When you call pop(), all of these are restored to their previous values.
Nested Groups
Groups can be nested for complex hierarchies:
1 2 3 4 5 6 7 8 91011121314151617181920212223
functiondraw(){push();translate(width/2,height/2);// Center of canvaspush();rotate(angle);translate(100,0);// Orbit radiusfill(255,200,0);ellipse(0,0,30,30);// Sunpop();push();rotate(angle*2);translate(50,0);fill(100,100,255);ellipse(0,0,20,20);// Planetpop();pop();// Back to original coordinatesdrawUI();}
<summary>push/pop Coordinate Stack Visualization</summary>
Type: microsim
Learning objective: Visualize how push() and pop() manage the transformation stack (Bloom: Understand, Analyze)
Canvas layout:
- Drawing area: 500x350 pixels
- Control area: 50 pixels
Visual elements:
- Left side: Interactive canvas showing shapes being drawn
- Right side: Stack visualization showing transformation states
Stack visualization:
- Vertical stack of "cards" representing saved states
- Each card shows: (x, y) origin, rotation, fill color
- Cards animate sliding on/off the stack
- Current state highlighted at top
Interactive features:
- "Execute" button steps through code line by line
- "Reset" returns to initial state
- "Auto Play" runs through code automatically
- Speed slider for auto play
Code display:
- Shows sample code with current line highlighted
- push() lines turn green when executed
- pop() lines turn red when executed
- translate/fill/rect lines turn blue
Sample code to execute:
```
push(); // Save state 1
translate(100, 50);
fill(255, 0, 0);
rect(0, 0, 40, 40);
push(); // Save state 2
translate(50, 50);
fill(0, 255, 0);
rect(0, 0, 30, 30);
pop(); // Restore state 2
fill(0, 0, 255);
rect(50, 0, 35, 35);
pop(); // Restore state 1
rect(0, 0, 50, 50);
```
Behavior:
- Each step shows which line executes
- Canvas updates to show drawn shapes
- Stack visualization updates to show saves/restores
- Origin marker moves to show current coordinate system
Educational annotations:
- "push() saves current state to stack"
- "pop() restores most recent saved state"
- "Notice how origin jumps back after pop()"
Color scheme:
- Stack cards: light blue with dark borders
- Current line: yellow highlight
- Origin marker: purple crosshairs
Implementation: p5.js with step-through animation
Coordinate Translation in Practice
Combining translate() with push()/pop() creates a powerful pattern for responsive element grouping.
functiondrawGroup(x,y,groupWidth,groupHeight){push();translate(x,y);// All coordinates relative to group origin (0, 0)fill(245);stroke(200);rect(0,0,groupWidth,groupHeight);// Title centered at top of groupfill(0);noStroke();textAlign(CENTER,TOP);textSize(14);text("Group Title",groupWidth/2,5);// Content arealetcontentTop=25;letcontentHeight=groupHeight-contentTop-10;stroke(220);noFill();rect(5,contentTop,groupWidth-10,contentHeight);pop();}
Responsive Two-Column Layout
Using groups enables sophisticated responsive layouts:
1 2 3 4 5 6 7 8 910111213141516171819202122
functiondraw(){background(240,248,255);// Calculate responsive column widthsletgap=10;letcolumnWidth=(canvasWidth-gap*3)/2;// Left column - Simulationpush();translate(gap,gap);drawSimulationPanel(columnWidth,drawHeight-gap*2);pop();// Right column - Data Displaypush();translate(gap*2+columnWidth,gap);drawDataPanel(columnWidth,drawHeight-gap*2);pop();// Control region spans full widthdrawControlRegion();}
Moving Groups Responsively
The beauty of this approach: to move an entire group, you only change the translate() call:
1 2 3 4 5 6 7 8 9101112
functiondraw(){// Calculate responsive positionletpanelX=canvasWidth*0.6;// 60% from leftletpanelY=drawHeight*0.1;// 10% from topletpanelW=canvasWidth*0.35;// 35% of widthletpanelH=drawHeight*0.8;// 80% of heightpush();translate(panelX,panelY);drawInfoPanel(panelW,panelH);// Everything inside uses local coordspop();}
Complete Responsive MicroSim Pattern
Here's a complete implementation combining all the techniques:
functiondrawResponsiveButtonRow(buttons,y){letbuttonCount=buttons.length;lettotalGap=canvasWidth*0.1;// 10% for gapsletbuttonWidth=(canvasWidth-totalGap)/buttonCount;letgap=totalGap/(buttonCount+1);for(leti=0;i<buttonCount;i++){letx=gap+i*(buttonWidth+gap);buttons[i].position(x,y);buttons[i].size(buttonWidth-10,30);}}
All x values use width, canvasWidth, or percentages
Sizes scale
Text and shapes scale with canvas dimensions
Controls reposition
Sliders and buttons adapt in windowResized()
Minimum width respected
MicroSim remains usable at 300-400px
Maximum width respected
MicroSim doesn't over-expand at 1200+px
Groups use push/pop
Related elements are isolated in transformation blocks
No fixed magic numbers
All pixel values derive from responsive calculations
Key Takeaways
You've learned the responsive design techniques that separate professional MicroSims from amateur ones:
windowResized() triggers responsive recalculation when the browser window changes size.
updateCanvasSize() centralizes all canvas dimension calculations for maintainability.
Container width detection ensures your MicroSim adapts to its embedding context.
Relative positioning expresses all coordinates as proportions of canvas dimensions.
Responsive width allows horizontal adaptation while maintaining vertical consistency.
Dynamic scaling adjusts element sizes proportionally to canvas dimensions.
translate() shifts the coordinate origin, simplifying position calculations.
push() and pop() create isolated transformation contexts for modular drawing.
Element grouping combines translate with push/pop for movable component groups.
Coordinate translation enables complex layouts with simple local coordinates.
All techniques combine to create MicroSims that work beautifully at any size.
Challenge: Make the Dancers Dance
Take a MicroSim you've created previously and apply responsive design techniques. Test it by resizing the window through the full range (300px to 1200px). Do your elements dance gracefully together, or do they stand frozen like statues? Fix any issues you find using the patterns from this chapter.
Next Steps
You've mastered the art of responsive design—your MicroSims can now dance gracefully on any stage, from narrow mobile screens to expansive desktop displays. In the next chapter, we'll explore animation and physics, bringing your responsive layouts to life with smooth motion, realistic physics, and engaging visual dynamics.
Remember: the resize test doesn't lie. When your elements flow together like coordinated dancers rather than standing rigid like forgotten statues, you've achieved responsive design mastery.