Семантика и структура чтения
В предыдущем уроке мы определили задачу: читатель должен найти объяснение Descendants, изучить пример и продолжить курс. Теперь представим эту структуру в HTML. Семантика связывает элементы не только с внешним видом, но и с назначением: заголовок начинает раздел, список объединяет однородные пункты, навигация содержит переходы, основное содержимое отвечает на запрос посетителя.
За основу возьмём отдельный файл a11y-lab/lesson.html. Это учебная страница с коротким фрагментом материала, а не замена существующего файла ProfessorWeb. В примере пока нет меню с раскрытием, диалога или отправки формы. Сначала создадим устойчивую оболочку, чтобы дальнейшие элементы имели определённое место и не усложняли объяснение раньше времени.
Документ, области и статья
Ниже приведён полный начальный HTML-файл. Изображения и дополнительные стили здесь не требуются. Ссылка на старый урок использует настоящий постоянный путь; в основной навигации есть переход к каталогу прежних уроков для третьего сценария. Эти корневые адреса требуют соответствующего учебного окружения: снимок не содержит всей библиотеки.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Запросы LINQ to XML — Профессор Веб</title>
<link rel="stylesheet" href="lesson.css">
</head>
<body>
<header class="site-header">
<a href="/">Профессор Веб</a>
<nav aria-label="Основная">
<a href="/catalog/">Каталог</a>
<a href="/catalog/legacy/">Прежние уроки</a>
</nav>
</header>
<main id="main-content">
<nav aria-label="Хлебные крошки">
<ol>
<li><a href="/">Главная</a></li>
<li><a href="/catalog/">Каталог</a></li>
<li><span aria-current="page">Запросы LINQ to XML</span></li>
</ol>
</nav>
<article aria-labelledby="lesson-title">
<header class="lesson-header">
<h1 id="lesson-title">Запросы LINQ to XML</h1>
<p>Выбираем элементы документа и разбираем результат.</p>
</header>
<nav id="lesson-toc" aria-label="В этой статье">
<ol>
<li><a href="#descendants">Элементы-потомки</a></li>
<li><a href="#filter">Условие отбора</a></li>
</ol>
</nav>
<section aria-labelledby="descendants">
<h2 id="descendants">Элементы-потомки</h2>
<p>Метод Descendants выбирает потомков с заданным именем.</p>
<pre><code>IEnumerable<XElement> employees =
xDoc.Descendants("Employee");</code></pre>
<p>Для учебного документа получаем сотрудников Alex и Elena.</p>
</section>
<section aria-labelledby="filter">
<h2 id="filter">Условие отбора</h2>
<p>Условие по FirstName оставляет сотрудника Elena.</p>
</section>
</article>
</main>
<footer class="site-footer">
<p><a href="/my/LINQ/linq_xml/level7/7_1.php">Полный старый урок</a></p>
</footer>
</body>
</html>
Результат этой разметки — документ с определённым названием, основным содержимым, навигационными областями и двумя разделами статьи. При чтении средствами, использующими структуру документа, становится возможным переходить по этим частям. Точные команды и произносимые фразы зависят от программы; урок не предполагает единственного варианта озвучивания.
header в начале body относится к сайту. Вложенный header относится к статье: он группирует её заголовок и вводный текст. Одинаковое имя элемента не означает одинаковую область доступности во всех местах. Назначение связано с контекстом, поэтому не нужно бездумно назначать каждому заголовочному блоку роль баннера. Для стандартной оболочки встроенных элементов достаточно.
main отделяет содержание текущей страницы от повторяемых частей сайта. В этом примере основной видимый main один. article уместен для самостоятельного урока, который можно прочитать отдельно от окружающей навигации. Он не нужен вокруг каждого абзаца или листинга: такая детализация разрушила бы понятную группировку, не добавив полезного отношения.
Заголовки как карта объяснения
Название урока получает h1, два основных понятия — h2. Если внутри раздела об отборе появится самостоятельный подраздел, для него подойдёт h3. Уровень выбирается по структуре объяснения, а не по требуемому размеру шрифта. Большой h2 можно уменьшить CSS; превращать его в абзац ради внешнего вида не требуется.
Рассмотрим изменение. В раздел о потомках добавляется пояснение «Когда имена повторяются в разных ветвях». Оно уточняет ограничение метода, поэтому становится h3 внутри этого раздела. Если поставить ещё один h1, структура предложит несколько равноправных начал документа. Если оформить пояснение только жирным текстом, визуальное различие сохранится, а связь с картой разделов станет менее очевидной.
Заголовок должен объяснять содержание. «Пример 2» позволяет отличить номер, но не понять тему без последовательного чтения. «Отбор сотрудника по имени» даёт смысл при переходе через список заголовков. При этом не следует превращать каждый переходный абзац в заголовок: полезная карта компактнее полного текста и отражает действительные разделы.
Элемент section в примере связан с заголовком через aria-labelledby. Это осознанно создаёт именованную область. Для маленькой статьи можно оставить заголовки прямо внутри article, без дополнительных именованных секций. Не нужно окружать каждую мелкую часть section с ARIA-именем: большое количество областей перегружает другой способ навигации так же, как длинное меню перегружает экран.
Практические примеры структурирования описаны в WAI Page Structure. Здесь важен выбор отношений: какой текст является названием страницы, какие части являются разделами и где повторяется навигация. Технология получает эти отношения из разметки, а оформление остаётся отдельным слоем.
Несколько навигаций с разными назначениями
На странице имеются основная навигация, хлебные крошки и оглавление. Всем трём подходит nav, но их задачи различаются. Поэтому в примере заданы короткие имена через aria-label. Читатель сможет отличить переход между разделами сайта от перехода внутри статьи. Добавлять к каждому имени слово «навигация» обычно излишне: роль уже передаёт этот смысл.
Пункты оглавления оформлены упорядоченным списком. Последовательность соответствует объяснению, а ссылки ведут к существующим идентификаторам заголовков. Для такого перехода не нужен обработчик JavaScript. Обычный href сохраняет назначение ссылки, возможность открыть адрес и работу якоря. Дальше мы рассмотрим положение после прокрутки, но базовый смысл перехода уже присутствует.
Хлебные крошки показывают положение статьи в библиотеке. Последний пункт — текущая страница, поэтому он представлен текстом с aria-current="page". Можно использовать ссылку на текущую страницу, если это принято в интерфейсе, однако создавать такую ссылку только ради ARIA не нужно. Описание текущего положения не превращает соседние пункты в кнопки: они по-прежнему открывают страницы.
Название документа title отличается от видимого h1: к теме добавлено имя сайта. Это помогает различать вкладки и историю. Если все главы имеют одинаковый title «Профессор Веб», пользователь получает несколько неразличимых документов. Если каждый заголовок перегружен служебными словами, труднее быстро увидеть тему. Короткое название материала в начале решает обе задачи.
Что семантика не исправляет сама
Правильные элементы не гарантируют удачного управления. Ссылка может иметь понятную роль, но быть полностью закрытой панелью. Заголовок может присутствовать в структуре и оказаться визуально скрытым после перехода. Поэтому семантическая оболочка — начальное состояние стенда, а не полный отчёт доступности. К ней добавляются поведение, оформление и будущие сценарии проверки.
Язык страницы lang="ru" описывает основной язык документа. Встреча названий LINQ и Descendants не требует немедленно переключать язык каждого слова: технические термины и код разберём отдельно. Сейчас важно, чтобы документ имел определённый основной язык и не оставлял выбор целиком внешней программе.
Добавлять ARIA-роль к элементу, у которого уже есть нужный встроенный смысл, в нашем примере незачем. Избыточная роль способна изменить ожидаемое поведение и не заменит отсутствующее действие. Например, назначение role="button" обычному тексту не создаёт клавиатурное управление. В следующем уроке мы продолжим с настоящими ссылками и кнопками и построим маршрут без положительного tabindex.