Главная » Статьи » Реактивное (react) гамбургер-меню

Реактивное (react) гамбургер-меню

Реактивное (react) гамбургер-меню

От автора: создайте свое собственное меню, используя React, хуки, TypeScript и styled-component. Я предпочитаю простое меню, которое не будет выглядеть навязчивым на странице, отвлекая внимание от контента — но в то же время доступное для моих пользователей. Вместе мы создадим простое липкое гамбургер-меню!

Что такое гамбургер-меню?

Я думаю, что название «гамбургер» — это просто слово для визуализации иконки меню, которая выглядит как гамбургер. Держу пари, что вы уже видели раньше значок с 3 линиями?

Выше приведен рисунок, показывающий, как я создала гамбургер-меню для своей веб-страницы #onefortheocean. В этой статье мы собираемся сделать более простую версию.

Технические ингредиенты

Думаю, начинать перечислять ингредиенты гамбургеров опасно — у каждого человека свои сильные личные предпочтения! То же самое касается ингредиентов технического стека. В любом случае, если вам не нравятся некоторые из моих ингредиентов, вы можете их убрать. Код написан с использованием компонентов React (16.13), TypeScript (3.8) и Styled (5.1).

Если вы хотите добавить кетчуп или кожуру помидора, вы можете это сделать! Я не против.

Основные компоненты

Я буду создавать компоненты с нуля, добавляя больше деталей в процессе. Я надеюсь, что вы таким образом тоже сможете получить все необходимое. Если вы опытный разработчик, вы можете в любое время покопаться в коде, представленном внизу статьи.

С кодом, приведенным ниже, мы получаем минимальную базу для начала. Мы добавим мобильные настройки, переходы и хуки (события клика) позже. Я решила разделить код на Menu и Hamburger, после добавления основной части ваше меню должно выглядеть примерно так:

Menu

Menu — это коричневый фон, который действует как фактическое меню, содержащее ссылки навигации.

import React, { useState } from "react";
import styled from "styled-components"; import { colors } from "../../global"; import Hamburger from "../Hamburger/Hamburger"; const StyledMenu = styled.nav<{ open: boolean }>` top: 0; left: 0; height: 100vh; width: 35vw; position: fixed; background-color: ${colors.lightbrown}; z-index: 1; padding: 10rem 0; flex-direction: column; display: ${({ open }) => (open ? "flex" : "none")};
`;
const StyledLink = styled.a` padding: 0 2rem; font-size: 2rem; color: ${colors.pearl}; text-decoration: none; :hover { color: ${colors.yellowmellow}; cursor: pointer; }
`; const Menu = () => { const [open, setOpen] = useState<boolean>(false); const close = () => setOpen(false); return ( <div> <StyledMenu open={open}> <StyledLink onClick={() => close()}>Link 1</StyledLink <StyledLink onClick={() => close()}>Link 2</StyledLink <StyledLink onClick={() => close()}>Link 3</StyledLink </StyledMenu> <Hamburger open={open} setOpen={setOpen} /> </div> );
};

Hamburger — это пиктограмма самого меню, используемая для переключения между видимым и скрытым Menu.

import React from "react";
import styled from "styled-components"; import { colors } from "../../global"; const StyledHamburger = styled.button<{ open: boolean }>` position: fixed; left: 3vw; top: 3vw; width: 2rem; height: 2rem; padding: 0; background: transparent; display: flex; flex-direction: column; justify-content: space-around; border: none; cursor: pointer; outline: none; z-index: 1; div { position: relative; width: 2rem; height: 0.25rem; border-radius: 10px; background-color: ${({ open }) => open ? colors.pearl : colors.lightbrown}; }
`; type Props = { open: boolean; setOpen: (v: boolean) => void;
}; const Hamburger = (props: Props) => ( <StyledHamburger open={props.open} onClick={() => props.setOpen(!props.open)} > <div /> <div /> <div /> </StyledHamburger>
);

Добавляем движение с помощью переходов

Теперь у вас есть основное гамбургер-меню. Наверное, немного скучно? Давайте добавим движения! Во-первых, мы хотим задать плавный переход отображения и скрытия Menu.

Чтобы максимально использовать возможности Hamburger, вы можете преобразовать его из значка гамбургера в знак крестика, когда Menu открыто — две иконки в одной! Теперь меню должно выглядеть примерно так:

// Menu
const StyledMenu = styled.nav<{ open: boolean }>` //... transition: transform 0.3s ease-in-out; transform: ${({ open }) => (open ? "translateX(0)" :"translateX(-100%)")};
`; // Hamburger
const StyledHamburger = styled.button<{ open: boolean }>` //... left: ${({ open }) => (open ? "29vw" : "3vw")}; div { //... transition: all 0.3s linear; transform-origin: 1px; :first-child { transform: ${({ open }) => (open ? "rotate(45deg)" : "rotate(0)")}; } :nth-child(2) { opacity: ${({ open }) => (open ? "0" : "1")}; transform: ${({ open }) => (open ? "translateX(20px)":"translateX(0)")}; } :nth-child(3) { transform: ${({ open }) => (open ? "rotate(-45deg)" : "rotate(0)")}; } }
`;

Для Menu мы задаем некоторую задержку. Для гамбургера мы поворачиваем первый и третий div на 45 градусов, образуя крест. Второй div мы просто делаем невидимым, потому что нам нужны только две линии, образующие значок закрытия. Вот и все — вы только что создали гамбургер-меню.

Настройка для мобильных устройств

Я обычно сохраняю дизайн простым без каких-либо значительных настроек для мобильных устройств, но меню слишком важно, чтобы не уделять ему больше внимания.

На маленьком экране Menu должно покрывать всю страницу. Я думаю, что это добавляет ощущение приложения — вы не согласны? В своем приложении я создала контрольную точку на 600px, где Menu разворачивается весь экран. К настоящему времени наше меню должно выглядеть на маленьких экранах примерно так:

//Menu
export const StyledMenu = styled.nav<{ open: boolean }>` //... @media (max-width: 600px) { width: 100%; }
`; //Hamburger
const StyledHamburger = styled.button<{ open: boolean }>` //... @media (max-width: 600px) { left: ${({ open }) => (open ? "initial" : "3vw")}; right: ${({ open }) => (open ? "2vw" : "initial")}; } //...
`;

Для Menu нам просто нужно создать медиа-запрос, при котором ширина меню становится 100% области просмотра, вместо 30%, когда ширина экрана составляет 600 пикселей или меньше. Для Hamburger я хотела переместить значок крестика при открытии меню на мобильном телефоне вправо. Свойство left используется для перемещения крестика от левого края. Когда вы работаете на мобильном телефоне, я хочу, чтобы значок (то есть когда меню охватывает весь экран) был на расстоянии x от правой стороны. Это имеет смысл?

Хуки

Для Menu требуется отслеживать его состояние — оно должно быть открытым или закрытым? Это простой хук useState с логическим значением open и функцией setOpen для изменения состояния open. Это мы уже добавили к Menu, поскольку это важная часть для его работы.

const [open, setOpen] = useState<boolean>(false);

Что произойдет, если пользователь кликает за пределами Menu? На данный момент ничего.

Если Menu открыто и пользователь кликает за его пределами, он ожидает, что он Menu закроется. Чтобы иметь возможность выяснить, нажимает ли пользователь внутри или снаружи меню, мы добавим ссылку на узел в Menu. Таким образом, мы сможем спросить: «Пользователь нажал на Menu или за его пределами?». Вместе с узлом мы можем использовать пользовательский хук useOnClickOutside. Он должен вести себя так:

import { useEffect, RefObject } from "react"; const useOnClickOutside = ( ref: RefObject<HTMLDivElement>, closeMenu: () => void
) => { useEffect(() => { const listener = (event: MouseEvent) => { if (ref.current && event.target && ref.current.contains(event.target as Node) ) { return; } closeMenu(); }; document.addEventListener("mousedown", listener); return () => { document.removeEventListener("mousedown", listener); }; }, [ref, closeMenu]);
};
const Menu = (props: Props) => { const [open, setOpen] = useState<boolean>(false); const node = useRef<HTMLDivElement>(null); useOnClickOutside(node, () => props.setOpen(false)); return ( <div ref={node}> .... </div> );
};

Все ингредиенты собраны вместе — ваш стильный и реактивный гамбургер готов:

Я надеюсь, что вы нашли эту статью полезной!

Автор: Marte Løge

Источник: https://medium.com

Редакция: Команда webformyself.