
简介一个基于HTML与CSS实现的侧边伸缩导航栏源码包面向前端初学者和网页布局练习者解决侧边栏展开收起、子菜单层级显示、移动端适配等常见交互需求。压缩包体积仅61KB共9个文件包含HTML页面、CSS样式表、JavaScript脚本、图标字体文件、效果预览图及说明文本类型覆盖页面结构、样式控制、字体图标和文档说明便于从零理解完整实现流程。示例采用div容器组织导航链接与子菜单通过按钮状态触发样式切换结合transition属性为宽度变化添加平滑动画并利用媒体查询在窄屏下隐藏侧边栏同时加入悬停高亮等细节以优化用户体验。现已有6077人学习下载代码简洁、注释清晰适合课程设计、个人博客模板或后台管理界面快速改造参考也可作为前端导航交互的练手素材。 前阵子帮朋友整理一个后台管理系统功能还没怎么加光是侧边导航栏的交互就把人折腾得够呛。翻了一圈现成的组件库要么体积太大要么样式风格跟页面完全不搭改起来反而比从头写还费劲。最后我索性用原生HTML和CSS把这个侧边伸缩导航栏整个手写了一遍效果居然出奇地干净利落——点击按钮平滑折叠成纯图标栏再点一下恢复完整菜单全程不需要一行JavaScript。这篇就把它拆开细讲从原理到完整代码再把几个容易踩的坑一并说出来。这套方案特别适合两类人一类是刚接触前端、想搞懂布局和过渡动画原理的新手另一类是手里页面比较多、不想为一个小交互就引入整个UI框架的开发者。看完你能直接拿去改改就用也能明白里面的过渡动画和响应式适配是怎么一回事。1. 先说清楚这个导航栏要解决什么问题——从后台布局的实际痛点起手做后台系统的人都有体会侧边导航栏几乎是个标配。数据管理、订单列表、用户配置、系统设置所有这些页面都挂在一级二级菜单下面。导航栏做得好不好直接影响整个后台的操作效率。1.1 后台布局里的老生常谈侧边栏和内容区的配合典型的后台布局就是一个左侧固定宽度的侧边栏加一个右侧自适应宽度的内容区。桌面屏幕足够宽的时候220px左右的一级菜单宽度能舒服地放下菜单文字。但到了笔记本甚至外接小屏显示器的时候问题就来了菜单占着200多像素正文区域被挤得很难受。这时候最常见的做法就是让导航栏具备伸缩能力——要么hover上去展开、移开展开区域就收回来要么通过一个按钮主动切换展开与折叠状态。折叠之后侧边栏只保留一排图标把宝贵的横向空间让给正文内容。别看这个交互简单真做起来要处理的事情并不少宽度切换的动画是否顺滑、折叠后菜单文字会不会溢出、图标和文字的排列是否还对得齐、内容区是不是能跟着自动扩宽。任何一处没处理好这个导航栏就会显得非常业余。1.2 为什么我不用现成的UI组件库市面上主流的组件库像Element的Container布局也好Ant Design的Sider也罢确实开箱即用。但问题是这些组件自带一套设计语言进了项目之后往往还要配合主题定制功能去覆盖默认样式改来改去代码量并不小。而且很多组件库的导航栏是配套了整个框架体系使用的里面包含了路由、状态管理等逻辑。如果你只是想要一个纯粹的伸缩导航条把它单独拆出来用反而碍手碍脚。所以我更倾向于直接写一个轻量的原生实现。结构简单、样式完全可控、没有任何多余的依赖而且可以根据实际页面随意调整。如果你也是只需要一个干净的伸缩侧边栏后面这套代码应该正好合适。2. 纯CSS实现伸缩的核心checkbox hack的原理与基础骨架纯CSS怎么记住当前是否处于折叠状态这个问题是整套方案的根本。答案就落在HTML里一个常常被人忽略的表单元素——checkbox上。2.1 checkbox hack到底是什么原理原理其实很朴素label和checkbox是天生绑定的点击label的时候浏览器会触发对应checkbox的选中状态切换。而checkbox的选中与否可以让CSS通过:checked伪类感知到。于是点击label - checkbox状态变化 - CSS根据状态调整样式这条链就打通了。用生活里的事情来打比方checkbox就像一个物理开关label就是它的拉绳而CSS的:checked就是看到开关状态之后执行动作的那个人。用户不需要直接碰到checkbox它可以通过label被远程操作而这套机制完全不需要JavaScript参与。这样做的最大好处是状态天然由文档结构管理。刷新页面之后状态会重置代码逻辑里也没有维护开还是关的变量省掉了一堆state管理的麻烦。对一个小交互来说没有比这更省心的方案。2.2 从零搭建HTML骨架先看整体DOM结构!DOCTYPE html html langzh-cn head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title纯CSS侧边伸缩导航栏/title link relstylesheet hrefstyle.css /head body input typecheckbox idmenu-toggle classmenu-toggle hidden div classlayout aside classsidebar div classsidebar-header label formenu-toggle classtoggle-btn☰/label span classlogo-textAdmin/span /div nav classmenu ul lia href#span classicon/spanspan classtext首页/span/a/li lia href#span classicon/spanspan classtext数据分析/span/a/li lia href#span classicon/spanspan classtext订单管理/span/a/li lia href#span classicon/spanspan classtext用户管理/span/a/li lia href#span classicon⚙️/spanspan classtext系统设置/span/a/li /ul /nav /aside main classcontent h1主要内容区/h1 p侧边栏折叠时这里会自动扩展宽度。/p /main /div /body /html有几个结构上的关键点必须说明checkbox放在.layout前面并且和.layout是兄弟关系这样才能用后面的兄弟选择器~在CSS里选中它后面的元素。hidden属性让checkbox不可见但它仍然是可操作的。label不在它旁边也没关系只要for属性的值和checkbox的id一致点击就能切换。每个菜单项由图标.icon和文字.text两块组成这是后面做宽屏展开/窄屏只显示图标的基础。2.3 基础CSS布局和伸缩状态切换先设定整体布局侧边栏和内容区使用flex排列* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Microsoft YaHei, PingFang SC, sans-serif; } .layout { display: flex; min-height: 100vh; } .sidebar { width: 220px; background-color: #2c3e50; color: #fff; transition: width 0.3s ease; overflow: hidden; display: flex; flex-direction: column; } .content { flex: 1; padding: 20px; background-color: #f5f6fa; } .menu ul { list-style: none; } .menu a { display: flex; align-items: center; gap: 12px; padding: 14px 20px; color: #ecf0f1; text-decoration: none; transition: background-color 0.2s ease; } .menu a:hover { background-color: #34495e; } .icon { font-size: 18px; width: 22px; text-align: center; flex-shrink: 0; }现在加入控制伸缩的核心规则.menu-toggle:checked ~ .layout .sidebar { width: 68px; } .menu-toggle:checked ~ .layout .sidebar .logo-text { opacity: 0; width: 0; overflow: hidden; } .menu-toggle:checked ~ .layout .sidebar .text { opacity: 0; width: 0; overflow: hidden; } .menu-toggle:checked ~ .layout .sidebar .menu a { justify-content: center; padding-left: 0; padding-right: 0; }.menu-toggle:checked的含义很直白当checkbox是选中状态时后面这一串条件全都满足的元素就应用大括号里的样式。侧边栏宽度从220px变到68px文字部分透明度变成0且宽度收成0菜单项里面的内容居中。这样折叠后侧边栏只留下一列图标简洁直观。注意这里的transition: width 0.3s ease写在.sidebar的默认样式里不是写在:checked的规则里。过渡动画必须放在元素的常态样式上沉浸式折叠的效果才出得来。到这里一个最基本、能用的侧边伸缩导航栏已经跑起来了。不过光能折叠只是一半真正决定质感的是后面要处理的动画细节和内容区适配。3. 过渡动画的细节处理宽度、图标与内容区的联动一个导航栏看起来高级还是廉价往往不在于功能而在于细节。宽度从220px收缩到68px这个过程里文字如何消失、图标怎么对齐、内容区怎么平滑跟进这些都是打磨的重点。3.1 为什么文字要这样隐藏而不是直接display: none很多人第一次做折叠菜单的时候会想窄了之后文字放不下直接display: none不就完了理论上没有错但你把display: none和transition放在一起用就会发现文字是瞬间消失的而不是跟着宽度变化慢慢隐藏。原因在于display属性根本不能被过渡动画插值它只有显示和不显示两个极端态不存在中间值。所以我用的是另一种更细腻的策略把文字宽度收成0同时透明度逐渐变成0。这样在侧边栏从220px收缩的过程中文字会给人一种逐渐被挤出视野又慢慢淡出的视觉感受看起来舒服得多.sidebar .text, .sidebar .logo-text { white-space: nowrap; opacity: 1; transition: opacity 0.2s ease, width 0.3s ease; overflow: hidden; }white-space: nowrap也格外关键。如果文字在收窄过程中自动折行整个侧边栏的布局会瞬间崩掉这个属性正是防止文字乱跑用的。3.2 图标和文字的对齐问题菜单项的原始状态是图标加文字横排中间用gap: 12px拉开距离。折叠之后文字的宽度变成0只剩图标这时候需要让图标在缩小后的侧边栏里处于水平居中的位置.menu-toggle:checked ~ .layout .sidebar .menu a { justify-content: center; padding-left: 0; padding-right: 0; }这一句的作用是重新定义菜单链接的布局方式。展开状态下内容从左侧开始排列折叠状态下整体居中。如果你不做这一步图标会一直偏左缩窄后的观感会非常不协调。顶部logo区域的label按钮也要做类似处理。默认情况下label和logo文字并排折叠后logo文字消失label按钮必然要居中显示。给toggle按钮也设定一个固定大小再配合margin: auto就能实现水平居中。3.3 按钮图标的旋转方向让交互更直白汉堡按钮和箭头按钮是两种常见的伸缩图标。用箭头的话展开状态下箭头朝左表示可以缩回去折叠状态下箭头应该朝右表示可以展开。纯CSS里通过:checked配合transform就能实现.toggle-btn { display: inline-block; transition: transform 0.3s ease; cursor: pointer; font-size: 20px; } .menu-toggle:checked ~ .layout .sidebar .toggle-btn { transform: rotate(180deg); }如果使用的是←箭头初始朝左折叠后旋转180度朝右一眼就能看懂当前是什么状态、点击会触发什么结果。3.4 内容区怎么自动跟着变宽内容区不需要写任何额外逻辑。因为.layout用的是display: flex侧边栏的宽度一旦变化.content的flex: 1会自动把多余的空间吸收掉。这也是flex布局比float布局做后台框架舒服很多的原因——你只需要让一边宽度变化另外一边自动撑满剩余空间。为了让这个被撑开的过程不是突然发生的可以给内容区也加一条背景色的过渡或者干脆不加因为侧边栏的宽度过渡已经带动了整个区域宽度的平滑变化。实践下来看到的效果是侧边栏很顺滑地缩窄内容区同时很顺滑地加宽整个过程像是有个隐形的手在两边同时推。4. 从桌面端到移动端多断点适配与伸缩策略切换到这里桌面端的侧边导航栏已经能流畅工作了。但现在的页面不光要在显示器上打开手机、平板上也得能看。侧边栏在小屏幕上继续占着220px显然不合适所以必须针对窄屏做专门适配。4.1 小屏幕下侧边栏的抽屉模式思路移动端屏幕宽度有限通常的做法是把整个侧边栏移出可视范围需要的时候再以抽屉的形式从左侧滑入。可以让侧边栏变成position: fixed用transform: translateX(-100%)把它推到屏幕外面点击菜单按钮后平移回可视区域media (max-width: 768px) { .sidebar { width: 220px !important; position: fixed; left: 0; top: 0; bottom: 0; z-index: 1000; transform: translateX(-100%); transition: transform 0.3s ease; } .menu-toggle:checked ~ .layout .sidebar { transform: translateX(0); } .content { margin-left: 0; } }这样小屏幕下侧边栏的折叠逻辑就不再是宽度收缩成图标栏而是整体移出屏幕。菜单文字在小屏下也应该正常显示因为移动端没有桌面端那种需要让出空间给正文的需求——整个屏幕都是页面内容侧边栏本身就是一种临时覆盖层。4.2 三种伸缩模式怎么选hover切换、点击切换、JS状态记忆做响应式适配的时候我想把三种常见的伸缩模式放在一起对比一下方便你根据自己项目的实际情况选模式实现方式优点缺点适合场景hover展开/收起:hover改变宽度无需点击操作直观移动端没有hover误触率高桌面端官网导航checkbox点击切换:checked控制宽度纯CSS、代码精简刷新后状态重置不能跨页面记忆后台管理系统JS点击切换class切换JS操作classlocalStorage记忆状态可记忆、可扩展子菜单多写JS逻辑和事件监听需要跨页面保持状态的后台我的建议是如果只是单页面的后台界面用纯CSS的checkbox方案最省心如果导航栏需要配合路由跳转、多页面保持折叠状态那就得引入JS把状态写到localStorage里。两者没有绝对的谁更好只有谁更适合当前场景。4.3 更深一层的多级菜单结构考虑实际上很多后台的导航不止一层二级、三级菜单很常见。纯CSS的checkbox方案做单级菜单非常干净但碰到多级菜单的时候就有个天然的短板子菜单的展开收起也需要各自的checkbox逻辑层级一多HTML结构会变得比较啰嗦。如果项目一开始就规划好了多级菜单我建议别硬用纯CSS死磕直接上一小段JS动态管理子菜单的展开状态会更省力。但对于一个一级菜单为主的中小型后台或者个人项目纯CSS方案完全够用而且能让你对布局和动画的理解更扎实。5. 实战中踩过的几个坑transition失效、布局错位与排查思路写这个导航栏的过程中有几个问题我印象特别深每一次都是看起来样式写得没问题浏览器里就是表现不对。把这几类问题总结出来你写的时候可以少走很多弯路。5.1 transition不生效问题往往出在初始状态没写transition新手最容易犯的错就是把transition写进:checked或者:hover的规则里。比如/* 错误示范 */ .menu-toggle:checked ~ .layout .sidebar { width: 68px; transition: width 0.3s ease; }这样写会导致一个问题当导航栏从展开状态进入折叠状态时width确实有动画但从折叠状态回到展开状态时transition规则失效了宽度是瞬间弹回去的。正确做法是把transition写在元素的默认规则中无论什么状态变化动画规则始终在.sidebar { width: 220px; transition: width 0.3s ease; }判断标准就一句话哪个属性的值会变化就把transition写在承载这个属性的选择器上。5.2 display:none参与的过渡必然失效前面说过文字隐藏不能用display: none这里再细讲一层。如果某个元素从显示到隐藏经过了display: none和opacity变化动画只能作用于opacitydisplay的切换还是瞬间的。结果就是元素透明度还在渐渐变低但元素本身突然消失了观感非常撕裂。正确做法是用width: 0结合overflow: hidden把元素尺寸真实地收敛到零。这样transition可以作用于宽度的每个中间值文字也跟着逐渐被挤出可视区域。5.3 flex布局下文字不收缩、内容区被撑破菜单项的文字在侧边栏缩窄之后不消失甚至把菜单项撑出边框这个问题也很典型。原因在于flex子项默认不收缩到内容最小宽度以下这个最小宽度一般就是里面最长单词或完整内容的宽度。解决办法是给文字加上.sidebar .text { min-width: 0; overflow: hidden; white-space: nowrap; }min-width: 0允许元素实际宽度收缩到比内容宽度更小overflow: hidden把溢出的部分藏起来white-space: nowrap避免文字折行。这三个属性经常是配套出现少了任何一个都可能出毛病。5.4 排查布局异常的完整链路如果你照着自己的代码写完之后发现布局不对我建议按照下面的顺序逐步排查效率会高很多先用浏览器的开发者工具检查.sidebar元素确认它的实际宽度是不是如预期变化。如果宽度没有变化大概率是:checked选择器没匹配上检查checkbox和.layout是不是兄弟关系。如果宽度变了但动画不生硬去.sidebar的Computed面板里看transition有没有生效再把鼠标移到transition条目上看动画作用的属性列表。如果文字没有隐藏检查.text的width是否为0、overflow是否为hidden以及min-width是不是0。如果内容区没有自动变宽检查.layout的display是不是flex以及.content的flex: 1有没有被覆盖。如果始终找不到问题可以临时在关键元素上加一个醒目的outline: 1px solid red把元素边界可视化往往一眼就能看出到底是哪个容器在作怪。5.5 一个小技巧折叠状态给菜单项加tooltip导航栏折叠成纯图标栏之后鼠标悬停在图标上如果不显示文字提示用户经常不知道这个图标到底是什么功能。加一个基于title属性的原生tooltip是最简单的方式但样式不可控想要更美观的提示框可以用::after伪元素配合style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />