3-terrarium/3-intro-to-DOM-and-closures/README.md
journey
title Your JavaScript DOM Journey
section Foundation
Understand DOM: 3: Student
Learn closures: 4: Student
Connect elements: 4: Student
section Interaction
Setup drag events: 4: Student
Track coordinates: 5: Student
Handle movement: 5: Student
section Polish
Add cleanup: 4: Student
Test functionality: 5: Student
Complete terrarium: 5: Student
Sketchnote by Tomomi Imura
Welcome to one of the most engaging aspects of web development - making things interactive! The Document Object Model (DOM) is like a bridge between your HTML and JavaScript, and today we'll use it to bring your terrarium to life. When Tim Berners-Lee created the first web browser, he envisioned a web where documents could be dynamic and interactive - the DOM makes that vision possible.
We'll also explore JavaScript closures, which might sound intimidating initially. Think of closures as creating "memory pockets" where your functions can remember important information. It's like each plant in your terrarium having its own data record to track its position. By the end of this lesson, you'll understand how natural and useful they are.
Here's what we're building: a terrarium where users can drag and drop plants anywhere they want. You'll learn the DOM manipulation techniques that power everything from drag-and-drop file uploads to interactive games. Let's make your terrarium come alive.
mindmap
root((DOM & JavaScript))
DOM Tree
Element Selection
Property Access
Event Handling
Dynamic Updates
Events
Pointer Events
Mouse Events
Touch Events
Event Listeners
Closures
Private Variables
Function Scope
Memory Persistence
State Management
Drag & Drop
Position Tracking
Coordinate Math
Event Lifecycle
User Interaction
Modern Patterns
Event Delegation
Performance
Cross-Device
Accessibility
The Document Object Model (DOM) is how JavaScript communicates with your HTML elements. When your browser loads an HTML page, it creates a structured representation of that page in memory - that's the DOM. Think of it as a family tree where every HTML element is a family member that JavaScript can access, modify, or rearrange.
DOM manipulation transforms static pages into interactive websites. Every time you see a button change color on hover, content update without page refresh, or elements you can drag around, that's DOM manipulation at work.
flowchart TD
A["Document"] --> B["HTML"]
B --> C["Head"]
B --> D["Body"]
C --> E["Title"]
C --> F["Meta Tags"]
D --> G["H1: My Terrarium"]
D --> H["Div: Page Container"]
H --> I["Div: Left Container"]
H --> J["Div: Right Container"]
H --> K["Div: Terrarium"]
I --> L["Plant Elements 1-7"]
J --> M["Plant Elements 8-14"]
L --> N["img#plant1"]
L --> O["img#plant2"]
M --> P["img#plant8"]
M --> Q["img#plant9"]
style A fill:#e1f5fe
style B fill:#f3e5f5
style D fill:#e8f5e8
style H fill:#fff3e0
style N fill:#ffebee
style O fill:#ffebee
style P fill:#ffebee
style Q fill:#ffebee
A representation of the DOM and the HTML markup that references it. From Olfa Nasraoui
Here's what makes the DOM powerful:
A JavaScript closure is like giving a function its own private workspace with persistent memory. Consider how Darwin's finches on the GalΓ‘pagos Islands each developed specialized beaks based on their specific environment - closures work similarly, creating specialized functions that "remember" their specific context even after their parent function has finished.
In our terrarium, closures help each plant remember its own position independently. This pattern appears throughout professional JavaScript development, making it a valuable concept to understand.
flowchart LR
A["dragElement(plant1)"] --> B["Creates Closure"]
A2["dragElement(plant2)"] --> B2["Creates Closure"]
B --> C["Private Variables"]
B2 --> C2["Private Variables"]
C --> D["pos1, pos2, pos3, pos4"]
C --> E["pointerDrag function"]
C --> F["elementDrag function"]
C --> G["stopElementDrag function"]
C2 --> D2["pos1, pos2, pos3, pos4"]
C2 --> E2["pointerDrag function"]
C2 --> F2["elementDrag function"]
C2 --> G2["stopElementDrag function"]
H["Plant 1 remembers its position"] --> B
H2["Plant 2 remembers its position"] --> B2
style B fill:#e8f5e8
style B2 fill:#e8f5e8
style C fill:#fff3e0
style C2 fill:#fff3e0
π‘ Understanding Closures: Closures are a significant topic in JavaScript, and many developers use them for years before fully grasping all the theoretical aspects. Today, we're focusing on practical application - you'll see closures naturally emerge as we build our interactive features. Understanding will develop as you see how they solve real problems.
A representation of the DOM and the HTML markup that references it. From Olfa Nasraoui
In this lesson, we will complete our interactive terrarium project by creating the JavaScript that will allow a user to manipulate the plants on the page.
You'll need your HTML and CSS files from the previous terrarium lessons - we're about to make that static design interactive. If you're joining for the first time, completing those lessons first will provide important context.
Here's what we'll build:
Let's create the JavaScript file that will make your terrarium interactive.
Step 1: Create your script file
In your terrarium folder, create a new file called script.js.
Step 2: Link the JavaScript to your HTML
Add this script tag to the <head> section of your index.html file:
<script src="./script.js" defer></script>
Why the defer attribute is important:
β οΈ Important Note: The
deferattribute prevents common timing issues. Without it, JavaScript may try to access HTML elements before they're loaded, causing errors.
Before we can make elements draggable, JavaScript needs to locate them in the DOM. Think of this like a library cataloging system - once you have the catalog number, you can find exactly the book you need and access all its contents.
We'll use the document.getElementById() method to make these connections. It's like having a precise filing system - you provide an ID, and it locates exactly the element you need in your HTML.
Add this code to your script.js file:
// Enable drag functionality for all 14 plants
dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
dragElement(document.getElementById('plant4'));
dragElement(document.getElementById('plant5'));
dragElement(document.getElementById('plant6'));
dragElement(document.getElementById('plant7'));
dragElement(document.getElementById('plant8'));
dragElement(document.getElementById('plant9'));
dragElement(document.getElementById('plant10'));
dragElement(document.getElementById('plant11'));
dragElement(document.getElementById('plant12'));
dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));
Here's what this code accomplishes:
dragElement function (which we'll create next)π― Why Use IDs Instead of Classes? IDs provide unique identifiers for specific elements, while CSS classes are designed for styling groups of elements. When JavaScript needs to manipulate individual elements, IDs offer the precision and performance we need.
π‘ Pro Tip: Notice how we're calling
dragElement()for each plant individually. This approach ensures that each plant gets its own independent dragging behavior, which is essential for smooth user interaction.
DOM Connection Understanding: Before moving to drag functionality, verify you can:
document.getElementById() locates HTML elementsdefer attribute in script tagsQuick Self-Test: What would happen if two elements had the same ID? Why does getElementById() return only one element?
Answer: IDs should be unique; if duplicated, only the first element is returned
Now we'll create the heart of our dragging functionality: a closure that manages the dragging behavior for each plant. This closure will contain multiple inner functions that work together to track mouse movements and update element positions.
Closures are perfect for this task because they allow us to create "private" variables that persist between function calls, giving each plant its own independent coordinate tracking system.
Let me demonstrate closures with a simple example that illustrates the concept:
function createCounter() {
let count = 0; // This is like a private variable
function increment() {
count++; // The inner function remembers the outer variable
return count;
}
return increment; // We're giving back the inner function
}
const myCounter = createCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
Here's what's happening in this closure pattern:
count variable that only exists within this closurecreateCounter() finishes execution, count persists and remembers its valueFor our terrarium, each plant needs to remember its current position coordinates. Closures provide the perfect solution:
Key benefits for our project:
π― Learning Goal: You don't need to master every aspect of closures right now. Focus on seeing how they help us organize code and maintain state for our dragging functionality.
stateDiagram-v2
[*] --> Ready: Page loads
Ready --> DragStart: User presses down (pointerdown)
DragStart --> Dragging: Mouse/finger moves (pointermove)
Dragging --> Dragging: Continue moving
Dragging --> DragEnd: User releases (pointerup)
DragEnd --> Ready: Reset for next drag
state DragStart {
[*] --> CapturePosition
CapturePosition --> SetupListeners
SetupListeners --> [*]
}
state Dragging {
[*] --> CalculateMovement
CalculateMovement --> UpdatePosition
UpdatePosition --> [*]
}
state DragEnd {
[*] --> RemoveListeners
RemoveListeners --> CleanupState
CleanupState --> [*]
}
Now let's build the main function that will handle all the dragging logic. Add this function below your plant element declarations:
function dragElement(terrariumElement) {
// Initialize position tracking variables
let pos1 = 0, // Previous mouse X position
pos2 = 0, // Previous mouse Y position
pos3 = 0, // Current mouse X position
pos4 = 0; // Current mouse Y position
// Set up the initial drag event listener
terrariumElement.onpointerdown = pointerDrag;
}
Understanding the position tracking system:
pos1 and pos2: Store the difference between old and new mouse positionspos3 and pos4: Track the current mouse coordinatesterrariumElement: The specific plant element we're making draggableonpointerdown: The event that triggers when the user starts draggingHere's how the closure pattern works:
dragElement functionYou might wonder why we use onpointerdown instead of the more familiar onclick. Here's the reasoning:
| Event Type | Best For | The Catch |
|---|---|---|
onclick | Simple button clicks | Can't handle dragging (just clicks and releases) |
onpointerdown | Both mouse and touch | Newer, but well-supported these days |
onmousedown | Desktop mouse only | Leaves mobile users out in the cold |
Why pointer events are perfect for what we're building:
π‘ Future-Proofing: Pointer events are the modern way to handle user interactions. Instead of writing separate code for mouse and touch, you get both for free. Pretty neat, right?
Event Handling Understanding: Pause to confirm your grasp of events:
preventDefault() play in smooth dragging?Real-World Connection: Think about drag-and-drop interfaces you use daily:
When a user presses down on a plant (whether with a mouse click or finger touch), the pointerDrag function springs into action. This function captures the initial coordinates and sets up the dragging system.
Add this function inside your dragElement closure, right after the line terrariumElement.onpointerdown = pointerDrag;:
function pointerDrag(e) {
// Prevent default browser behavior (like text selection)
e.preventDefault();
// Capture the initial mouse/touch position
pos3 = e.clientX; // X coordinate where drag started
pos4 = e.clientY; // Y coordinate where drag started
// Set up event listeners for the dragging process
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
}
Step by step, here's what's happening:
The e.preventDefault() line is crucial for smooth dragging:
Without prevention, browsers might:
π Experiment: After completing this lesson, try removing
e.preventDefault()and see how it affects the dragging experience. You'll quickly understand why this line is essential!
The e.clientX and e.clientY properties give us precise mouse/touch coordinates:
| Property | What It Measures | Use Case |
|---|---|---|
clientX | Horizontal position relative to the viewport | Tracking left-right movement |
clientY | Vertical position relative to the viewport | Tracking up-down movement |
Understanding these coordinates:
Notice how we attach the move and stop events to the entire document, not just the plant element:
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
Why attach to the document:
β‘ Performance Note: We'll clean up these document-level listeners when dragging stops to avoid memory leaks and performance issues.
Now we'll add the two remaining functions that handle the actual dragging movement and the cleanup when dragging stops. These functions work together to create smooth, responsive plant movement across your terrarium.
Add the elementDrag function right after the closing curly bracket of pointerDrag:
function elementDrag(e) {
// Calculate the distance moved since the last event
pos1 = pos3 - e.clientX; // Horizontal distance moved
pos2 = pos4 - e.clientY; // Vertical distance moved
// Update the current position tracking
pos3 = e.clientX; // New current X position
pos4 = e.clientY; // New current Y position
// Apply the movement to the element's position
terrariumElement.style.top = (terrariumElement.offsetTop - pos2) + 'px';
terrariumElement.style.left = (terrariumElement.offsetLeft - pos1) + 'px';
}
Understanding the coordinate mathematics:
pos1 and pos2: Calculate how far the mouse has moved since the last updatepos3 and pos4: Store the current mouse position for the next calculationoffsetTop and offsetLeft: Get the element's current position on the pagesequenceDiagram
participant User
participant Mouse
participant JavaScript
participant Plant
User->>Mouse: Start drag at (100, 50)
Mouse->>JavaScript: pointerdown event
JavaScript->>JavaScript: Store initial position (pos3=100, pos4=50)
JavaScript->>JavaScript: Setup move/up listeners
User->>Mouse: Move to (110, 60)
Mouse->>JavaScript: pointermove event
JavaScript->>JavaScript: Calculate: pos1=10, pos2=10
JavaScript->>Plant: Update: left += 10px, top += 10px
Plant->>Plant: Render at new position
User->>Mouse: Release at (120, 65)
Mouse->>JavaScript: pointerup event
JavaScript->>JavaScript: Remove listeners
JavaScript->>JavaScript: Reset for next drag
Here's the movement calculation breakdown:
sequenceDiagram
participant Mouse
participant JavaScript
participant Plant
Mouse->>JavaScript: Move from (100,50) to (110,60)
JavaScript->>JavaScript: Calculate: moved 10px right, 10px down
JavaScript->>Plant: Update position by +10px right, +10px down
Plant->>Plant: Render at new position
Add the cleanup function after the closing curly bracket of elementDrag:
function stopElementDrag() {
// Remove the document-level event listeners
document.onpointerup = null;
document.onpointermove = null;
}
Why cleanup is essential:
What happens without cleanup:
Our dragging system manipulates two key CSS properties:
| Property | What It Controls | How We Use It |
|---|---|---|
top | Distance from the top edge | Vertical positioning during drag |
left | Distance from the left edge | Horizontal positioning during drag |
Key insights about offset properties:
offsetTop: Current distance from the top of the positioned parent elementoffsetLeft: Current distance from the left of the positioned parent elementπ― Design Philosophy: This drag system is intentionally flexible β there are no "drop zones" or restrictions. Users can place plants anywhere, giving them complete creative control over their terrarium design.
Congratulations! You've just built a sophisticated drag-and-drop system using vanilla JavaScript. Your complete dragElement function now contains a powerful closure that manages:
What your closure accomplishes:
Now test your interactive terrarium! Open your index.html file in a web browser and try the functionality:
π₯ Achievement: You've created a fully interactive web application using core concepts that professional developers use daily. That drag-and-drop functionality uses the same principles behind file uploads, kanban boards, and many other interactive interfaces.
Complete System Understanding: Verify your mastery of the full drag system:
Code Quality Reflection: Review your complete solution:
Use the Agent mode to complete the following challenge:
Description: Enhance the terrarium project by adding a reset functionality that returns all plants to their original positions with smooth animations.
Prompt: Create a reset button that, when clicked, animates all plants back to their original sidebar positions using CSS transitions. The function should store the original positions when the page loads and smoothly transition plants back to those positions over 1 second when the reset button is pressed.
Learn more about agent mode here.
Ready to take your terrarium to the next level? Try implementing these enhancements:
Creative Extensions:
π‘ Learning Opportunity: Each of these challenges will teach you new aspects of DOM manipulation, event handling, and user experience design.
You've mastered the fundamentals of DOM manipulation and closures, but there's always more to explore! Here are some pathways to expand your knowledge and skills.
We used pointer events for maximum flexibility, but web development offers multiple approaches:
| Approach | Best For | Learning Value |
|---|---|---|
| HTML Drag and Drop API | File uploads, formal drag zones | Understanding native browser capabilities |
| Touch Events | Mobile-specific interactions | Mobile-first development patterns |
CSS transform properties | Smooth animations | Performance optimization techniques |
Next steps in your learning journey:
Technical Documentation:
Browser Compatibility:
Practice Opportunities:
π― Learning Strategy: The best way to solidify these concepts is through practice. Try building variations of draggable interfaces β each project will teach you something new about user interaction and DOM manipulation.
document.querySelector('body') in the consoleinnerHTML or textContenttimeline
title DOM & JavaScript Learning Progression
section Foundation (15 minutes)
DOM Understanding: Element selection methods
: Tree structure navigation
: Property access patterns
section Event Handling (20 minutes)
User Interaction: Pointer event basics
: Event listener setup
: Cross-device compatibility
: Event prevention techniques
section Closures (25 minutes)
Scope Management: Private variable creation
: Function persistence
: State management patterns
: Memory efficiency
section Drag System (30 minutes)
Interactive Features: Coordinate tracking
: Position calculation
: Movement mathematics
: Cleanup procedures
section Advanced Patterns (45 minutes)
Professional Skills: Event delegation
: Performance optimization
: Error handling
: Accessibility considerations
section Framework Understanding (1 week)
Modern Development: Virtual DOM concepts
: State management libraries
: Component architectures
: Build tool integration
section Expert Level (1 month)
Advanced DOM APIs: Intersection Observer
: Mutation Observer
: Custom Elements
: Web Components
After completing this lesson, you now have:
Professional Skills Gained: You've built features using the same techniques as:
Next Level: You're ready to explore modern frameworks like React, Vue, or Angular that build upon these fundamental DOM manipulation concepts!