简介:这是一套聚焦网页导航交互的HTML菜单源码集,主要面向前端开发者和网页设计爱好者,既适合初学者学习菜单结构,也能帮助有经验者快速搭建项目导航。压缩包共38个文件,约481KB,涵盖HTML、CSS、JavaScript、jQuery、Less与Scss等类型;其中Less和Scss提供可调变量,方便二次定制主题,JS和jQuery负责下拉、滑动、状态切换等动态效果,同时集成Font Awesome字体图标,可增强菜单的视觉表现力。已有2595人学习下载。源码包含纯CSS和预处理器双版本,便于对照理解预处理语法;通过实际阅读,还能掌握响应式处理、平滑过渡、事件绑定等前端实用技巧。整套包内页面与样式文件组织清晰,便于按需取用和二次开发,适合作为个人项目参考或导航交互练习的起点。
1. 这套HTML菜单源码能帮你省掉什么:从静态页到后台模板的复用逻辑
拿到任何一套HTML菜单源码,我第一件事不是看样式有多漂亮,而是看它的数据是怎么进来的。很多菜单模板做得花团锦簇,但菜单项写死在HTML里,换一套后台数据就得重改页面,这种源码再好看我也只能归档吃灰。这套源码不一样的地方在于,它把菜单项抽成了由jQuery读取的数据结构,页面结构、样式、行为三层拆开,你在静态页里能跑起来,换到Java后台模板里照样能渲染。
它解决的是三个具体问题:一是多级菜单的展开/收起状态管理,二是当前菜单项的高亮联动,三是菜单项数据与页面结构的解耦。适合谁用?前端刚入门想参考菜单交互写法的,后端工程师要往模板里嵌导航的,还有做后台管理系统需要现成侧边栏骨架的。下面我从源码结构讲起,逐步拆到部署、样式定制和排坑。
2. 先看懂菜单源码的骨架:DOM结构、CSS状态与jQuery渲染逻辑
2.1 源码里最核心的三种菜单形态:手风琴、下拉悬浮、侧栏折叠
这套源码里其实包含了三种常见菜单形态,你需要按场景挑着用。第一种是手风琴式侧栏菜单,点击一级菜单展开二级菜单,同时收起其他同级菜单,适合后台管理系统的左侧导航。第二种是hover触发的下拉菜单,鼠标移上去就弹出子菜单,适合网站顶部导航。第三种是折叠式菜单,支持点击图标把整个侧栏收成一条窄边,只显示图标,这是后台系统最常用的形态。
三种形态在源码里不是三套独立文件,而是同一套DOM结构配合不同的CSS状态类实现的。核心结构就是一个嵌套的ul列表,一级菜单挂.has-sub类表示有子菜单,子菜单用.sub-menu包一层,展开时给父级加.open,高亮时给当前项加.active。看懂这套类名约定,你就能在不改JS的情况下控制菜单行为。
2.2 jQuery是怎么把静态数据“喂”给菜单的
菜单项数据在源码里是通过一个配置对象传入的,这是整套源码最值得参考的部分。它的原理是:你维护一份菜单数据数组,jQuery读取后动态生成DOM,而不是在HTML里手工写死一堆ul和li。这样后台返回什么菜单结构,前端就渲染什么结构。
看一段核心渲染逻辑的简化写法:
var menuData = [ { title: '仪表盘', icon: 'icon-dashboard', url: 'index.html' }, { title: '系统管理', icon: 'icon-set', children: [ { title: '用户管理', url: 'user.html' }, { title: '权限配置', url: 'permission.html', children: [ { title: '角色管理', url: 'role.html' } ]} ] } ]; function buildMenu(data, parentEl) { var ul = $('<ul>').addClass('nav-menu'); $.each(data, function (index, item) { var li = $('<li>'); if (item.children && item.children.length > 0) { li.addClass('has-sub'); var a = $('<a>').text(item.title).attr('href', 'javascript:;'); a.append($('<span>').addClass('arrow')); li.append(a); var subUl = buildMenu(item.children); li.append(subUl); } else { var a = $('<a>').text(item.title).attr('href', item.url); li.append(a); } ul.append(li); }); parentEl.append(ul); return ul; } buildMenu(menuData, $('#sidebar'));这里item.title是菜单显示文字,item.children表示子菜单数组,递归调用buildMenu生成嵌套结构。注意叶子节点和父节点的渲染逻辑是分开的,父节点带箭头,叶子节点绑实际链接。参数层面的要点有三个:url字段支持javascript:;这类空链接,方便纯前端演示;icon字段预留了图标类名,样式定制时会用到;数据结构里允许多级嵌套,理论上无限层级都能渲染,但实际超过三级就要考虑交互负担了。
2.3 文件清单与对应关系
拿到源码包后,建议先对照文件清单确认版本再动手:
| 文件/目录 | 作用 |
|---|---|
css/menu.css | 菜单的布局、状态、动画样式,主要调这里 |
css/iconfont.css | 字体图标定义,换图标时维护这个文件 |
js/menu.js | jQuery插件主体,包含渲染、展开收起、高亮联动的逻辑 |
js/menu-data.js | 菜单数据配置,实际项目中通常被后台接口数据替代 |
index.html | 演示页,包含完整的菜单结构和调用示例 |
我一般拿到源码先打开index.html看一眼菜单初始结构,再去js/menu.js里找插件初始化入口。多数菜单插件初始化就一行,但参数藏在插件定义里,这一步看清楚后面才好改。整套源码依赖jQuery,版本建议用1.12.x或3.x,兼容性最好。
3. 把菜单装进自己项目的四步操作:引入、初始化、配置、联动高亮
3.1 静态页面里最快接入的写法
在纯静态页面里接入这套菜单,不需要构建工具,按顺序引入样式和脚本就行。顺序错了会翻车,jQuery必须先于菜单脚本加载。
<link rel="stylesheet" href="css/iconfont.css"> <link rel="stylesheet" href="css/menu.css"> <script src="js/jquery.min.js"></script> <script src="js/menu.js"></script> <script> $('#sidebar').sidebarMenu({ mode: 'accordion', // 手风琴模式,同时只展开一个一级菜单 hover: false, // 关闭hover展开,改为点击展开 activeClass: 'active', // 高亮类名 arrow: '<span class="arrow"></span>' // 箭头元素的HTML }); </script>初始化参数里,mode有两个可选值,accordion是手风琴,multiple允许多个菜单同时展开,后台系统建议用accordion,官网类站点用multiple体验更宽松。hover改成true后,鼠标移入展开、移出收起,适合顶部导航场景。activeClass对应CSS里高亮态类名,如果你项目里已有同名的样式要提前改掉。箭头元素支持自定义HTML,有的设计稿箭头在右侧、有的在左侧,这里就能调。
3.2 配合Java后台模板动态生成菜单项
如果你项目是Java后端渲染页面,比如用JSP、Thymeleaf,或者前端用thymeleaf模板,菜单数据就不该写死在JS里。常见做法是在Controller里把菜单树查出来,转成JSON字符串,再用模板引擎输出到页面,让menu.js直接消费。
@Controller public class MenuController { @GetMapping("/menu") public String index(Model model) { List<MenuVO> menus = menuService.listUserMenus(1L); String menuJson = JSON.toJSONString(menus); model.addAttribute("menuJson", menuJson); return "index"; } }<script> // 从服务端渲染的隐藏域或script标签里读取菜单数据 var menuData = JSON.parse($('#menu-data').text()); $('#sidebar').sidebarMenu({ data: menuData, // 用接口数据替换内置的menu-data.js mode: 'accordion' }); </script> <script type="application/json" id="menu-data">${menuJson}</script>这里有个关键细节:menuJson里每项必须包含title、url、children、icon这些字段,字段名和源码里buildMenu读取的保持一致。如果后端返回的是menuName或者childList,要么后端转成前端约定字段,要么改menu.js里的读取逻辑。我偏向后端适配前端,因为前端是通用组件,换项目还能复用。
3.3 菜单与页面滚动/路由联动高亮
静态页面里高亮好做,点击哪个菜单就给哪个加active。但实际项目里刷新页面后菜单高亮必须还原,不然用户一刷新就迷路。常规做法有两种:一是根据当前页面URL匹配菜单项的url字段,二是配合前端路由框架的afterEach钩子设置高亮。
// 页面加载时根据URL自动高亮 function highlightFromUrl() { var path = location.pathname.split('/').pop() || 'index.html'; $('.nav-menu a').each(function () { var href = $(this).attr('href'); if (href === path) { $(this).closest('li').addClass('active'); $(this).parents('.has-sub').each(function () { $(this).addClass('open'); }); } }); } $(function () { highlightFromUrl(); // 点击菜单后高亮切换 $('#sidebar').on('click', '.nav-menu a', function () { $('.nav-menu li').removeClass('active'); $(this).closest('li').addClass('active'); }); });这个写法里,closest('li')拿到的可能是一级li也可能是子级li,而parents('.has-sub')负责把包含当前项的所有父级菜单展开,保证刷新后子菜单是展开状态而不是收起状态。highlightFromUrl要在菜单初始化之后再执行,因为初始化时DOM才被渲染出来。如果你的页面URL带参数,记得先.split('?')[0]去掉查询字符串再比对。
4. 菜单样式定制与视觉升级:CSS变量、图标字体与响应式收放
4.1 用CSS变量改配色和尺寸,不动JS
源码样式里预设了一组CSS变量,改菜单风格基本不用去碰具体类名。CSS变量的好处是全局换肤只改一个值,不用追着样式表找颜色。
:root { --menu-bg: #1e293b; /* 侧栏背景色 */ --menu-item-height: 44px; /* 每个菜单项的高度 */ --menu-text-color: #cbd5e1; /* 默认文字色 */ --menu-active-color: #3b82f6; /* 高亮背景色 */ --menu-hover-bg: rgba(255, 255, 255, 0.06); /* hover背景 */ --menu-sub-bg: #0f172a; /* 子菜单背景色,比一级深一点 */ --menu-radius: 8px; /* 菜单项的圆角 */ } .nav-menu > li > a { height: var(--menu-item-height); line-height: var(--menu-item-height); border-radius: var(--menu-radius); color: var(--menu-text-color); } .nav-menu > li.active > a { background-color: var(--menu-active-color); color: #fff; }改尺寸时要留意line-height和height必须同步改,不然文字会垂直偏移。高亮色建议同时改--menu-active-color和子菜单背景色,保持视觉层次。圆角在侧栏宽边距时好看,窄屏折叠状态下圆角建议归零,不然两个图标之间会出现缺口。
4.2 字体图标替换成自己的图标库
源码里图标用的是字体图标,类名形如icon-dashboard、icon-set。你项目里如果用的是别家的图标库,比如Font Awesome,只需要把menu-data.js里的icon字段改成目标图标类名,然后在CSS里做一次映射或者直接改HTML模板里的<i>标签。
// 把字体图标映射到Font Awesome示例 var iconMap = { 'icon-dashboard': 'fa-solid fa-gauge', 'icon-set': 'fa-solid fa-gear', 'icon-user': 'fa-solid fa-user-group' }; function renderIcon(li, iconName) { if (!iconName) return; var cls = iconMap[iconName] || iconName; li.prepend($('<i>').addClass(cls)); }注意prepend加图标时必须放在文字节点前面,否则视觉上图标在文字右侧,和CSS里margin-right的设定冲突。如果图标加载后是方块字符,先确认字体文件路径对不对,再看类名拼写是否和图标库定义一致,通常是这两处问题。
4.3 移动端适配与遮罩层
菜单在窄屏下一般是隐藏状态,通过汉堡按钮唤出侧栏,同时盖一层半透明遮罩防止用户误触页面主体。源码里适配逻辑和很多后台模板不太一样,它能做到菜单收起后有一个半透明遮罩层,点击遮罩时同时收起菜单和遮罩。
$('#sidebar-toggle').on('click', function () { var $sidebar = $('#sidebar'); if ($sidebar.hasClass('open')) { closeSidebar(); } else { openSidebar(); } }); function openSidebar() { $('#sidebar').addClass('open'); $('<div class="sidebar-overlay"></div>') .appendTo('body') .on('click', closeSidebar); // 点击遮罩直接关闭 } function closeSidebar() { $('#sidebar').removeClass('open'); $('.sidebar-overlay').remove(); }遮罩层元素是动态创建的,每次打开生成、关闭移除,避免多次点击产生多个遮罩堆叠。z-index方面,遮罩建议比侧栏低一个层级,比页面主体内容高,实际操作中侧栏设z-index: 1000,遮罩设z-index: 999就行。如果你发现菜单打开后还能滚动页面,给body加一个overflow: hidden,关闭时再移除。
5. 菜单源码排坑实录:jQuery冲突、展开逻辑、点击穿透
5.1 项目里引用其他JS库导致菜单打不开
现象:菜单点击后毫无反应,控制台报错xxx is not a function,或者直接报$ is not defined。
原因:最常见的是页面里又引入了别的JS库,把$符号占用了,比如某些老版本组件库会用jQuery.noConflict()释放$符号。菜单源码里全部用的$,一旦$被接管,插件初始化自然失败。
解决:把菜单脚本改为用jQuery调用,或者自己包一层匿名函数传参。我习惯在menu.js开头加一行(function($){ ... })(jQuery);,这样插件内部始终用传入的jQuery对象,不受外部污染。如果只改调用处就报错,那是因为插件定义内部已经绑定了$,改外面没有用,必须改插件内部。
5.2 二级菜单点不开,点击事件被遮罩层吃掉
现象:侧栏菜单展开正常,但点击二级菜单项时页面跳转了,展开状态闪一下立刻收起。
原因:页面同时存在两套菜单初始化代码,第二套初始化把第一套的事件覆盖了;或者遮罩层z-index高于菜单本身,点击落在遮罩层上触发了关闭逻辑。
解决:先查页面里是否重复引入了menu.js或重复调用了sidebarMenu初始化,只保留一份。再看遮罩层的z-index是否高于侧栏,遮罩只应该在移动端出现,桌面端如果遮罩还在,检查是不是CSS媒体查询没写display: none。调试时我在Chrome开发者工具里用document.querySelectorAll('.sidebar-overlay')确认遮罩数量。
5.3 页面刷新后菜单高亮丢失
现象:从子页面刷新,菜单回到默认状态,当前页面的菜单项既不展开也不高亮,用户需要手动再点开一次。
原因:初始化时没有调用URL匹配逻辑,高亮只发生在点击事件里。刷新后内存里没有状态,自然回到初始。
解决:在初始化完成回调里手动调用highlightFromUrl()。注意必须等菜单DOM渲染完再执行,所以代码要放在初始化配置的onReady回调里,而不是直接在初始化代码后面紧接着调。如果放在后面调,在高亮逻辑执行时DOM还没有生成,选择器选不到任何元素。
5.4 菜单折叠动画卡顿,展开收起掉帧
现象:菜单展开时动画一顿一顿的,低配电脑上尤其明显。
原因:CSS里用了height和margin做动画,这两个属性每帧都触发重排,性能开销大。侧栏菜单层级多的时候,重排积压就会掉帧。
解决:把动画属性改成transform和opacity。子菜单先用max-height: 0加overflow: hidden隐藏,展开时设max-height: 500px过渡。这个方案虽然不够完美,但正常情况下帧率稳定。实际测试中max-height的值要大于菜单展开后最大高度,设小了会截断,设大了动画速度不均匀,我一般取预期最大高度的1.5倍左右。
5.5 菜单项右侧箭头方向不对
现象:一级菜单有子菜单,但箭头朝下;展开后箭头应该朝上,实际还是朝下。
原因:源码里箭头方向通过.open类控制,但你的项目里.open类被其他样式覆盖了,或者CSS片段加载顺序不对,菜单样式被后面的样式表覆盖掉。
解决:检查menu.css是否在页面末尾引入,或者在menu.css末尾追加一段更高优先级的覆盖规则:
.nav-menu > li.has-sub.open > a .arrow { transform: rotate(180deg); }注意边框型箭头和背景型箭头的写法完全不同,边框型是旋转元素,背景型是切换背景图,遇到方向不对不要硬改transform,先看箭头是用哪种方式画的。
6. 进阶玩法:菜单折叠状态记忆与后端菜单接口对接
菜单做进生产项目后,有两个体验细节最影响口碑,一个是从侧栏切到主内容再切回来,菜单展开状态能否保持;另一个是后端权限变了,菜单能否自动跟着变。这套源码只要稍加扩展就能做到。
折叠状态记忆我用localStorage实现。每次菜单展开收起的事件触发时,把当前一级菜单展开的索引存进本地,刷新后初始化阶段读出来恢复。注意存索引比存类名可靠,因为类名可能被其他逻辑改动。
$('#sidebar').on('menu-open menu-close', function (e, index) { var state = JSON.parse(localStorage.getItem('menu-state') || '[]'); if (e.type === 'menu-open') { state.push(index); } else { var pos = state.indexOf(index); if (pos >= 0) state.splice(pos, 1); } localStorage.setItem('menu-state', JSON.stringify(state)); });后端菜单接口对接,我一般要求后端直接返回符合字段约定的树形JSON。实际项目里我遇到过权限系统返回的菜单带visible字段,实现时在渲染循环里加一层条件过滤,不可见的菜单直接从数据源剔除,而不是渲染完再隐藏,这样既省DOM开销也避免用户通过控制台改样式看到无权限菜单。从那以后我每次接菜单需求都会先问后端要接口返回样例,而不是先写前端等数据,这个习惯帮我避开过至少三次返工。
如果你只是要一套现成的菜单界面,把menu-data.js里的数据换成你的后台导航内容就能直接用。记得把初始化里的hover和mode按你的场景先定好再上线。希望这篇拆解能帮你把这套源码真正落到自己的项目里。
本文还有配套的精品资源,点击获取