Как создать выезжающее меню на React с анимацией
В этой статье мы разберём реализацию мобильного выезжающего меню (off-canvas menu) на React с использованием хуков useState, useEffect и useContext. Вы узнаете, как управлять состоянием бургер-кнопки, анимировать появление панели и блокировать прокрутку основного контента. Готовый код подходит для адаптивных веб-приложений и интернет-магазинов.
Компонент Menu на React
Основной компонент Menu использует локальное состояние isExpanded для отслеживания открытия/закрытия панели. При изменении состояния через useEffect добавляется или удаляется класс menu-open на document.body, что позволяет управлять CSS-анимациями и блокировать скролл. Контекст AuthContext определяет, показывать ссылки для авторизованного пользователя или гостя.
function Menu() {
const [isExpanded, setIsExpanded] = useState(false);
const { userId } = useContext(AuthContext);
useEffect(() => {
if (isExpanded) {
document.body.classList.add("menu-open");
} else {
document.body.classList.remove("menu-open");
}
return () => {
document.body.classList.remove("menu-open");
};
}, [isExpanded]);
return (
<>
<button className="burger-button" onClick={() => setIsExpanded(!isExpanded)}>
☰
</button>
<aside className="menu">
<div className="menu-header">
<div className="logo" onClick={() => setIsExpanded(!isExpanded)}>
<Logo />
<Sidekick />
</div>
</div>
<nav className={
`mobile-nav ${isExpanded ? "expanded" : ""}`
}>
{userId ? (
<>
<a>Profile info</a>
<a>Statistics</a>
</>
) : (
<>
<a>Sing In</a>
<a>Sing Up</a>
</>
)}
</nav>
</aside>
</>
);
}CSS-стили для выезжающей панели
Стили реализуют плавное выезжание меню справа с использованием transform: translateX(100%) и opacity: 0. При открытии через класс body.menu-open панель сдвигается в видимую область. Бургер-кнопка скрыта на десктопе и появляется только при ширине экрана до 500px. Для дополнительной адаптации под маленькие экраны (до 376px) уменьшаются размеры кнопки.
.burger-button {
display: none;
position: absolute;
top: 0;
right: 0;
height: 48px;
width: 48px;
background-color: transparent;
border: none;
font-size: 24px;
z-index: 101;
color: var(--text-color);
}
.menu {
display: block;
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 80vw;
height: 100vh;
background-color: var(--background-root);
border-left: 1px solid var(--border-color);
transform: translateX(100%);
opacity: 0;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
z-index: 100;
}
.mobile-nav {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
gap: 10px;
width: 80vw;
height: calc(100vh - 48px);
top: 48px;
right: 0;
z-index: 100;
background-color: var(--background-root);
}
@media (max-width: 500px) {
.burger-button {
display: flex;
justify-content: center;
align-items: center;
}
body.menu-open .menu {
transform: translateX(0);
opacity: 1;
}
body.menu-open .menu-header {
opacity: 1;
transform: translateX(0);
display: flex;
}
body.menu-open main,
body.menu-open footer {
filter: blur(4px);
pointer-events: none;
}
}Особенности реализации
При открытии меню на main и footer накладывается размытие (blur) и отключаются события мыши. Это стандартный UX-паттерн для мобильных навигаций. Логотип в шапке меню также служит кнопкой закрытия. Обратите внимание, что в коде используется useEffect с очисткой - это предотвращает утечку класса menu-open при размонтировании компонента.
Адаптация под разные экраны
Для экранов шире 500px бургер-кнопка скрыта, а меню остаётся за пределами видимой области. При ширине до 376px размер кнопки уменьшается до 32x32 пикселей, что удобно для устройств с маленьким дисплеем. Вы можете легко изменить 80vw на фиксированную ширину (например, 300px) для более предсказуемого поведения.