如何实现移动端或浏览器缩放时点击侧边栏外部关闭菜单
移动端点击侧边栏外部关闭菜单的解决方案
问题描述
移动端视图下打开侧边栏菜单后,点击菜单外部区域无法关闭菜单,需通过JavaScript实现该交互。
解决方案步骤
1. 添加遮罩层(优化交互体验)
在HTML的.sidebar元素后新增一个遮罩层,用于捕获外部点击事件:
<div class="sidebar"> <!-- 原有侧边栏内容 --> </div> <!-- 新增遮罩层 --> <div class="sidebar-overlay"></div>
2. 更新CSS样式
为遮罩层添加基础样式,并设置仅在移动端菜单打开时显示:
/* 遮罩层基础样式 */ .sidebar-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 80; display: none; } /* 移动端下,菜单打开时显示遮罩 */ @media (max-width: 480px) { #menu-toggle:checked ~ .sidebar-overlay { display: block; } }
3. 添加JavaScript逻辑
在</body>标签前插入以下代码,实现点击外部关闭菜单的逻辑:
const menuToggle = document.getElementById('menu-toggle'); const sidebar = document.querySelector('.sidebar'); const menuButton = document.querySelector('label[for="menu-toggle"]'); // 点击遮罩或菜单外部关闭菜单 document.addEventListener('click', function(e) { // 仅在移动端且菜单已打开时处理 if (window.innerWidth <= 480 && menuToggle.checked) { const isClickInsideSidebar = sidebar.contains(e.target); const isClickOnMenuButton = menuButton.contains(e.target); if (!isClickInsideSidebar && !isClickOnMenuButton) { menuToggle.checked = false; } } }); // 窗口大小变化时重置菜单状态 window.addEventListener('resize', function() { if (window.innerWidth > 480) { menuToggle.checked = false; } });
完整修改后代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"> <title>Admin Dashboard | Keyframe Effects</title> <link rel="stylesheet" href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css"> <style> @import url('https://fonts.googleapis.com/css2?family=Merriweather+Sans:wght@300;400;500;600&display=swap'); :root { --main-color: #34425A; --color-dark: #34425A; --text-grey: #B0B0B0; } * { margin: 0; padding: 0; text-decoration: none; list-style-type: none; box-sizing: border-box; font-family: 'Merriweather', sans-serif; } #menu-toggle { display: none; } .sidebar { position: fixed; height: 100%; width: 165px; left: 0; bottom: 0; top: 0; z-index: 100; background-color: #d6eaf8; transition: left 300ms; } .side-menu ul li:hover { background-color: #1b4f72; } .side-menu a { color: #555; font-size: 1rem; } .side-menu a:hover { color: #fff; } .side-header { background-color: #2471a3; height: 60px; display: flex; justify-content: center; align-items: center; } .side-header h3, .side-header span { color: #fff; font-weight: 400; } .side-content { padding: 2rem 0rem; height: calc(100vh - 60px); overflow: auto; } .side-content::-webkit-scrollbar { width: 5px; } .side-content::-webkit-scrollbar-track { box-shadow: inset 0 0 5px grey; border-radius: 10px; } .side-content::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 10px; } .side-content::-webkit-scrollbar-thumb:hover { background: #b30000; } .side-menu ul { text-align: center; } .side-menu a { display: block; padding: 1rem 0rem; text-align: left; } .side-menu a.active { background: #1b4f72; border-bottom: solid 1px #d6eaf8; } .side-menu a.active span, .side-menu a.active small { color: #fff; } .side-menu a span { min-height: 15px; display: inline-flex; align-items: center; font-size: 1.7rem; margin: 0rem 0.5rem 0rem 0.5rem !important; } .side-menu a small { font-size: 0.95rem; font-weight: normal; margin: 0rem 1rem 0rem 1rem !important; font-family: 'Open Sans', sans-serif; } #menu-toggle:checked~.sidebar { width: 60px; } #menu-toggle:checked~.sidebar .side-header span { display: none; } #menu-toggle:checked~.main-content { margin-left: 60px; width: calc(100% - 60px); } #menu-toggle:checked~.main-content header { left: 60px; } #menu-toggle:checked~.sidebar .profile, #menu-toggle:checked~.sidebar .side-menu a small { display: none; } #menu-toggle:checked~.sidebar .side-menu a { font-size: 1.3rem; padding: 0.7rem 0rem 0.7rem 0rem; text-align: center; } #menu-toggle:checked~.sidebar .side-menu a span { font-size: 1.3rem; } .main-content { margin-left: 165px; width: calc(100% - 165px); transition: margin-left 300ms; } header { position: fixed; right: 0; top: 0; left: 165px; z-index: 100; height: 60px; box-shadow: 0px 5px 5px -5px rgb(0 0 0 /10%); background-color: #2471a3; transition: left 300ms; } .header-content, .header-menu { display: flex; align-items: center; } .header-content { justify-content: space-between; padding: 1rem 1rem; color: #fff!important; } .header-content label:first-child span { font-size: 1.3rem; } .header-content label { cursor: pointer; } .header-menu { justify-content: flex-end; padding-top: .5rem; } .page-content { height: 100vh; padding: 1.3rem 1rem; background: #f1f4f9; } /* 新增遮罩层样式 */ .sidebar-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 80; display: none; } @media (min-width: 320px) and (max-width: 480px) { .sidebar { left: -165px; z-index: 90; } header { left: 0; width: 100%; } .main-content { margin-left: 0; width: 100%; } #menu-toggle:checked~.sidebar { left: 0; width: 165px; } #menu-toggle:checked~.sidebar .side-header span { display: inline-block; } #menu-toggle:checked~.sidebar .profile, #menu-toggle:checked~.sidebar .side-menu a small { display: block; } #menu-toggle:checked~.sidebar .side-menu a span { font-size: 1.7rem; } #menu-toggle:checked~.main-content header { left: 0px; } /* 移动端菜单打开时显示遮罩 */ #menu-toggle:checked ~ .sidebar-overlay { display: block; } } </style> </head> <body> <input type="checkbox" id="menu-toggle"> <div class="sidebar"> <div class="side-header"> <h3>L<span>ogo</span></h3> </div> <div class="side-content"> <div class="side-menu"> <ul> <li> <a href="" class="active"> <span class="las la-home"><small>Dashboard</small></span> </a> </li> <li> <a href=""> <span class="las la-user-alt"><small>Profile</small></span> </a> </li> <li> <a href=""> <span class="las la-envelope"><small>Mailbox</small></span> </a> </li> <li> <a href=""> <span class="las la-clipboard-list"><small>Projects</small></span> </a> </li> <li> <a href=""> <span class="las la-shopping-cart"><small>Orders</small></span> </a> </li> <li> <a href=""> <span class="las la-tasks"><small>Tasks</small></span> </a> </li> </ul> </div> </div> </div> <!-- 新增遮罩层 --> <div class="sidebar-overlay"></div> <div class="main-content"> <header> <div class="header-content"> <label for="menu-toggle"> <span class="las la-bars"></span> </label> </div> </header> <main> <div class="page-header"> <h1>Dashboard</h1> <small>Home / Dashboard</small> </div> <div class="page-content"> Page content goes here... </div> </main> </div> <script> const menuToggle = document.getElementById('menu-toggle'); const sidebar = document.querySelector('.sidebar'); const menuButton = document.querySelector('label[for="menu-toggle"]'); // 点击遮罩或菜单外部关闭菜单 document.addEventListener('click', function(e) { if (window.innerWidth <= 480 && menuToggle.checked) { const isClickInsideSidebar = sidebar.contains(e.target); const isClickOnMenuButton = menuButton.contains(e.target); if (!isClickInsideSidebar && !isClickOnMenuButton) { menuToggle.checked = false; } } }); // 窗口大小变化时重置菜单状态 window.addEventListener('resize', function() { if (window.innerWidth > 480) { menuToggle.checked = false; } }); </script> </body> </html>
逻辑说明
- 遮罩层在移动端菜单打开时显示,既增强视觉遮挡反馈,又能捕获外部点击事件
- JS监听全局点击,判断点击区域是否属于侧边栏或菜单按钮,非目标区域则关闭菜单
- 窗口尺寸变化时自动重置菜单状态,避免移动端切换到桌面端后菜单状态异常
内容的提问来源于stack exchange,提问作者Eng Khalid Abdi
相关产品推荐
相关产品推荐

