Перейти к содержанию

Прогрессивное улучшение веб-интерфейса: каталог уроков

Современный интерфейс начинается с договора о том, что посетитель должен получить. В учебном каталоге «Полка» это три материала, их названия, краткие описания и постоянные ссылки. Диалоги, подсказки и переходы появятся позже. Сейчас подготовим документ, который объясняет своё назначение без сценариев и не теряет смысл после отключения эффектов. Нужны знакомство с HTML, каскадом CSS и обработчиками событий.

У каждой карточки есть тема и длительность. Эти значения служат исходными данными, а не результатом измерения: двадцать минут обозначают редакционную оценку времени чтения. Ссылки ведут на отдельный документ article.html с тремя разделами. Такой маленький набор удобен для изучения поведения: смена фильтра в дальнейшем должна оставить две карточки контента либо одну карточку интерфейса. Если сразу взять тысячу случайных записей, причину ошибки будет труднее увидеть.

Сначала содержание, затем возможности

В полном HTML уже есть ориентиры header, main, footer, ссылка пропуска и заголовок первого уровня. Список карточек остаётся ul: группировка записей не меняется от того, как они расположены сеткой. Заголовок содержит ссылку, поэтому открыть урок можно клавиатурой, в новой вкладке и из контекстного меню. Карточка целиком не получает обработчик клика. Это сохраняет обычные действия браузера и позволяет выделить её текст.

CSS задаёт ширину контейнера, читаемый интервал и сетку, которая переходит к одной колонке. Выражение min(100%, 18rem) не заставляет карточку выходить за узкий экран. Видимый focus-visible помогает различать текущую ссылку без мыши. Он не заменяет порядок DOM: порядок фокуса совпадает с порядком материалов. Основные ссылки имеют подчёркивание, чтобы их назначение не определялось одним цветом.

Короткий app.js отмечает наличие слоя улучшений. Он пока не перестраивает список и не прячет текст. Признак data-enhanced пригодится при исследовании состояния документа, но не является универсальной проверкой совместимости. Успешная загрузка JavaScript не означает наличия любого нового CSS-свойства. В следующих этапах каждое улучшение будет включаться по своему признаку.

Наблюдаемый эффект этого этапа — обычный каталог, в котором три названия открывают соответствующие фрагменты статьи. Он ожидается из написанной разметки; браузерный запуск при подготовке не выполнялся. Для будущего просмотра начните с отключённого JavaScript. Затем включите его и убедитесь, что сами ссылки и данные не меняются. Такая последовательность отделяет обязательную часть продукта от дополнительного поведения.

Не делайте все кнопки видимыми заранее с обещанием, что сценарий потом оживит их. Посетитель может нажать элемент раньше завершения загрузки. Мы будем показывать управляющие элементы после регистрации обработчика, а содержимое сохранять в обычном документе. Исключение допустимо для настоящей ссылки или нативной формы, которая уже имеет самостоятельный путь действия.

Полный файл index.html текущего этапа:

<!doctype html>
<html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Учебный каталог</title><link rel="stylesheet" href="styles.css"><script type="module" src="app.js"></script></head>
<body><a href="#main">Перейти к материалам</a><header><a href="index.html">Учебный каталог</a></header><main id="main"><h1>Учебные материалы</h1><p>Каталог «Полка»: статьи о контенте и интерфейсах.</p><ul class="cards"><li class="card" data-topic="content" data-minutes="20"><h2><a href="article.html#markdown">Markdown и структура урока</a></h2><p class="card-description">Разделяем содержание и оформление библиотеки.</p><p class="card-meta">Контент · 20 минут</p></li>
<li class="card" data-topic="interface" data-minutes="35"><h2><a href="article.html#navigation">Навигация учебного каталога</a></h2><p class="card-description">Сохраняем обычные ссылки и последовательность чтения.</p><p class="card-meta">Интерфейс · 35 минут</p></li>
<li class="card" data-topic="content" data-minutes="25"><h2><a href="article.html#urls">Постоянные адреса материалов</a></h2><p class="card-description">Меняем каталог, сохраняя адрес каждой статьи.</p><p class="card-meta">Контент · 25 минут</p></li></ul><p class="notice">Каждый урок доступен по обычной ссылке. Фильтры и эффекты дополняют чтение.</p></main><footer>Полка · учебный проект</footer></body></html>

Полный файл app.js текущего этапа:

document.documentElement.dataset.enhanced = 'true';

Полный файл styles.css текущего этапа:

* { box-sizing: border-box; }
:root { color-scheme: light; --accent: #6d28d9; --paper: #fff; --ink: #20202a; }
body { margin: 0; color: var(--ink); background: #f7f6fb; font: 1rem/1.6 system-ui, sans-serif; }
header, main, footer { max-width: 72rem; margin-inline: auto; padding: 1.5rem; }
a { color: #5923b5; text-underline-offset: .18em; }
button { font: inherit; padding: .5rem .9rem; cursor: pointer; }
:focus-visible { outline: 3px solid #17693d; outline-offset: 4px; }
[hidden] { display: none !important; }
.cards { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); list-style: none; padding: 0; }
.card, aside { padding: 1.25rem; border: 1px solid #ddd8e8; border-radius: .7rem; background: white; }
.card h2 { margin-block-start: 0; }
.card-meta { color: #51495f; }
.notice { border-inline-start: .25rem solid var(--accent); padding-inline-start: 1rem; }
dialog { width: min(36rem, calc(100vw - 2rem)); border: 1px solid #b9afca; padding: 1.5rem; border-radius: .8rem; }
dialog::backdrop { background: rgb(20 15 30 / .5); }

Разбор изменения каталога

Семантическая основа пригодится и при росте списка. Если в каталоге станет двадцать записей, сетка решит вопрос размещения, но не определит тему, адрес и порядок. Эти данные должны существовать до визуального слоя. Сейчас они показаны прямо в HTML, чтобы не добавлять генератор раньше изучения браузерных возможностей. В дальнейшем такой же документ можно получить из Markdown, сохранив смысл ссылок и заголовков.

Ссылка пропуска особенно полезна на длинной странице. Человек, который открыл уже десятый урок, не обязан каждый раз проходить все пункты меню. Её адрес указывает на существующий main, а не просто на первое большое визуальное поле. При проверке важно увидеть конечный фокус или положение чтения, а не только перемещение пикселей. Браузеры и вспомогательные технологии могут по-разному трактовать фрагменты, поэтому настоящий целевой маршрут требует наблюдения.

Для самостоятельного изменения добавьте четвёртую карточку темы content с уникальным названием и ссылкой на новый раздел статьи. Сначала согласуйте href и id раздела, затем описание и длительность. Не меняйте одновременно размеры всех карточек. После этого будущая фильтрация должна давать уже три записи контента. Такая маленькая модификация показывает, что интерфейс зависит от данных и не должен содержать зашитое число результатов.

Рассмотрим ошибочную версию карточки: вместо ссылки в заголовке стоит div, а обработчик назначен всему контейнеру. На первом просмотре мышью переход может выглядеть исправным. Однако фокус не приходит к названию обычным Tab, новая вкладка не предлагается привычным действием, а при выборе текста срабатывает переход. Исправление начинается с возврата настоящего href, а не с добавления отдельных обходов для каждого симптома.

Другая ошибка возникает, когда визуальные карточки сортируются только через CSS order. Человек видит новый порядок, но DOM и клавиатурная последовательность могут остаться прежними. В нашем исходнике порядок материалов совпадает в обоих представлениях. Если позднее появится редакционная сортировка, она должна менять данные или разметку, а не только положение блоков на экране. Проверяйте порядок чтения вместе с положением карточек.

Обратите внимание на раздел статьи, к которому ведёт каждый fragment. Ссылка article.html#urls корректна лишь при существующем идентификаторе. Браузер может открыть документ даже без такого раздела, поэтому успешная загрузка файла ещё не означает точного перехода. Для маленького набора выпишите три пары href и id и сравните их вручную. Эта простая сверка даёт более ясный адресный договор, чем общая надпись о готовности страницы.

Наконец, оформление сеткой не делает карточки независимыми документами. Они остаются элементами одного списка. Не добавляйте роль button на весь li и не помещайте одну ссылку поверх другого элемента управления. Когда в будущем у карточки появится отдельное действие, оно должно иметь собственное понятное место в порядке фокуса, не споря с ссылкой названия.

Условия для самостоятельного наблюдения

Это план будущего разбора, а не журнал выполненных проверок. Рассмотрите одинаковые исходные данные и сопоставьте действие с ожидаемым состоянием.

Условие Ожидаемое состояние и его граница
Отключённый JavaScript Все три ссылки остаются в списке, а документ статьи можно открыть обычным переходом.
Экран уже карточки Сетка должна перейти к одной колонке без горизонтального выхода содержимого.
Работа клавиатурой Ссылка пропуска получает фокус и переносит его к основному содержимому документа.
Длинное название Название занимает несколько строк, не перекрывая описание соседней карточки.
Переход в новую вкладку Контекстное меню ссылки сохраняет стандартные действия браузера.
Отсутствующий файл статьи Ошибка ресурса остаётся проблемой маршрута; эффект интерфейса не может исправить её.

Оглавление серии, курсы ProfessorWeb и полный архив исходников. Для этого урока выберите lesson-01.

Первичные источники: Interop 2026.

Редакционный статус: локальный черновик. Установка, учебные программы, браузерные сценарии, тесты и публикация не выполнялись. Описанные эффекты ожидаемые.