Stacking Shapes
Overview
The Stacking Shapes component is an interactive physics-driven experience built with Matter.js that features draggable brand shapes, a logo sprite, sound effects, and a scoring system. It creates an engaging, playful experience perfect for 404 pages or other interactive sections of the By Default website.
Features
- Interactive Physics: Draggable shapes with realistic physics simulation
- Logo Integration: SVG logo as a physics body with custom rendering
- Sound System: Collision-based audio feedback with user controls
- Scoring System: Progress tracking with win condition at 100%
- Responsive Design: Scales appropriately across different screen sizes
- Accessibility: Keyboard navigation and screen reader support
- Performance Optimized: Pauses when tab is hidden, cleans up on unload
File Structure
stacking-shapes/
stacking-shapes.css - Styling for canvas, modal, and UI elements
stacking-shapes.js - Main physics engine implementation
stacking-shapes-1.js - Backup version 1
stacking-shapes-2.js - Backup version 2
Technical Stack
- Matter.js 0.20.x - 2D physics engine
- pathseg polyfill - SVG path compatibility
- Web Audio API - Sound generation
- Canvas 2D - Rendering
- LocalStorage - Sound preference persistence
HTML Structure
Required Elements
<!-- Main container -->
<div id="stacking-shapes-canvas"></div>
<!-- Score HUD -->
<div class="stacking-shapes-score-bar">
<span id="stacking-shapes-score-fill" style="width:0%"></span>
</div>
<div id="stacking-shapes-score-number">0%</div>
<button id="stacking-shapes-score-reset">Reset</button>
<!-- Sound Controls -->
<input type="checkbox" id="stacking-shapes-sound-toggle" />
<span class="stacking-shapes-voice-control">Sound : On</span>
<!-- Modal -->
<div id="stacking-shapes-media-modal" class="stacking-shapes-modal" aria-hidden="true">
<div class="stacking-shapes-modal__dialog">
<button id="stacking-shapes-modal-close">Close</button>
<div id="stacking-shapes-modal-body"></div>
</div>
</div>
<!-- SVG Paths (hidden) -->
<svg width="0" height="0" style="position:absolute;left:-9999px;">
<path id="shape1" d="..." />
<path id="shape2" d="..." />
<path id="shape3" d="..." />
<path id="shape4" d="..." />
<path id="logo-shape" d="..." />
</svg>
Shape Configuration
Adding New Shapes
- Add SVG Path: Create a new
<path>element in the hidden SVG - Update JavaScript: Add shape to the
shapesarray ininitHeroPhysics()
// In initStackingShapes() function
window.heroPhysics.shapes = [
bodyFromPath("shape1", innerWidth * 0.25, innerHeight * 0.25, "#094C45", "one", "#news"),
bodyFromPath("shape2", innerWidth * 0.5, innerHeight * 0.3, "#F7A3BC", "two", "#founders"),
bodyFromPath("shape3", innerWidth * 0.75, innerHeight * 0.3, "#FFB533", "three", "#work"),
bodyFromPath("shape4", innerWidth * 0.15, innerHeight * 0.15, "#88D3CD", "four", "#about"),
// Add your new shape here
bodyFromPath("shape5", innerWidth * 0.6, innerHeight * 0.4, "#FF6B6B", "five", "#contact"),
];
Shape Properties
bodyFromPath(id, x, y, fill, label, link)
- id: SVG path element ID
- x, y: Initial position coordinates
- fill: Hex color code (e.g., "#094C45")
- label: Text displayed on the shape
- link: Optional URL for click actions
Physics Properties
Shapes can be customized with different physics properties:
{
restitution: 0.6, // Bounciness (0-1)
frictionAir: 0.02, // Air resistance
render: {
fillStyle: "#094C45", // Color
strokeStyle: "transparent"
}
}
Logo Customization
Changing the Logo
- Update SVG Path: Modify the
#logo-shapepath element - Update Image Source: Change the logo image URL in
addLogoSprite()
// In addLogoSprite() function
logoImage.src = "path/to/your/logo.svg";
Logo Positioning
Adjust logo offset and scaling:
// Logo image offset adjustments
const offsetX = -3; // Move left/right
const offsetY = -24; // Move up/down
// Original body dimensions (adjust if needed)
const originalBodyWidth = 370; // Half of viewBox width
const originalBodyHeight = 176; // Half of viewBox height
Sound System
Sound Configuration
Sounds are generated using Web Audio API with configurable parameters:
// In stackingShapesPlayPop() function
const speed = Math.min(Math.max(impact, 0), 10);
const peak = 0.01 + 0.02 * (speed / 10); // Volume
const pitch = 100 + 10 * speed; // Frequency
Sound Triggers
- Collision Speed: Minimum 2.0 units for sound
- Cooldown: 300ms between sounds per body
- User Interaction: First gesture enables audio context
Customizing Sound
To modify sound characteristics:
// In stackingShapesPlayPop() function
osc.type = "triangle"; // Wave type: sine, square, triangle, sawtooth
gain.gain.exponentialRampToValueAtTime(peak, t + 0.01); // Attack
gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.16); // Decay
Scoring System
How Scoring Works
- Collision-based: Only shape-to-shape collisions score
- Speed threshold: Minimum 2.2 units relative velocity
- Cooldown: 600ms per collision pair
- Interaction window: 4 seconds after user interaction
- Win condition: 100% score triggers victory modal
Score Configuration
// In initScoreSystem() function
const MIN_SPEED_FOR_SCORE = 2.2; // Minimum collision speed
const SCORE_COOLDOWN_MS = 600; // Time between scores
const inc = Math.min(2, 0.2 + speed * 0.2); // Score increment
Responsive Design
Scaling System
The hero uses a responsive scaling system based on a 1280x800 base:
const BASE_W = 1280, BASE_H = 800;
function getResponsiveScale() {
const s = Math.min(innerWidth / BASE_W, innerHeight / BASE_H);
return Math.max(0.45, Math.min(1.2, s)); // Min 45%, Max 120%
}
Responsive Behavior
- Shapes scale uniformly with screen size
- Walls rebuild on resize
- Shapes reposition within safe margins
- Logo scales proportionally
Mobile Considerations
- Touch support: Drag shapes with touch
- Scroll prevention:
touch-action: pan-yallows vertical scroll - Performance: Pauses when tab hidden
Accessibility Features
Keyboard Navigation
- Voice control: Space/Enter to toggle sound
- Modal escape: Escape key closes modals
- Focus management: Proper focus handling
Screen Reader Support
<!-- Score announcements -->
<div class="stacking-shapes-score-bar" aria-live="polite">
<span id="stacking-shapes-score-fill"></span>
</div>
<!-- Modal accessibility -->
<div id="stacking-shapes-media-modal" aria-hidden="true">
<button id="stacking-shapes-modal-close" aria-label="Close">Close</button>
</div>
Performance Optimization
Lifecycle Management
- Initialization: Only runs once, guards against double-init
- Pause/Resume: Stops when tab hidden, resumes when visible
- Cleanup: Properly destroys engine on page unload
Memory Management
// Cleanup function
function cleanupStackingShapes() {
if (window.stackingShapes.engine) {
Matter.Runner.stop(window.stackingShapes.runner);
Matter.Render.stop(window.stackingShapes.render);
Matter.Engine.clear(window.stackingShapes.engine);
// Reset all references
}
}
Performance Settings
// Fixed timestep for consistent physics
window.stackingShapes.runner = Runner.create({
isFixed: true,
delta: 1000/60 // 60 FPS
});
Troubleshooting
Common Issues
Shapes not appearing:
- Check SVG paths exist with correct IDs
- Verify Matter.js library loaded
- Check browser console for errors
Sound not working:
- Ensure user has interacted with page
- Check sound toggle state
- Verify Web Audio API support
Performance issues:
- Check if tab is hidden (physics pause)
- Monitor memory usage
- Verify cleanup on navigation
Responsive problems:
- Check viewport meta tag
- Verify canvas resize handling
- Test on different screen sizes
Integration with Webflow
Webflow-Specific Considerations
- Modal content: Webflow handles modal content management
- Responsive breakpoints: Align with Webflow's responsive system
- Asset management: Use Webflow's asset URLs for images
- Custom code: Place in Webflow's custom code sections
Deployment Checklist
- Matter.js and pathseg libraries loaded
- All required HTML elements present
- SVG paths properly defined
- CSS custom properties set
- Sound toggle functional
- Modal system working
- Responsive scaling tested
- Accessibility features verified