浏览器级“实时搜索联想”前端实现总结
浏览器级“实时搜索联想”前端实现总结
1. 目标
-
搜索框的联想词不是本地写死数组,而是像 Chrome 地址栏一样,实时调用搜索引擎官方建议接口。
-
点击/回车后跳转到真正的搜索结果页(百度/Google/Bing 等)。
2. 核心思路
-
监听输入框
input事件。 -
动态拼接搜索引擎的建议 API(JSONP 或 CORS-JSON)。
-
返回结果 → 渲染下拉列表。
-
用户确认 →
window.open(_blank)在新标签打开搜索结果。
3. 主流建议接口速查
| 引擎 | 接口(GET) | 返回格式 | 备注 |
| ---------- | ------------------------------------------------------------ | --------- | ------------------ |
| 百度 | https://suggestion.baidu.com/su?wd=关键词&cb=xxx | JSONP | 无限制,最稳定 |
| Google | https://www.google.com/complete/search?client=firefox&q=关键词 | JSON | 部分地区需科学上网 |
| Bing | https://www.bing.com/AS/Suggestions?pt=page.home&mkt=zh-cn&qry=关键词 | HTML 片段 | 需解析 DOM |
4. 最小可运行示例(百度版)
把下面代码保存为 index.html 即可直接体验。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<title>实时搜索联想 Demo</title>
<style>
* { box-sizing: border-box; }
body { font: 16px/1.5 -apple-system, Roboto; background: #f5f5f5; margin: 0; }
.search-box { position: relative; width: 520px; margin: 80px auto; }
#searchInput { width: 100%; padding: 10px 15px; font-size: 16px; border: 1px solid #bbb; border-radius: 4px; outline: none; }
#searchBtn { position: absolute; right: -80px; top: 0; width: 80px; height: 100%; border: 1px solid #bbb; border-left: 0; border-radius: 0 4px 4px 0; background: #0084ff; color: #fff; cursor: pointer; }
#searchBtn:hover { background: #006acc; }
.suggest { position: absolute; left: 0; right: 0; top: 100%; margin: 0; padding: 0; list-style: none; background: #fff; border: 1px solid #bbb; border-top: 0; max-height: 300px; overflow-y: auto; z-index: 999; }
.suggest li { padding: 8px 15px; cursor: pointer; }
.suggest li:hover, .suggest li.active { background: #f0f0f0; }
</style>
</head>
<body>
<div class="search-box">
<input id="searchInput" type="text" autocomplete="off" placeholder="输入关键词,实时获取百度联想" />
<button id="searchBtn">搜索</button>
<ul id="suggestList" class="suggest"></ul>
</div>
<script>
const input = document.getElementById('searchInput');
const btn = document.getElementById('searchBtn');
const list = document.getElementById('suggestList');
let idx = -1;
const cache = {};
input.addEventListener('input', onInput);
input.addEventListener('keydown', onKey);
btn.addEventListener('click', () => doSearch(input.value.trim()));
function onInput() {
const kw = input.value.trim();
if (!kw) return hideList();
if (cache[kw]) return renderList(cache[kw]);
const script = document.createElement('script');
script.src = `https://suggestion.baidu.com/su?wd=${encodeURIComponent(kw)}&cb=handleBaiduSuggest`;
document.body.appendChild(script);
script.onload = () => script.remove();
}
window.handleBaiduSuggest = function (res) {
const arr = res.s || [];
cache[res.q] = arr;
renderList(arr);
};
function renderList(arr) {
if (!arr.length) return hideList();
list.innerHTML = '';
arr.forEach((text, i) => {
const li = document.createElement('li');
li.textContent = text;
li.addEventListener('click', () => {
input.value = text;
doSearch(text);
});
list.appendChild(li);
});
list.style.display = 'block';
idx = -1;
}
function hideList() { list.style.display = 'none'; }
function onKey(e) {
const items = list.querySelectorAll('li');
if (!items.length) return;
if (e.key === 'ArrowDown') { idx = Math.min(idx + 1, items.length - 1); updateActive(items); }
else if (e.key === 'ArrowUp') { idx = Math.max(idx - 1, -1); updateActive(items); }
else if (e.key === 'Enter') {
if (idx >= 0) input.value = items[idx].textContent;
doSearch(input.value.trim());
} else if (e.key === 'Escape') { hideList(); }
}
function updateActive(items) { items.forEach((li, i) => li.classList.toggle('active', i === idx)); }
function doSearch(kw) {
if (!kw) return;
hideList();
window.open('https://www.baidu.com/s?wd=' + encodeURIComponent(kw), '_blank');
}
</script>
</body>
</html>
---
*本文由[萧兮的博客](https://www.20010515.xyz)原创发布,欢迎转载,转载务必保留原文链接。*
**萧兮的博客**:[https://www.20010515.xyz](https://www.20010515.xyz) · 原文:[https://www.20010515.xyz/posts/84a12786-bb99-4e82-a141-e0f05a2f7979](https://www.20010515.xyz/posts/84a12786-bb99-4e82-a141-e0f05a2f7979)