模式 简单
Debounced Search
Search input pattern with debounce timing, loading state, and stale-request result protection.
在 Lab 中打开
MCP
vanilla-js css
目标: JS HTML
代码
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #070c17;
color: #e2e8f0;
font-family: "Sora", system-ui, sans-serif;
}
.shell {
width: min(700px, calc(100% - 2rem));
margin: 2rem auto;
}
label {
display: grid;
gap: 0.4rem;
color: #94a3b8;
font-size: 0.85rem;
}
input {
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 10px;
background: #10182b;
color: #e2e8f0;
padding: 0.6rem;
}
#state {
min-height: 1.2rem;
font-size: 0.85rem;
color: #93c5fd;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.55rem;
}
li {
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 10px;
background: #111c30;
padding: 0.65rem;
}(() => {
const DATA = [
"billing portal",
"dashboard metrics",
"error tracking",
"event ingestion",
"feature flags",
"git workflow",
"incident report",
"knowledge base",
"live updates",
"message queue",
"notification center",
"payment retries",
"query planner",
"release notes",
"search analytics",
"session replay",
"status page",
"team invites",
];
const input = document.getElementById("search");
const state = document.getElementById("state");
const results = document.getElementById("results");
let debounceId = 0;
let requestToken = 0;
const render = (items) => {
results.innerHTML = "";
for (const item of items) {
const li = document.createElement("li");
li.textContent = item;
results.appendChild(li);
}
};
const search = (query, token) =>
new Promise((resolve) => {
setTimeout(
() => {
const normalized = query.trim().toLowerCase();
const data = normalized
? DATA.filter((item) => item.includes(normalized)).slice(0, 8)
: DATA.slice(0, 8);
resolve({ token, data });
},
260 + Math.random() * 280
);
});
const run = async () => {
const query = input.value;
const token = ++requestToken;
state.textContent = "Searching...";
const response = await search(query, token);
if (response.token !== requestToken) return;
render(response.data);
state.textContent = response.data.length
? `${response.data.length} result${response.data.length === 1 ? "" : "s"}`
: "No results";
};
input.addEventListener("input", () => {
clearTimeout(debounceId);
debounceId = window.setTimeout(run, 300);
});
run();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Debounced Search</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="shell">
<h1>Debounced Search</h1>
<label>
Search
<input id="search" type="search" placeholder="Type a keyword" autocomplete="off" />
</label>
<p id="state" role="status" aria-live="polite"></p>
<ul id="results"></ul>
</main>
<script src="script.js"></script>
</body>
</html>Debounced Search
A clean search pattern that reduces request churn and prevents out-of-order responses from overriding newer results.
Features
- 300ms input debounce
- Loading and empty states
- Request token stale-guard
- Fast keyboard interaction