下拉菜單是網站導航核心組件。本文提供一個零依賴、純 CSS+JS 實現:語義化 HTML 結構、CSS 絕對定位隱藏/顯示、JavaScript 管理鼠標進出延遲計時器,防止誤觸發抖動,兼容性極佳。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* === 基礎樣式 === */
#sddm { margin: 0; padding: 0; list-style: none; font-family: arial; }
#sddm li { float: left; position: relative; }
#sddm li a { display: block; padding: 8px 15px; color: #2875DE; text-decoration: none; background: #EAEBD8; }
#sddm li a:hover { background: #49A3FF; color: #FFF; }
/* === 子菜單面板 === */
#sddm div {
position: absolute;
visibility: hidden;
top: 100%; left: 0;
margin: 0; padding: 0;
background: #EAEBD8;
border: 1px solid #5970B2;
min-width: 160px;
z-index: 100;
}
#sddm div a {
position: relative;
display: block;
padding: 5px 10px;
white-space: nowrap;
color: #2875DE;
background: #EAEBD8;
}
#sddm div a:hover { background: #49A3FF; color: #FFF; }
</style>
</head>
<body>
<ul id="sddm">
<li><a href="/">首頁</a></li>
<li>
<a href="/about" onmouseover="mopen('m1')" onmouseout="mclosetime()">關於我們</a>
<div id="m1" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">公司簡介</a>
<a href="#">企業文化</a>
<a href="#">榮譽資質</a>
<a href="#">發展歷程</a>
</div>
</li>
<li>
<a href="/news" onmouseover="mopen('m2')" onmouseout="mclosetime()">新聞中心</a>
<div id="m2" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">公司新聞</a>
<a href="#">行業動態</a>
<a href="#">媒體報道</a>
</div>
</li>
<li>
<a href="/solution" onmouseover="mopen('m3')" onmouseout="mclosetime()">解決方案</a>
<div id="m3" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">行業方案</a>
<a href="#">技術架構</a>
<a href="#">案例研究</a>
</div>
</li>
<li>
<a href="/product" onmouseover="mopen('m4')" onmouseout="mclosetime()">產品展示</a>
<div id="m4" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">硬體產品</a>
<a href="#">軟體服務</a>
<a href="#">增值服務</a>
</div>
</li>
<li>
<a href="/support" onmouseover="mopen('m5')" onmouseout="mclosetime()">支持與下載</a>
<div id="m5" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">驅動下載</a>
<a href="#">使用手冊</a>
<a href="#">常見問題</a>
<a href="#">工單提交</a>
</div>
</li>
<li>
<a href="/jobs" onmouseover="mopen('m6')" onmouseout="mclosetime()">人才招聘</a>
<div id="m6" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">社招崗位</a>
<a href="#">校招專區</a>
<a href="#">實習生計劃</a>
</div>
</li>
<li>
<a href="/contact" onmouseover="mopen('m7')" onmouseout="mclosetime()">聯系我們</a>
<div id="m7" onmouseover="mcancelclosetime()" onmouseout="mclosetime()">
<a href="#">聯系方式</a>
<a href="#">在線留言</a>
<a href="#">地圖導航</a>
</div>
</li>
</ul>
<script>
// === 狀態管理 ===
var timeout = 500; // 延遲隱藏毫秒數
var closetimer = 0; // 計時器 ID
var ddmenuitem = 0; // 當前顯示的子菜單元素
// 顯示指定子菜單
function mopen(id) {
mcancelclosetime(); // 取消待執行的隱藏
if (ddmenuitem) ddmenuitem.style.visibility = 'hidden'; // 隱藏舊菜單
ddmenuitem = document.getElementById(id);
ddmenuitem.style.visibility = 'visible';
}
// 隱藏當前子菜單
function mclose() {
if (ddmenuitem) ddmenuitem.style.visibility = 'hidden';
}
// 啟動隱藏計時器
function mclosetime() {
closetimer = window.setTimeout(mclose, timeout);
}
// 取消隱藏計時器
function mcancelclosetime() {
if (closetimer) {
window.clearTimeout(closetimer);
closetimer = null;
}
}
// 點擊頁面其他區域關閉菜單
document.onclick = mclose;
</script>
</body>
</html>
| 事件 | 觸發元素 | 處理函數 | 作用 |
|---|---|---|---|
| mouseover | 主菜單 <a> | mopen(id) | 取消隱藏計時、隱藏舊菜單、顯示目標菜單 |
| mouseout | 主菜單 <a> | mclosetime() | 啟動 500ms 計時器,延後隱藏 |
| mouseover | 子菜單 <div> | mcancelclosetime() | 鼠標移入子菜單區域,取消隱藏計時 |
| mouseout | 子菜單 <div> | mclosetime() | 鼠標移出子菜單,重新啟動計時 |
| click | document | mclose() | 點擊空白處強制關閉 |
這套「進入即顯、離開延遲、子區域保護」邏輯,解決了菜單項間縫隙導致的閃爍、誤關閉等經典體驗問題。
li:hover > div { visibility: visible; } 實現,無 JS 依賴,但無法控制延遲與點擊外部關閉。role="menubar"、aria-haspopup="true"、aria-expanded、鍵盤 Tab/Enter/Esc 支持。@media (max-width: 768px) 切換佈局。<DropdownMenu :items="menuData" />,狀態內建、可複用、可測試。<?php
// 假設從數據庫讀取菜單結構
$menu = [
['name' => '關於我們', 'url' => '/about', 'children' => [
['name' => '公司簡介', 'url' => '/about/profile'],
['name' => '企業文化', 'url' => '/about/culture'],
]],
['name' => '新聞中心', 'url' => '/news', 'children' => [
['name' => '公司新聞', 'url' => '/news/company'],
['name' => '行業動態', 'url' => '/news/industry'],
]],
// ...
];
function renderMenu($items, $level = 0) {
$idPrefix = $level === 0 ? 'm' : 'sub';
static $counter = 0;
echo $level === 0 ? '<ul id="sddm">' : '<ul class="sub-menu">';
foreach ($items as $item) {
$counter++;
$menuId = $idPrefix . $counter;
$hasChildren = !empty($item['children']);
echo '<li>';
$attrs = $hasChildren ? "onmouseover=\"mopen('$menuId')\" onmouseout=\"mclosetime()\"" : '';
echo "<a href=\"{$item['url']}\" $attrs>{$item['name']}</a>";
if ($hasChildren) {
echo "<div id=\"$menuId\" onmouseover=\"mcancelclosetime()\" onmouseout=\"mclosetime()\">";
renderMenu($item['children'], $level + 1);
echo '</div>';
}
echo '</li>';
}
echo '</ul>';
}
renderMenu($menu);
?>
從靜態代碼到動態渲染,從桌面端到響應式,下拉菜單的演進映照著前端工程化的歷程。掌握原生實現原理,才能在選型組件庫時心中有數、不被綁架。