<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debouncing and Throttling Demo</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.demo-container {
background: white;
border-radius: 10px;
padding: 25px;
margin: 20px 0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h2 {
color: #333;
border-bottom: 3px solid #4CAF50;
padding-bottom: 10px;
margin-bottom: 20px;
}
.example-box {
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
margin: 15px 0;
background: #fafafa;
}
.input-demo {
margin: 20px 0;
}
input[type="text"] {
width: 100%;
padding: 12px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
input[type="text"]:focus {
outline: none;
border-color: #4CAF50;
}
.counter-display {
display: flex;
gap: 30px;
margin: 20px 0;
justify-content: space-around;
}
.counter-box {
flex: 1;
text-align: center;
padding: 20px;
border-radius: 8px;
background: #f5f5f5;
border: 2px solid #ddd;
}
.counter-box.normal {
border-color: #FF5722;
background: #FFF3E0;
}
.counter-box.debounced {
border-color: #2196F3;
background: #E3F2FD;
}
.counter-box.throttled {
border-color: #9C27B0;
background: #F3E5F5;
}
.counter-value {
font-size: 36px;
font-weight: bold;
margin: 10px 0;
}
.counter-label {
font-size: 18px;
font-weight: bold;
}
.visual-demo {
margin: 30px 0;
}
.timeline {
position: relative;
height: 60px;
background: #f0f0f0;
border-radius: 5px;
margin: 20px 0;
overflow: hidden;
}
.timeline-event {
position: absolute;
width: 3px;
height: 100%;
background: #FF5722;
transition: left 0.1s;
}
.timeline-event.executed {
background: #4CAF50;
width: 5px;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 12px 24px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin: 10px 5px;
transition: background 0.3s;
}
button:hover {
background: #45a049;
}
button:active {
transform: scale(0.98);
}
.code-example {
background: #263238;
color: #ECEFF1;
padding: 20px;
border-radius: 8px;
margin: 15px 0;
font-family: 'Courier New', monospace;
overflow-x: auto;
line-height: 1.6;
}
.keyword {
color: #82B1FF;
}
.function {
color: #C3E88D;
}
.string {
color: #F07178;
}
.number {
color: #F78C6C;
}
.comment {
color: #546E7A;
font-style: italic;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 20px 0;
}
.concept-box {
background: white;
padding: 20px;
border-radius: 8px;
border: 2px solid #e0e0e0;
}
.concept-box h3 {
margin-top: 0;
color: #333;
}
.real-example {
background: #E8F5E9;
padding: 15px;
border-radius: 5px;
margin: 10px 0;
border-left: 4px solid #4CAF50;
}
.scroll-demo {
height: 200px;
overflow-y: scroll;
border: 2px solid #ddd;
border-radius: 5px;
padding: 20px;
background: white;
margin: 20px 0;
}
.scroll-content {
height: 1000px;
background: linear-gradient(to bottom, #E3F2FD, #F3E5F5);
padding: 20px;
position: relative;
}
.mouse-area {
height: 200px;
background: #f0f0f0;
border: 2px dashed #999;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: crosshair;
margin: 20px 0;
position: relative;
}
.mouse-tracker {
position: absolute;
padding: 5px 10px;
background: #333;
color: white;
border-radius: 5px;
font-size: 12px;
pointer-events: none;
}
.event-log {
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
height: 150px;
overflow-y: auto;
font-family: monospace;
font-size: 14px;
margin: 10px 0;
}
.log-entry {
padding: 2px 0;
}
.log-entry.normal {
color: #FF5722;
}
.log-entry.debounced {
color: #2196F3;
}
.log-entry.throttled {
color: #9C27B0;
}
.visual-explanation {
background: #FFF3E0;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
border: 2px solid #FFB74D;
}
.diagram {
text-align: center;
margin: 20px 0;
font-family: monospace;
font-size: 14px;
line-height: 1.8;
}
</style>
</head>
<body>
<div class="header">
<h1>🎯 Debouncing vs Throttling</h1>
<p style="font-size: 18px; color: #666;">Control how often your functions run!</p>
</div>
<!-- Concept Introduction -->
<div class="demo-container">
<h2>📚 What Are They?</h2>
<div class="comparison-grid">
<div class="concept-box">
<h3>🛑 Debouncing</h3>
<p><strong>Wait until user stops, then execute</strong></p>
<div class="real-example">
<strong>Real Life:</strong> Elevator doors - they wait until everyone stops entering before closing
</div>
<p>Perfect for: Search boxes, form validation, save drafts</p>
</div>
<div class="concept-box">
<h3>⏱️ Throttling</h3>
<p><strong>Execute regularly, but limit frequency</strong></p>
<div class="real-example">
<strong>Real Life:</strong> Subway trains - they leave every 5 minutes, no matter what
</div>
<p>Perfect for: Scroll events, resize events, API rate limiting</p>
</div>
</div>
</div>
<!-- Interactive Demo 1: Search Input -->
<div class="demo-container">
<h2>🔍 Demo 1: Search Input</h2>
<p>Type in the search box and see how each approach handles it:</p>
<div class="input-demo">
<input type="text" id="searchInput" placeholder="Type to search..." />
<div class="counter-display">
<div class="counter-box normal">
<div class="counter-label">❌ Normal</div>
<div class="counter-value" id="normalCount">0</div>
<div>API calls</div>
</div>
<div class="counter-box debounced">
<div class="counter-label">✅ Debounced (500ms)</div>
<div class="counter-value" id="debouncedCount">0</div>
<div>API calls</div>
</div>
<div class="counter-box throttled">
<div class="counter-label">✅ Throttled (1s)</div>
<div class="counter-value" id="throttledCount">0</div>
<div>API calls</div>
</div>
</div>
<div class="event-log" id="searchLog"></div>
</div>
</div>
<!-- Visual Explanation -->
<div class="demo-container">
<h2>👁️ Visual Difference</h2>
<div class="visual-explanation">
<h3>Debouncing Timeline</h3>
<div class="diagram">
User types: H E L L O (stops) |--500ms--| → Execute!
↓ ↓ ↓ ↓ ↓ ↑
X X X X X ✓
(cancelled) (runs)
</div>
</div>
<div class="visual-explanation" style="background: #F3E5F5; border-color: #BA68C8;">
<h3>Throttling Timeline</h3>
<div class="diagram">
User types: H E L L O W O R L D
↓ ↓ ↓ ↓ ↓
✓ X ✓ X ✓
|--1s--| |--1s--| |--1s--|
(runs) (runs) (runs)
</div>
</div>
</div>
<!-- Button Click Demo -->
<div class="demo-container">
<h2>🖱️ Demo 2: Button Spam Protection</h2>
<p>Click the buttons rapidly to see the difference:</p>
<div class="example-box">
<button onclick="handleNormalClick()">Normal Button</button>
<button onclick="handleDebouncedClick()">Debounced Button (2s)</button>
<button onclick="handleThrottledClick()">Throttled Button (1s)</button>
<div class="counter-display" style="margin-top: 20px;">
<div class="counter-box normal">
<div class="counter-label">Normal Clicks</div>
<div class="counter-value" id="normalClickCount">0</div>
</div>
<div class="counter-box debounced">
<div class="counter-label">Debounced Actions</div>
<div class="counter-value" id="debouncedClickCount">0</div>
</div>
<div class="counter-box throttled">
<div class="counter-label">Throttled Actions</div>
<div class="counter-value" id="throttledClickCount">0</div>
</div>
</div>
</div>
</div>
<!-- Mouse Move Demo -->
<div class="demo-container">
<h2>🖱️ Demo 3: Mouse Tracking</h2>
<p>Move your mouse in the area below:</p>
<div class="mouse-area" id="mouseArea">
<span style="color: #999;">Move mouse here</span>
<div class="mouse-tracker" id="mouseTracker" style="display: none;"></div>
</div>
<div class="counter-display">
<div class="counter-box normal">
<div class="counter-label">Normal Events</div>
<div class="counter-value" id="mouseMoveCount">0</div>
</div>
<div class="counter-box throttled">
<div class="counter-label">Throttled (100ms)</div>
<div class="counter-value" id="mouseThrottledCount">0</div>
</div>
</div>
</div>
<!-- Code Examples -->
<div class="demo-container">
<h2>📝 Simple Implementation</h2>
<h3>Debounce Function</h3>
<div class="code-example">
<span class="comment">// Simple Debounce Implementation</span>
<span class="keyword">function</span> <span class="function">debounce</span>(func, delay) {
<span class="keyword">let</span> timeoutId;
<span class="keyword">return</span> <span class="keyword">function</span>(...args) {
<span class="comment">// Clear previous timeout if user is still typing</span>
clearTimeout(timeoutId);
<span class="comment">// Set new timeout</span>
timeoutId = setTimeout(() => {
func.apply(<span class="keyword">this</span>, args);
}, delay);
};
}
<span class="comment">// Usage Example</span>
<span class="keyword">const</span> search = <span class="function">debounce</span>((text) => {
console.log(<span class="string">'Searching for:'</span>, text);
}, <span class="number">500</span>);
<span class="comment">// This will only execute once, 500ms after last call</span>
search(<span class="string">'H'</span>);
search(<span class="string">'He'</span>);
search(<span class="string">'Hello'</span>); <span class="comment">// Only this executes!</span>
</div>
<h3>Throttle Function</h3>
<div class="code-example">
<span class="comment">// Simple Throttle Implementation</span>
<span class="keyword">function</span> <span class="function">throttle</span>(func, limit) {
<span class="keyword">let</span> inThrottle;
<span class="keyword">return</span> <span class="keyword">function</span>(...args) {
<span class="keyword">if</span> (!inThrottle) {
func.apply(<span class="keyword">this</span>, args);
inThrottle = <span class="keyword">true</span>;
setTimeout(() => {
inThrottle = <span class="keyword">false</span>;
}, limit);
}
};
}
<span class="comment">// Usage Example</span>
<span class="keyword">const</span> updatePosition = <span class="function">throttle</span>((x, y) => {
console.log(<span class="string">'Position:'</span>, x, y);
}, <span class="number">1000</span>);
<span class="comment">// Only executes once per second maximum</span>
updatePosition(<span class="number">10</span>, <span class="number">20</span>); <span class="comment">//
Executes</span>
updatePosition(<span class="number">15</span>, <span class="number">25</span>); <span class="comment">//
Skipped</span>
updatePosition(<span class="number">20</span>, <span class="number">30</span>); <span class="comment">//
Skipped</span>
</div>
</div>
<!-- Real World Examples -->
<div class="demo-container">
<h2>🌍 Real World Examples</h2>
<div class="comparison-grid">
<div class="concept-box">
<h3>Debouncing Use Cases</h3>
<ul>
<li><strong>Search Box:</strong> Wait until user stops typing</li>
<li><strong>Auto-save:</strong> Save after user stops editing</li>
<li><strong>Form Validation:</strong> Validate after user finishes</li>
<li><strong>Window Resize:</strong> Recalculate after resize ends</li>
</ul>
</div>
<div class="concept-box">
<h3>Throttling Use Cases</h3>
<ul>
<li><strong>Scroll Events:</strong> Check position every 100ms</li>
<li><strong>API Rate Limiting:</strong> Max 1 request per second</li>
<li><strong>Game Movements:</strong> Update position at fixed rate</li>
<li><strong>Progress Updates:</strong> Update UI periodically</li>
</ul>
</div>
</div>
</div>
<!-- When to Use What -->
<div class="demo-container">
<h2>🤔 Which One to Use?</h2>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<th style="border: 1px solid #ddd; padding: 12px; background: #f2f2f2;">Scenario</th>
<th style="border: 1px solid #ddd; padding: 12px; background: #f2f2f2;">Use Debounce</th>
<th style="border: 1px solid #ddd; padding: 12px; background: #f2f2f2;">Use Throttle</th>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 12px;">Search suggestions</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">✅</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">❌</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 12px;">Infinite scroll</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">❌</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">✅</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 12px;">Button click (prevent spam)</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">✅</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">❌</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 12px;">Mouse position tracking</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">❌</td>
<td style="border: 1px solid #ddd; padding: 12px; text-align: center;">✅</td>
</tr>
</table>
</div>
<script>
// Counters
let normalCount = 0;
let debouncedCount = 0;
let throttledCount = 0;
let normalClickCount = 0;
let debouncedClickCount = 0;
let throttledClickCount = 0;
let mouseMoveCount = 0;
let mouseThrottledCount = 0;
// Debounce implementation
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Throttle implementation
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// Search input handlers
const searchInput = document.getElementById('searchInput');
const searchLog = document.getElementById('searchLog');
function addLogEntry(text, type = 'normal') {
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
searchLog.insertBefore(entry, searchLog.firstChild);
// Keep only last 5 entries
while (searchLog.children.length > 5) {
searchLog.removeChild(searchLog.lastChild);
}
}
// Normal search
function normalSearch(value) {
normalCount++;
document.getElementById('normalCount').textContent = normalCount;
addLogEntry(`Normal: Searching "${value}"`, 'normal');
}
// Debounced search
const debouncedSearch = debounce((value) => {
debouncedCount++;
document.getElementById('debouncedCount').textContent = debouncedCount;
addLogEntry(`Debounced: Searching "${value}"`, 'debounced');
}, 500);
// Throttled search
const throttledSearch = throttle((value) => {
throttledCount++;
document.getElementById('throttledCount').textContent = throttledCount;
addLogEntry(`Throttled: Searching "${value}"`, 'throttled');
}, 1000);
// Search input event
searchInput.addEventListener('input', (e) => {
const value = e.target.value;
normalSearch(value);
debouncedSearch(value);
throttledSearch(value);
});
// Button click handlers
function handleNormalClick() {
normalClickCount++;
document.getElementById('normalClickCount').textContent = normalClickCount;
}
const handleDebouncedClick = debounce(() => {
debouncedClickCount++;
document.getElementById('debouncedClickCount').textContent = debouncedClickCount;
}, 2000);
const handleThrottledClick = throttle(() => {
throttledClickCount++;
document.getElementById('throttledClickCount').textContent = throttledClickCount;
}, 1000);
// Mouse tracking
const mouseArea = document.getElementById('mouseArea');
const mouseTracker = document.getElementById('mouseTracker');
function updateMousePosition(e) {
const rect = mouseArea.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
mouseMoveCount++;
document.getElementById('mouseMoveCount').textContent = mouseMoveCount;
mouseTracker.style.display = 'block';
mouseTracker.style.left = x + 'px';
mouseTracker.style.top = y + 'px';
mouseTracker.textContent = `${Math.round(x)}, ${Math.round(y)}`;
}
const throttledMouseMove = throttle((e) => {
mouseThrottledCount++;
document.getElementById('mouseThrottledCount').textContent = mouseThrottledCount;
}, 100);
mouseArea.addEventListener('mousemove', (e) => {
updateMousePosition(e);
throttledMouseMove(e);
});
mouseArea.addEventListener('mouseleave', () => {
mouseTracker.style.display = 'none';
});
</script>
</body>
</html>
Imagine you're in an elevator. The door doesn't close every time someone presses the button. It waits until everyone stops pressing, THEN closes.
Like a bus that leaves every 10 minutes. It doesn't matter if 1 or 100 people are waiting - it leaves on schedule.
No comments:
Post a Comment