实时列表搜索
输入关键词即时筛选城市,并高亮匹配结果。
启用 JavaScript 后可编辑、运行和保存此案例。固定源码如下:
<!doctype html>
<html lang="zh-Hans" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>实时列表搜索</title>
<style>
* { box-sizing: border-box; }
body { min-height: 100vh; margin: 0; padding: clamp(28px, 6vw, 70px) 20px; color: #242f35; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: #e8f0f0; }
main { width: min(850px, 100%); margin: auto; }
header { margin-bottom: 28px; }
.eyebrow { color: #d25c42; font-size: 11px; font-weight: 900; letter-spacing: .18em; }
h1 { margin: 10px 0 10px; font: 700 clamp(38px, 8vw, 68px)/1 Georgia, "STSong", serif; }
header p { margin: 0; color: #718087; }
.search { display: flex; gap: 10px; padding: 9px; border: 1px solid #cad6d7; border-radius: 17px; background: #fff; box-shadow: 0 16px 38px rgba(46,68,72,.08); }
.search span { display: grid; width: 42px; place-items: center; color: #d25c42; font-size: 22px; }
input { min-width: 0; flex: 1; border: 0; color: #243138; font: inherit; font-size: 17px; outline: 0; }
input::placeholder { color: #9aa7aa; }
.clear { padding: 10px 15px; border: 0; border-radius: 10px; color: #66777c; font: inherit; font-weight: 750; background: #edf2f2; cursor: pointer; }
.meta { display: flex; justify-content: space-between; gap: 16px; margin: 20px 2px 12px; color: #718087; font-size: 12px; }
.meta strong { color: #d25c42; }
.list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
article { display: grid; grid-template-columns: 48px 1fr auto; gap: 14px; align-items: center; padding: 16px; border: 1px solid #d3ddde; border-radius: 16px; background: rgba(255,255,255,.75); transition: transform .18s ease, background .18s ease; }
article:hover { transform: translateY(-3px); background: #fff; }
.code { display: grid; width: 48px; height: 48px; place-items: center; border-radius: 13px; color: #fff; font: 800 12px/1 Georgia, serif; background: var(--color); }
h2 { margin: 0 0 4px; font: 700 19px/1.2 Georgia, "STSong", serif; }
article p { margin: 0; color: #849196; font-size: 12px; }
.temperature { color: #53636a; font-size: 13px; font-weight: 800; }
mark { padding: 1px 2px; color: inherit; background: #ffd7a3; border-radius: 3px; }
.empty { grid-column: 1 / -1; padding: 58px 20px; border: 1px dashed #b9c8ca; border-radius: 18px; color: #718087; text-align: center; }
@media (max-width: 650px) { .list { grid-template-columns: 1fr; } }
@media (max-width: 420px) { article { grid-template-columns: 42px 1fr; } .temperature { grid-column: 2; } }
@media (prefers-reduced-motion: reduce) { article { transition: none; } }
</style>
</head>
<body>
<main>
<header><span class="eyebrow">CITY WEATHER INDEX</span><h1>今天去哪里?</h1><p>搜索城市名、拼音或省份,列表会即时更新。</p></header>
<div class="search"><span aria-hidden="true">⌕</span><input id="query" type="search" aria-label="搜索城市" placeholder="例如:杭州、chengdu、广东" autocomplete="off"><button id="clear" class="clear" type="button">清空</button></div>
<div class="meta"><span>匹配城市</span><span><strong id="count">0</strong> 个结果</span></div>
<section id="list" class="list" aria-live="polite"></section>
</main>
<script>
const cities = [
{ name: '北京', pinyin: 'beijing', province: '北京', code: 'BJ', weather: '晴朗', temp: '29°', color: '#d86a45' },
{ name: '上海', pinyin: 'shanghai', province: '上海', code: 'SH', weather: '多云', temp: '31°', color: '#547c99' },
{ name: '杭州', pinyin: 'hangzhou', province: '浙江', code: 'HZ', weather: '阵雨', temp: '28°', color: '#4d8b72' },
{ name: '成都', pinyin: 'chengdu', province: '四川', code: 'CD', weather: '阴', temp: '26°', color: '#8a7753' },
{ name: '厦门', pinyin: 'xiamen', province: '福建', code: 'XM', weather: '晴朗', temp: '30°', color: '#3e8e9b' },
{ name: '青岛', pinyin: 'qingdao', province: '山东', code: 'QD', weather: '海风', temp: '25°', color: '#4672a1' },
{ name: '广州', pinyin: 'guangzhou', province: '广东', code: 'GZ', weather: '雷雨', temp: '32°', color: '#9b5e70' },
{ name: '大理', pinyin: 'dali', province: '云南', code: 'DL', weather: '晴朗', temp: '23°', color: '#687a43' }
];
const queryInput = document.querySelector('#query');
const list = document.querySelector('#list');
const count = document.querySelector('#count');
function appendHighlighted(target, text, query) {
const index = text.toLocaleLowerCase().indexOf(query);
if (!query || index < 0) { target.textContent = text; return; }
target.append(document.createTextNode(text.slice(0, index)));
const mark = document.createElement('mark');
mark.textContent = text.slice(index, index + query.length);
target.append(mark, document.createTextNode(text.slice(index + query.length)));
}
function createCard(city, query) {
const article = document.createElement('article');
const code = document.createElement('span'); code.className = 'code'; code.style.setProperty('--color', city.color); code.textContent = city.code;
const info = document.createElement('div');
const title = document.createElement('h2'); appendHighlighted(title, city.name, query);
const detail = document.createElement('p'); appendHighlighted(detail, `${city.province} · ${city.pinyin} · ${city.weather}`, query);
const temp = document.createElement('span'); temp.className = 'temperature'; temp.textContent = city.temp;
info.append(title, detail); article.append(code, info, temp); return article;
}
function render() {
const query = queryInput.value.trim().toLocaleLowerCase();
const matches = cities.filter(city => `${city.name} ${city.pinyin} ${city.province}`.toLocaleLowerCase().includes(query));
list.replaceChildren(...matches.map(city => createCard(city, query)));
count.textContent = String(matches.length);
if (!matches.length) { const empty = document.createElement('p'); empty.className = 'empty'; empty.textContent = '没有找到匹配城市,试试更短的关键词。'; list.append(empty); }
}
queryInput.addEventListener('input', render);
document.querySelector('#clear').addEventListener('click', () => { queryInput.value = ''; queryInput.focus(); render(); });
render();
</script>
</body>
</html>