Як створити мобільне меню з burger-кнопкою без jQuery

Для створення мобільного меню у WordPress зовсім не обов’язково підключати jQuery або сторонні JavaScript-бібліотеки.

Сучасного Vanilla JavaScript цілком достатньо, щоб реалізувати:

  • відкриття та закриття меню;
  • анімацію burger-кнопки;
  • блокування прокрутки сторінки;
  • закриття меню клавішею Escape;
  • закриття після переходу за посиланням;
  • коректні aria-* атрибути для accessibility.

У цій статті створимо просте мобільне меню для власної WordPress-теми.

1. Виводимо меню в header.php

Припустимо, що у functions.php вже зареєстрована позиція меню:

function wp_hunter_setup() {

	register_nav_menus(
		array(
			'primary' => __( 'Primary Menu', 'wp-hunter' ),
		)
	);

}
add_action( 'after_setup_theme', 'wp_hunter_setup' );

Тепер у header.php перевіримо, чи призначене меню, та додамо burger-кнопку:

<?php if ( has_nav_menu( 'primary' ) ) : ?>

	<button
		class="menu-toggle"
		type="button"
		aria-expanded="false"
		aria-controls="primary-navigation"
		aria-label="<?php esc_attr_e( 'Open menu', 'wp-hunter' ); ?>"
	>
		<span class="menu-toggle__line"></span>
		<span class="menu-toggle__line"></span>
		<span class="menu-toggle__line"></span>
	</button>

	<nav
		id="primary-navigation"
		class="primary-navigation"
		aria-label="<?php esc_attr_e( 'Primary Navigation', 'wp-hunter' ); ?>"
	>
		<?php
		wp_nav_menu(
			array(
				'theme_location' => 'primary',
				'container'      => false,
				'menu_class'     => 'primary-menu',
				'fallback_cb'    => false,
			)
		);
		?>
	</nav>

<?php endif; ?>

Тут важливими є два атрибути:

aria-expanded="false"

показує, чи відкрите меню.

А:

aria-controls="primary-navigation"

вказує, яким елементом керує кнопка.

ID:

id="primary-navigation"

відповідно встановлений для <nav>.

2. Створюємо burger-кнопку

Всередині кнопки ми використали три звичайні <span>:

<span class="menu-toggle__line"></span>
<span class="menu-toggle__line"></span>
<span class="menu-toggle__line"></span>

Саме з них за допомогою CSS створимо burger.

.menu-toggle {
	display: none;
	width: 44px;
	height: 44px;
	padding: 10px;
	border: 0;
	background: transparent;
	cursor: pointer;
}

.menu-toggle__line {
	display: block;
	width: 24px;
	height: 2px;
	margin: 5px auto;
	background: currentColor;
	transition:
		transform 0.3s ease,
		opacity 0.3s ease;
}

На desktop кнопка прихована:

display: none;

Показувати її будемо лише на мобільних пристроях.

3. Анімація burger → close

Коли меню відкрите, JavaScript змінюватиме:

aria-expanded="false"

на:

aria-expanded="true"

Це дозволяє керувати анімацією без додаткових CSS-класів:

.menu-toggle[aria-expanded="true"] .menu-toggle__line:nth-child(1) {
	transform: translateY(7px) rotate(45deg);
}

.menu-toggle[aria-expanded="true"] .menu-toggle__line:nth-child(2) {
	opacity: 0;
}

.menu-toggle[aria-expanded="true"] .menu-toggle__line:nth-child(3) {
	transform: translateY(-7px) rotate(-45deg);
}

Три лінії burger-кнопки плавно перетворяться на хрестик.

4. Стилі мобільного меню

Тепер створимо саме мобільне меню.

Наприклад, нехай воно виїжджає справа:

@media (max-width: 991px) {

	.menu-toggle {
		display: block;
		position: relative;
		z-index: 1001;
	}

	.primary-navigation {
		position: fixed;
		top: 0;
		right: 0;
		bottom: 0;
		width: min(400px, 100%);
		padding: 100px 30px 40px;
		background: #fff;

		transform: translateX(100%);
		visibility: hidden;
		transition:
			transform 0.35s ease,
			visibility 0.35s;
		z-index: 1000;
	}

	body.menu-open .primary-navigation {
		transform: translateX(0);
		visibility: visible;
	}

	.primary-menu {
		display: flex;
		flex-direction: column;
		gap: 20px;
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.primary-menu a {
		display: block;
		text-decoration: none;
	}

	body.menu-open {
		overflow: hidden;
	}
}

Ключовим тут є клас:

body.menu-open

Коли він з’являється на <body>, меню стає видимим.

Також:

body.menu-open {
	overflow: hidden;
}

блокує прокрутку сторінки під відкритим мобільним меню.

5. JavaScript без jQuery

Створимо файл:

/assets/js/navigation.js

і додамо:

document.addEventListener('DOMContentLoaded', () => {

	const toggle = document.querySelector('.menu-toggle');
	const navigation = document.querySelector('.primary-navigation');

	if (!toggle || !navigation) {
		return;
	}

	const openMenu = () => {
		document.body.classList.add('menu-open');

		toggle.setAttribute('aria-expanded', 'true');
		toggle.setAttribute('aria-label', 'Close menu');
	};

	const closeMenu = () => {
		document.body.classList.remove('menu-open');

		toggle.setAttribute('aria-expanded', 'false');
		toggle.setAttribute('aria-label', 'Open menu');
	};

	const toggleMenu = () => {
		const isOpen = toggle.getAttribute('aria-expanded') === 'true';

		if (isOpen) {
			closeMenu();
		} else {
			openMenu();
		}
	};

	toggle.addEventListener('click', toggleMenu);

});

Все.

Для базового мобільного меню нам не потрібні ні jQuery, ні Bootstrap, ні будь-які інші залежності.

6. Закриваємо меню клавішею Escape

Додамо можливість закрити меню клавішею Escape.

Це особливо важливо для користувачів, які працюють із сайтом за допомогою клавіатури.

document.addEventListener('keydown', (event) => {

	if (event.key === 'Escape') {
		closeMenu();
	}

});

Тепер натискання:

Esc

закриє мобільне меню.

7. Закриваємо меню після кліку на посилання

На мобільних пристроях після вибору пункту навігації меню також бажано закривати.

Додаємо:

navigation.addEventListener('click', (event) => {

	if (event.target.closest('a')) {
		closeMenu();
	}

});

Це особливо корисно, якщо меню містить anchor-посилання:

#about
#services
#contacts

Користувач натискає пункт — меню закривається, а браузер переходить до потрібної секції.

8. Закриваємо меню при кліку поза ним

Також можна закривати меню, якщо користувач натиснув у будь-якому місці за межами навігації.

document.addEventListener('click', (event) => {

	const clickedInsideMenu = navigation.contains(event.target);
	const clickedToggle = toggle.contains(event.target);

	if (!clickedInsideMenu && !clickedToggle) {
		closeMenu();
	}

});

При цьому клік на самому меню або burger-кнопці не викликатиме закриття.

9. Враховуємо зміну розміру браузера

Є ще один невеликий нюанс.

Наприклад, користувач відкрив мобільне меню, а потім збільшив ширину браузера до desktop.

На <body> все ще залишиться:

class="menu-open"

Тому краще автоматично скинути стан меню при переході на desktop.

const desktopMedia = window.matchMedia('(min-width: 992px)');

desktopMedia.addEventListener('change', (event) => {

	if (event.matches) {
		closeMenu();
	}

});

Тепер при переході вище 992px мобільне меню автоматично повернеться у початковий стан.

10. Повний JavaScript

У результаті наш navigation.js виглядатиме так:

document.addEventListener('DOMContentLoaded', () => {

	const toggle = document.querySelector('.menu-toggle');
	const navigation = document.querySelector('.primary-navigation');

	if (!toggle || !navigation) {
		return;
	}

	const openMenu = () => {
		document.body.classList.add('menu-open');

		toggle.setAttribute('aria-expanded', 'true');
		toggle.setAttribute('aria-label', 'Close menu');
	};

	const closeMenu = () => {
		document.body.classList.remove('menu-open');

		toggle.setAttribute('aria-expanded', 'false');
		toggle.setAttribute('aria-label', 'Open menu');
	};

	const toggleMenu = () => {
		const isOpen = toggle.getAttribute('aria-expanded') === 'true';

		isOpen ? closeMenu() : openMenu();
	};

	toggle.addEventListener('click', toggleMenu);

	document.addEventListener('keydown', (event) => {

		if (event.key === 'Escape') {
			closeMenu();
		}

	});

	navigation.addEventListener('click', (event) => {

		if (event.target.closest('a')) {
			closeMenu();
		}

	});

	document.addEventListener('click', (event) => {

		const clickedInsideMenu = navigation.contains(event.target);
		const clickedToggle = toggle.contains(event.target);

		if (!clickedInsideMenu && !clickedToggle) {
			closeMenu();
		}

	});

	const desktopMedia = window.matchMedia('(min-width: 992px)');

	desktopMedia.addEventListener('change', (event) => {

		if (event.matches) {
			closeMenu();
		}

	});

});

Для звичайного мобільного меню цього більш ніж достатньо.

11. Підключаємо JavaScript у WordPress

Не варто вставляти:

<script src="..."></script>

безпосередньо в header.php або footer.php.

WordPress має власний механізм підключення JavaScript-файлів — wp_enqueue_script().

Додаємо у functions.php:

function wp_hunter_scripts() {

	wp_enqueue_script(
		'wp-hunter-navigation',
		get_template_directory_uri() . '/assets/js/navigation.js',
		array(),
		wp_get_theme()->get( 'Version' ),
		array(
			'strategy'  => 'defer',
			'in_footer' => true,
		)
	);

}
add_action( 'wp_enqueue_scripts', 'wp_hunter_scripts' );

Наш скрипт не використовує jQuery, тому список залежностей порожній:

array()

Тобто WordPress не буде завантажувати jQuery заради роботи мобільного меню.

12. Повна структура

У результаті структура теми може виглядати так:

your-theme/
│
├── assets/
│   ├── css/
│   │   └── main.css
│   │
│   └── js/
│       └── navigation.js
│
├── functions.php
├── header.php
├── footer.php
├── index.php
└── style.css

У functions.php:

register_nav_menus();

реєструємо позицію меню.

У header.php:

wp_nav_menu();

виводимо навігацію та burger-кнопку.

У:

navigation.js

керуємо відкриттям та закриттям меню.

А CSS відповідає за зовнішній вигляд та анімацію.

Чому краще без jQuery?

Колись для такої задачі часто використовували:

$('.menu-toggle').on('click', function() {
	$('.menu').toggleClass('active');
});

Але сьогодні для настільки простої логіки jQuery не потрібен.

Vanilla JavaScript вже має все необхідне:

document.querySelector()
addEventListener()
classList.add()
classList.remove()
classList.toggle()
closest()
matchMedia()

У результаті ми не створюємо зайву залежність і залишаємо код теми простішим.

Не забуваємо про accessibility

Burger-кнопка повинна бути саме:

<button>

а не:

<div>

або:

<span>

Кнопка вже має правильну семантику та доступна для керування клавіатурою.

Також використовуємо:

aria-expanded="false"

щоб повідомляти стан меню.

Коли меню відкривається:

aria-expanded="true"

А через:

aria-controls="primary-navigation"

зв’язуємо кнопку безпосередньо з навігацією.

Підсумок

Для створення сучасного мобільного меню у WordPress не потрібен jQuery.

Достатньо:

wp_nav_menu()
+
CSS
+
Vanilla JavaScript

У нашому прикладі burger-кнопка:

  • відкриває та закриває меню;
  • анімується у хрестик;
  • змінює aria-expanded;
  • блокує прокрутку сторінки;
  • закривається через Escape;
  • закривається після переходу за посиланням;
  • закривається при кліку поза меню;
  • автоматично скидається при переході на desktop.

Для більшості власних WordPress-тем цього достатньо, а JavaScript залишається невеликим, зрозумілим і без сторонніх залежностей.

Реєстрація та виведення меню у WordPress

Навігаційне меню — один із базових елементів будь-якої WordPress-теми. Якщо ви розробляєте власну класичну або hybrid-тему, найзручніше зареєструвати окремі позиції меню та виводити їх у потрібних частинах шаблону.

У цій статті розберемо, як правильно зареєструвати та вивести меню у WordPress 7.1.

Реєстрація меню у WordPress

Для створення позицій меню використовується функція:

register_nav_menus();

Наприклад, створимо дві області:

  • головне меню в шапці;
  • меню у футері.

Код додаємо у functions.php теми:

function wp_hunter_setup() {

	register_nav_menus(
		array(
			'primary' => __( 'Головне меню', 'wp-hunter' ),
			'footer'  => __( 'Меню у футері', 'wp-hunter' ),
		)
	);

}
add_action( 'after_setup_theme', 'wp_hunter_setup' );

Після цього WordPress буде знати про дві позиції:

primary
footer

primary та footer — це системні ідентифікатори позицій. Їх ми будемо використовувати під час виведення меню в шаблоні.

Функція register_nav_menus() автоматично активує підтримку меню для теми, тому додатково викликати:

add_theme_support( 'menus' );

не потрібно.

Реєстрація одного меню

Якщо потрібна лише одна позиція, можна використати:

register_nav_menu();

Наприклад:

function wp_hunter_setup() {

	register_nav_menu(
		'primary',
		__( 'Головне меню', 'wp-hunter' )
	);

}
add_action( 'after_setup_theme', 'wp_hunter_setup' );

Але якщо тема має декілька меню, зручніше одразу використовувати register_nav_menus().

Виведення меню в темі

Для виведення зареєстрованого меню використовується:

wp_nav_menu();

Наприклад, у header.php:

<?php
wp_nav_menu(
	array(
		'theme_location' => 'primary',
	)
);
?>

Параметр:

'theme_location' => 'primary'

вказує WordPress, яку саме зареєстровану позицію потрібно вивести.

Налаштовуємо HTML меню

У реальному проєкті зазвичай потрібно контролювати класи та HTML-обгортку.

Наприклад:

<?php
wp_nav_menu(
	array(
		'theme_location'  => 'primary',
		'container'       => 'nav',
		'container_class' => 'header-nav',
		'menu_class'      => 'header-menu',
		'menu_id'         => 'header-menu',
		'fallback_cb'     => false,
	)
);
?>

У результаті отримаємо приблизно таку структуру:

<nav class="header-nav">
	<ul id="header-menu" class="header-menu">
		<li class="menu-item">
			<a href="/">Головна</a>
		</li>

		<li class="menu-item">
			<a href="/blog/">Блог</a>
		</li>
	</ul>
</nav>

Основні параметри тут:

'theme_location'

визначає позицію меню.

'container'

задає HTML-елемент навколо меню.

'container_class'

додає клас контейнеру.

'menu_class'

задає клас для <ul>.

'menu_id'

задає його id.

А:

'fallback_cb' => false

забороняє WordPress автоматично підставляти інше меню або список сторінок, якщо меню для цієї позиції ще не призначене.

Перевірка, чи призначене меню

Перед виведенням меню можна перевірити, чи користувач справді призначив меню для потрібної області.

Для цього використовується:

has_nav_menu();

Приклад:

<?php if ( has_nav_menu( 'primary' ) ) : ?>

	<nav class="header-nav">

		<?php
		wp_nav_menu(
			array(
				'theme_location' => 'primary',
				'container'      => false,
				'menu_class'     => 'header-menu',
				'fallback_cb'    => false,
			)
		);
		?>

	</nav>

<?php endif; ?>

Цей варіант особливо зручний, якщо ви не хочете виводити порожній <nav>.

Виведення меню у футері

З другою позицією працюємо аналогічно.

Наприклад, у footer.php:

<?php
wp_nav_menu(
	array(
		'theme_location' => 'footer',
		'container'      => 'nav',
		'menu_class'     => 'footer-menu',
		'fallback_cb'    => false,
	)
);
?>

Таким чином одне меню можна використовувати в шапці сайту, інше — у футері.

Додавання класів до посилань меню

За замовчуванням WordPress додає багато класів до <li>, але іноді потрібно додати власний клас безпосередньо до <a>.

Наприклад:

function wp_hunter_menu_link_attributes( $atts, $menu_item, $args ) {

	if ( isset( $args->theme_location ) && 'primary' === $args->theme_location ) {
		$atts['class'] = 'header-menu__link';
	}

	return $atts;
}
add_filter( 'nav_menu_link_attributes', 'wp_hunter_menu_link_attributes', 10, 3 );

Тепер посилання головного меню матимуть вигляд:

<a class="header-menu__link" href="/">
	Головна
</a>

Це значно спрощує стилізацію меню.

Клас активного пункту меню

WordPress автоматично додає спеціальні класи до активних пунктів:

current-menu-item
current-menu-parent
current-menu-ancestor

Тому поточний пункт можна оформити звичайним CSS:

.header-menu .current-menu-item > a {
	font-weight: 600;
}

Наприклад, можна додати підкреслення:

.header-menu .current-menu-item > a {
	text-decoration: underline;
	text-underline-offset: 6px;
}

Додаткова PHP-логіка для визначення активної сторінки в більшості випадків не потрібна.

Повний приклад

У functions.php:

function wp_hunter_setup() {

	register_nav_menus(
		array(
			'primary' => __( 'Головне меню', 'wp-hunter' ),
			'footer'  => __( 'Меню у футері', 'wp-hunter' ),
		)
	);

}
add_action( 'after_setup_theme', 'wp_hunter_setup' );

У header.php:

<?php if ( has_nav_menu( 'primary' ) ) : ?>

	<nav class="header-nav" aria-label="<?php esc_attr_e( 'Головна навігація', 'wp-hunter' ); ?>">

		<?php
		wp_nav_menu(
			array(
				'theme_location' => 'primary',
				'container'      => false,
				'menu_class'     => 'header-menu',
				'fallback_cb'    => false,
			)
		);
		?>

	</nav>

<?php endif; ?>

І у footer.php:

<?php
wp_nav_menu(
	array(
		'theme_location' => 'footer',
		'container'      => false,
		'menu_class'     => 'footer-menu',
		'fallback_cb'    => false,
	)
);
?>

Цього достатньо для реалізації стандартної системи меню у власній WordPress-темі.

А як щодо Block Themes?

Важливо розділяти класичні та блокові WordPress-теми.

Для classic themes WordPress рекомендує систему:

register_nav_menus()
wp_nav_menu()

У повноцінних Block Themes навігація будується переважно через блок Navigation (core/navigation) у Site Editor, тому вручну реєструвати PHP-позиції меню зазвичай не потрібно.

Якщо ж ви розробляєте власну класичну або hybrid-тему з header.php, footer.php та PHP-шаблонами, register_nav_menus() і wp_nav_menu() залишаються простим і зручним рішенням.

Підсумок

Для роботи з меню у класичній WordPress-темі достатньо двох основних функцій:

register_nav_menus();

— реєструє позиції меню;

wp_nav_menu();

— виводить меню у потрібному місці шаблону.

А зв’язок між ними відбувається через:

theme_location

Наприклад:

'primary'

реєструємо у functions.php, а потім цю ж позицію передаємо в wp_nav_menu().

Такий підхід дозволяє залишити структуру навігації керованою через WordPress, а HTML, CSS та логіку відображення — повністю контролювати на рівні власної теми.

WP-Hunter

Безпечний вхід до акаунта

Увійдіть, створіть акаунт або відновіть пароль.

З поверненням

Увійдіть, щоб продовжити.