You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

响应式导航菜单适配问题:屏幕Resize时的类处理方案咨询

响应式导航菜单的适配问题

我制作了一个响应式导航菜单:

  • 桌面端(≥980px):导航项横向显示
  • 小屏端(<980px):导航项隐藏,显示汉堡按钮;点击汉堡按钮后右侧滑出全屏菜单,同时给body添加stop-scrolling类禁止页面滚动;点击关闭按钮收起菜单并移除该类

问题:小屏状态下打开菜单未关闭时,放大屏幕到桌面端尺寸,菜单虽通过CSS媒体查询自动隐藏,但stop-scrolling类未被移除;再次缩小屏幕,菜单仍保持打开状态。

我尝试过的思路:

  • 有人建议用window.addEventListener("resize", ())检测屏幕宽度达到阈值时,移除菜单的main-nav-open类及body的stop-scrolling类,但担心该事件频繁触发导致性能问题,而节流/防抖会导致处理延迟
  • 考虑用MutationObserver监听汉堡按钮的display属性变化,却不知如何监听特定样式属性的变更

现咨询:

  1. resize事件是否为最优方案?
  2. 如何用MutationObserver监听元素的特定样式属性变化?

附实现代码

HTML

<header class="main-header">
        <img class="business-logo-header" src="images\icons\business-logo-header.svg" alt="Logo" width="320" height="51" decoding="async">
        <button class="nav-open-btn" type="button">
          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 6 16 12">
          <g id="Menu / Hamburger_MD">
          <path class="nav-open-path" d="M5 17H19M5 12H19M5 7H19" stroke="#0d4c92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
          </g>
          </svg>
        </button>
        <nav class="main-nav">
          <ul class="main-nav-list">
            <button class="nav-close-btn" type="button">
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5 5 14 14">
                <g id="Menu / Close_MD">
                <path class="nav-close-path" id="Vector" d="M18 18L12 12M12 12L6 6M12 12L18 6M12 12L6 18" stroke="#0d4c92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                </g>
                </svg>
            </button>
            <li><a class="list-item" href="index.html">Home</a></li>
            <li><a class="list-item" href="pricing.html">Pricing</a></li>
            <li><a class="list-item" href="gallery.html">Gallery</a></li>
            <li><a class="list-item" href="contact.html">Contact</a></li>
          </ul>
        </nav>
    </header>

CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.main-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 3rem;
}

.business-logo-header {
  width: 70%;
  max-width: 32rem;
  height: auto;
}

.nav-open-btn {
  width: 15%;
  max-width: 6.8rem;
  margin: 1.25rem 0;
  position: relative;
  display: block;
  border: none;
  background-color: transparent;
  cursor: pointer;
}

.nav-open-path,
.nav-close-path {
  transition: stroke 200ms;
}

.nav-open-btn:hover .nav-open-path {
  stroke: #59c1bd;
}

.main-nav {
  display: flex;
  align-items: center;
  background-color: #fff;
  height: 100%;
  width: 0%;
  position: fixed;
  z-index: 1;
  top: 0;
  right: 0;
  transition: all 250ms;
}

.main-nav-open {
  background-color: grey;
  width: 100%;
}

.nav-close-btn {
  width: clamp(5rem, 3.79vw + 3.79rem, 7.5rem);
  position: relative;
  display: block;
  margin: clamp(0.75rem, 0.76vw + 0.51rem, 1.25rem);
  border: none;
  background-color: transparent;
  cursor: pointer;
}

.nav-close-btn:hover .nav-close-path {
  stroke: #59c1bd;
}

.main-nav-list {
  display: flex;
  flex-direction: column;
  margin: auto;
  list-style: none;
  align-items: center;
}

.list-item:link,
.list-item:visited {
  display: block;
  margin: clamp(0.75rem, 0.76vw + 0.51rem, 1.25rem);
  font-size: clamp(3.6rem, 2.42vw + 2.82rem, 5.2rem);
  font-weight: 700;
  text-decoration: none;
  color: #0d4c92;
  transition: color 200ms;
}

.list-item:hover,
.list-item:active {
  color: #59c1bd;
}

@media (min-width: 980px) {
  /* NAVIGATION */
  .nav-open-btn {
    display: none;
  }

  .main-nav {
    background-color: transparent;
    height: auto;
    width: auto;
    position: static;
    z-index: 0;
    top: auto;
    right: auto;
  }

  .main-nav-open {
    width: auto;
  }

  .nav-close-btn {
    display: none;
  }

  .main-nav-list {
    flex-direction: row;
    margin: 0;
    align-items: normal;
  }

  .list-item:link,
  .list-item:visited {
    font-size: 36px;
  }
}

JavaScript

const navOpenBTN = document.querySelector(".nav-open-btn");
const navCloseBTN = document.querySelector(".nav-close-btn");
const mainNav = document.querySelector(".main-nav");

navOpenBTN.addEventListener("click", () => {
  mainNav.classList.add("main-nav-open");
  document.body.classList.add("stop-scrolling");
});

navCloseBTN.addEventListener("click", () => {
  mainNav.classList.remove("main-nav-open");
  document.body.classList.remove("stop-scrolling");
});

解决方案

一、Resize事件方案(推荐,性能可控)

Resize事件确实会频繁触发,但只要做轻量逻辑处理+合理节流,完全可以避免性能问题,且实现简单,是这类响应式适配场景的主流方案。

优化思路

  1. 定义和CSS一致的断点常量,避免硬编码不一致
  2. 封装导航状态重置函数:当屏幕宽度超过断点时,移除菜单的main-nav-open类和body的stop-scrolling类
  3. 给resize事件添加节流(比如100ms间隔),既保证响应及时,又大幅减少触发次数
  4. 页面初始化时执行一次状态重置,避免初始状态异常

优化后的JavaScript代码

const navOpenBTN = document.querySelector(".nav-open-btn");
const navCloseBTN = document.querySelector(".nav-close-btn");
const mainNav = document.querySelector(".main-nav");
const BREAKPOINT = 980; // 和CSS媒体查询断点保持一致

// 重置导航状态的函数
const resetNavState = () => {
  if (window.innerWidth >= BREAKPOINT) {
    mainNav.classList.remove("main-nav-open");
    document.body.classList.remove("stop-scrolling");
  }
};

// 节流函数:控制事件触发频率
const throttle = (func, delay) => {
  let lastTriggerTime = 0;
  return (...args) => {
    const now = Date.now();
    if (now - lastTriggerTime >= delay) {
      func(...args);
      lastTriggerTime = now;
    }
  };
};

// 绑定点击事件
navOpenBTN.addEventListener("click", () => {
  mainNav.classList.add("main-nav-open");
  document.body.classList.add("stop-scrolling");
});

navCloseBTN.addEventListener("click", () => {
  mainNav.classList.remove("main-nav-open");
  document.body.classList.remove("stop-scrolling");
});

// 初始化执行一次,确保状态正确
resetNavState();
// 绑定节流后的resize事件
window.addEventListener("resize", throttle(resetNavState, 100));

说明:100ms的节流间隔在视觉上几乎无延迟,同时能将resize事件的触发次数从每秒几十次降到10次以内,完全不会造成性能负担。

二、MutationObserver方案(不推荐,实现复杂)

MutationObserver无法直接监听单个CSS属性的变化,只能监听元素的style属性、类名变化,或者整个文档样式表的变更。对于媒体查询触发的样式变更(非JS主动修改),监听逻辑会非常繁琐,且兼容性不如resize方案,因此不推荐在这个场景下使用。

如果一定要尝试,只能通过监听文档样式表的变化,再检查目标元素的display属性,但这种方式会额外增加不必要的性能开销,代码复杂度也远高于resize方案。


内容的提问来源于stack exchange,提问作者TherealMIST

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 11:44:55