實作 Google 風格搜尋建議時,常遇到「點擊輸入框外部區域,建議下拉框不自動關閉」的體驗問題。本文提供原生 JavaScript 完整解決方案,並分析事件流機制。
原始碼中:
onblur="losefouse()" 綁定在輸入框,當焦點離開觸發。<li>)時,焦點先離開輸入框觸發 onblur,導致下拉框隱藏,隨後 onclick 才執行 fillin(),造成「點擊無效、需點兩次」或「閃爍」體驗。<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>AJAX 搜尋建議 - 自動關閉修正版</title>
<style>
#livesearch {
position: absolute;
width: 300px;
border: 1px solid #999;
background: #fff;
display: none;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
}
#livesearch ul { margin: 0; padding: 0; list-style: none; }
#livesearch li { padding: 8px 12px; cursor: pointer; }
#livesearch li:hover { background: #f0f0f0; }
</style>
</head>
<body>
<h3>搜尋建議示範</h3>
<div style="position: relative; width: 300px;">
<input type="text" id="txt1" size="30" autocomplete="off" placeholder="輸入關鍵字...">
<div id="livesearch"></div>
</div>
<script>
const input = document.getElementById('txt1');
const suggestBox = document.getElementById('livesearch');
let hideTimer = null;
let isMouseDownOnSuggest = false;
// 1. 輸入框事件
input.addEventListener('input', debounce(doSearch, 200));
input.addEventListener('focus', () => {
if (suggestBox.dataset.hasContent === 'true') suggestBox.style.display = 'block';
});
// 2. 建議框滑鼠事件:阻止輸入框 blur 隱藏
suggestBox.addEventListener('mousedown', (e) => {
isMouseDownOnSuggest = true;
e.preventDefault(); // 關鍵:阻止輸入框失去焦點
});
suggestBox.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (li) {
input.value = li.textContent;
suggestBox.style.display = 'none';
suggestBox.dataset.hasContent = 'false';
}
isMouseDownOnSuggest = false;
});
// 3. 輸入框 blur:延遲隱藏,給 suggestBox click 時間執行
input.addEventListener('blur', () => {
hideTimer = setTimeout(() => {
if (!isMouseDownOnSuggest) {
suggestBox.style.display = 'none';
}
}, 200);
});
// 4. 點擊文檔其他區域關閉
document.addEventListener('click', (e) => {
if (!input.contains(e.target) && !suggestBox.contains(e.target)) {
suggestBox.style.display = 'none';
}
});
// 5. 防抖搜尋
function debounce(fn, delay) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function doSearch() {
const kw = input.value.trim();
if (!kw) {
suggestBox.innerHTML = '';
suggestBox.style.display = 'none';
suggestBox.dataset.hasContent = 'false';
return;
}
// 模擬 AJAX(實際替換為 fetch)
setTimeout(() => {
const mockData = ['蘋果', '蘋果手機', '蘋果筆電', '香蕉', '香蕉牛奶'].filter(s => s.includes(kw));
if (mockData.length) {
suggestBox.innerHTML = '' + mockData.map(s => `- ${s}
`).join('') + '
';
suggestBox.style.display = 'block';
suggestBox.dataset.hasContent = 'true';
} else {
suggestBox.style.display = 'none';
suggestBox.dataset.hasContent = 'false';
}
}, 100);
}
</script>
</body>
</html>
| 技術點 | 作用 |
|---|---|
mousedown + preventDefault() | 阻止瀏覽器預設行為:滑鼠按下建議項時,輸入框不會失去焦點,blur 不會立即觸發 |
blur + setTimeout(200ms) | 延遲隱藏,給 click 事件足夠時間執行 fillin() |
isMouseDownOnSuggest 旗標 | 區分「點擊建議項」(不隱藏)與「點擊其他區域」(隱藏) |
document.click 委派 | 點擊頁面任意非輸入框/建議框區域時強制關閉 |
debounce | 輸入時延遲 200ms 發送請求,避免頻繁查詢 |
position: absolute + z-index | 建議框脫離文檔流,覆蓋在其他內容上方 |
fetch + AbortController:取代舊式 XMLHttpRequest,支援請求取消。keydown 處理 ArrowUp/Down、Enter、Escape,完整鍵盤可及性。role="listbox", aria-expanded, aria-activedescendant,螢幕閱讀器支援。「點擊外部自動關閉」的核心在於**事件順序控制**:用 mousedown preventDefault 阻止焦點轉移,配合 blur 延遲 給予點擊處理時間窗,再以 document.click 委派 覆蓋所有其他點擊場景。此模式適用於所有「下拉選單、自動完成、彈出選單」類 UI 組件。