Интерактивные элементы и виджеты в веб-разработке
Создавайте доступные пользовательские виджеты на JavaScript — слайдеры, модальные окна, вкладки и аккордеоны — а также используйте Canvas API для динамической графики.
Создание пользовательских виджетов и использование HTML API может значительно улучшить интерактивность и пользовательский опыт ваших веб-приложений. Это руководство содержит пошаговые инструкции по созданию пользовательских интерактивных элементов — слайдеров, модальных окон и вкладок, — а также знакомит вас с HTML5 API, в частности Canvas API для создания динамической графики.
Введение
Виджет — это самодостаточный интерактивный компонент пользовательского интерфейса: слайдер, модальное диалоговое окно, панель с вкладками, аккордеон, — который пользователь может задействовать для изменения отображаемого содержимого или ввода данных. Браузеры поставляют ряд встроенных виджетов (<input type="range">, <dialog>, <details>), однако зачастую приходится создавать собственные, когда требуется нестандартное поведение, стилизация или макет, которые встроенные элементы не обеспечивают.
В этом руководстве показано, как создать три виджета, к которым обращаются чаще всего, — слайдер, модальное окно и набор вкладок, — а затем рассматривается Canvas API — возможность HTML5 для рисования динамической графики. Каждый пример реализует поведение с помощью DOM и обработки событий, поэтому рекомендуется заранее освоить выбор элементов и события браузера.
Нативный виджет или пользовательский?
Перед тем как писать JavaScript, рассмотрите возможность использования нативного элемента. Нативные виджеты доступны, управляются с клавиатуры и поддерживают работу с формами «из коробки», а также продолжают работать, если скрипт не загрузился.
| Потребность | Нативный элемент | Создавайте свой, когда… |
|---|---|---|
| Выбрать значение в диапазоне | <input type="range"> | нужен двухползунковый или нелинейный элемент управления |
| Показать блокирующий диалог | <dialog> + showModal() | нужен полностью нестандартный макет или анимация |
| Сворачиваемый раздел | <details> / <summary> | нужен анимированный аккордеон с группой панелей |
| Вкладки | (нет нативного элемента) | всегда создаётся своё — следуйте шаблону ARIA tabs |
Пример слайдера ниже фактически оборачивает нативный <input type="range">, что является рекомендуемым подходом. Модальное окно и вкладки создаются с нуля, чтобы вы могли увидеть все составные части, однако в реальных проектах предпочтительнее использовать <dialog> для модальных окон.
Лучшие практики
- Используйте семантический HTML: убедитесь, что структура HTML является осмысленной и доступной.
- Разделяйте ответственности: держите HTML, CSS и JavaScript раздельно для поддержания чистого и управляемого кода.
- Доступность: убедитесь, что интерактивные элементы доступны для всех пользователей, включая тех, кто использует программы чтения с экрана.
- Оптимизация производительности: минимизируйте манипуляции с DOM и оптимизируйте JavaScript для обеспечения плавного взаимодействия.
- Адаптивный дизайн: убедитесь, что интерактивные элементы корректно работают на разных размерах экрана и устройствах.
Создание пользовательских виджетов
Для продакшн-приложений в первую очередь рассмотрите нативные HTML-элементы: <dialog> для модальных окон и <details> / <summary> для сворачиваемого содержимого. Они бесплатно предоставляют управление фокусом, клавишу Escape и поддержку клавиатуры.
Пользовательский слайдер
Пользовательский слайдер позволяет выбирать значение из диапазона. Вот как создать его с помощью HTML, CSS и JavaScript.
Пример
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom Slider</title>
<style>
.slider-container {
display: flex;
align-items: center;
gap: 10px;
}
#slider {
width: 200px;
}
</style>
</head>
<body>
<div class="slider-container">
<input type="range" id="slider" min="0" max="100" value="50" aria-label="Value slider" aria-describedby="slider-value" />
<span id="slider-value">50</span>
</div>
<script>
document.getElementById('slider').addEventListener('input', function() {
document.getElementById('slider-value').textContent = this.value;
});
</script>
</body>
</html>В этом примере создаётся простой слайдер с элементом управления диапазоном и блоком span для отображения текущего значения. JavaScript обновляет текстовое содержимое span по мере перемещения слайдера.
Пользовательское модальное окно
Модальные окна используются для отображения содержимого в оверлее. Вот как создать пользовательское модальное окно.
Пример
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom Modal</title>
<style>
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
text-align: center;
}
.close {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="open-modal">Open Modal</button>
<div id="modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" tabindex="-1">
<div class="modal-content">
<span id="close-modal" class="close" aria-label="Close modal">×</span>
<h2 id="modal-title">Custom Modal</h2>
<p>This is a custom modal!</p>
</div>
</div>
<script>
const modal = document.getElementById('modal');
const openBtn = document.getElementById('open-modal');
const closeBtn = document.getElementById('close-modal');
openBtn.addEventListener('click', () => {
modal.style.display = 'flex';
closeBtn.focus();
});
closeBtn.addEventListener('click', () => {
modal.style.display = 'none';
openBtn.focus();
});
window.addEventListener('keydown', (e) => {
if (modal.style.display === 'flex' && e.key === 'Escape') {
modal.style.display = 'none';
openBtn.focus();
}
});
modal.addEventListener('click', (event) => {
if (event.target === modal) {
modal.style.display = 'none';
openBtn.focus();
}
});
</script>
</body>
</html>В этом примере показано, как создать модальное окно, которое можно открывать и закрывать с помощью JavaScript. Модальное окно отображает оверлей и блок с содержимым, который можно закрыть нажатием кнопки, клавиши Escape или кликом по оверлею. Управление фокусом реализовано так, чтобы пользователи клавиатуры могли навигировать по диалогу.
Пользовательские вкладки
Вкладки позволяют переключаться между различными разделами содержимого. Вот как создать пользовательские вкладки.
Пример
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom Tabs</title>
<style>
.tabs {
display: flex;
gap: 10px;
}
.tab-button {
padding: 10px 20px;
cursor: pointer;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 5px;
}
.tab-button.active {
background-color: #fff;
border-bottom: 2px solid #000;
}
.tab-content {
display: none;
margin-top: 20px;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="tabs" role="tablist">
<button class="tab-button active" role="tab" aria-selected="true" data-tab="tab1">Tab 1</button>
<button class="tab-button" role="tab" aria-selected="false" data-tab="tab2">Tab 2</button>
<button class="tab-button" role="tab" aria-selected="false" data-tab="tab3">Tab 3</button>
</div>
<div class="tab-content active" role="tabpanel" aria-labelledby="tab1">
<p>Content for Tab 1</p>
</div>
<div class="tab-content" role="tabpanel" aria-labelledby="tab2">
<p>Content for Tab 2</p>
</div>
<div class="tab-content" role="tabpanel" aria-labelledby="tab3">
<p>Content for Tab 3</p>
</div>
<script>
// Array.from is important: querySelectorAll returns a NodeList,
// which has no .indexOf method. We need a real array for the
// arrow-key navigation below.
const buttons = Array.from(document.querySelectorAll('.tab-button'));
const contents = document.querySelectorAll('.tab-content');
function activateTab(clickedBtn) {
buttons.forEach(btn => {
btn.classList.remove('active');
btn.setAttribute('aria-selected', 'false');
});
contents.forEach(content => content.classList.remove('active'));
clickedBtn.classList.add('active');
clickedBtn.setAttribute('aria-selected', 'true');
document.getElementById(clickedBtn.dataset.tab).classList.add('active');
}
buttons.forEach(button => {
button.addEventListener('click', () => activateTab(button));
button.addEventListener('keydown', (e) => {
let nextIndex;
if (e.key === 'ArrowRight') nextIndex = (buttons.indexOf(button) + 1) % buttons.length;
else if (e.key === 'ArrowLeft') nextIndex = (buttons.indexOf(button) - 1 + buttons.length) % buttons.length;
else return;
e.preventDefault();
buttons[nextIndex].focus();
activateTab(buttons[nextIndex]);
});
});
</script>
</body>
</html>В этом примере создаётся интерфейс с вкладками. Клик по кнопке вкладки или использование клавиш со стрелками влево/вправо отобразит соответствующее содержимое и скроет остальное. Обработка клавиш со стрелками соответствует шаблону WAI-ARIA tabs, что позволяет пользователям клавиатуры перемещаться между вкладками без мыши — ключевой элемент доступности DOM.
Аккордеон с нативным <details>
Не каждому сворачиваемому виджету нужен JavaScript. Нативный элемент <details> предоставляет переключатель открытия/закрытия, поддержку клавиатуры и правильную семантику без единой строки скрипта. Используйте его для FAQ и панелей раскрытия информации.
Пример
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Accordion</title>
</head>
<body>
<details>
<summary>What is a widget?</summary>
<p>A self-contained interactive UI component.</p>
</details>
<details>
<summary>Do I always need JavaScript?</summary>
<p>No — native elements like this one work without any script.</p>
</details>
<script>
// Optional: react when a panel opens (e.g. lazy-load content).
document.querySelectorAll('details').forEach((d) => {
d.addEventListener('toggle', () => {
console.log(d.open ? 'opened' : 'closed');
});
});
</script>
</body>
</html>Событие toggle срабатывает каждый раз, когда пользователь разворачивает или сворачивает панель, — это позволяет лениво загружать содержимое или отслеживать аналитику. Прибегайте к аккордеону на JavaScript только тогда, когда вам нужна анимация или поведение «только одна панель открыта одновременно».
Использование HTML5 API
Введение в HTML5 API
HTML5 API предоставляют мощные возможности, расширяющие функциональность веб-приложений. Одним из наиболее универсальных HTML5 API является Canvas API, который позволяет создавать динамическую графику.
Использование Canvas API
Canvas API позволяет рисовать графику непосредственно на веб-странице. Вот базовый пример использования Canvas API.
Пример
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas API Example</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #000;"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// Draw a rectangle
ctx.fillStyle = '#FF0000';
ctx.fillRect(50, 50, 150, 100);
// Draw a circle
ctx.beginPath();
ctx.arc(200, 200, 40, 0, 2 * Math.PI);
ctx.fillStyle = '#00FF00';
ctx.fill();
// Draw text
ctx.font = '20px Arial';
ctx.fillStyle = '#0000FF';
ctx.fillText('Hello Canvas', 100, 300);
</script>
</body>
</html>В этом примере демонстрируются базовые функции рисования Canvas API:
fillRect(x, y, width, height)рисует закрашенный прямоугольник, левый верхний угол которого находится в точке(x, y).arc(x, y, radius, startAngle, endAngle)добавляет путь окружности с центром в точке(x, y); углы задаются в радианах, поэтому полная окружность — от0до2 * Math.PI. Перед вызовом необходимо вызватьbeginPath(), а после —fill()(илиstroke()).fillText(text, x, y)рисует текст, где(x, y)— начало базовой линии, а не верхний левый угол.
Частая ошибка: устанавливайте fillStyle до вызова функции рисования, на которую он должен влиять, — canvas хранит последний установленный цвет, поэтому случайное повторное использование одного цвета для разных фигур — распространённая проблема. Для анимации очищайте предыдущий кадр с помощью ctx.clearRect(0, 0, canvas.width, canvas.height) и перерисовывайте внутри requestAnimationFrame. Установка размера canvas через CSS вместо атрибутов width/height растягивает рисунок — всегда задавайте размер через атрибуты.
Всегда убеждайтесь, что ваши интерактивные элементы доступны. Используйте роли и свойства ARIA, семантический HTML и обеспечивайте навигацию с клавиатуры для создания инклюзивного пользовательского опыта для всех пользователей. Это не только улучшает доступность, но и повышает общую удобство использования и SEO.
Заключение
Пользовательские виджеты — слайдеры, модальные окна, вкладки, аккордеоны — и HTML5 API, такие как Canvas, позволяют создавать интерактивные поверхности, на которые опираются современные веб-приложения. Главный урок, применимый ко всем из них, одинаков: начинайте с нативного элемента, если он существует, затем добавляйте JavaScript только для того поведения, которое браузер не предоставляет сам, и никогда не пропускайте поддержку клавиатуры и ARIA.
Чтобы двигаться дальше, ознакомьтесь с техниками манипуляции DOM — строительными блоками, которые используют эти виджеты, с обработкой событий в DOM — для понимания того, как подключается пользовательский ввод, и с оптимизацией производительности DOM для обеспечения плавного взаимодействия.