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

Видимый и незакрытый фокус

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

Продолжим a11y-lab/lesson.css и lesson.js из предыдущего урока. Добавим различимый индикатор, затем введём закреплённый хедер только для достаточно широкого и высокого окна. Высоту панели будем получать из самого элемента, а не угадывать по размеру одного скриншота. Это позволит объяснить поведение при изменении текста и ширины.

Индикатор положения

В начальных стилях уже существует рамка для фокуса. Сохраним базовое правило :focus и добавим следующие уточнения в конец CSS. Они показывают назначение дополнительного слоя, не отключая индикатор полностью ради внешнего вида:

:root {
  --focus-color: #56318c;
  --header-offset: 0px;
}

a:focus, button:focus, input:focus, textarea:focus {
  outline: 3px solid var(--focus-color);
  outline-offset: 3px;
}

:focus-visible {
  outline: 3px solid var(--focus-color);
  outline-offset: 3px;
}

main:focus {
  outline: 3px solid var(--focus-color);
  outline-offset: 3px;
}

Первое правило обеспечивает видимый результат для перечисленных элементов даже без применения специального псевдокласса. :focus-visible позволяет описать случаи, когда браузер считает индикатор необходимым для текущего способа взаимодействия. Здесь нет правила :focus { outline: none; }, поэтому изменение не превращается в удаление исходной подсказки.

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

В WAI, Focus Visible объясняется необходимость видимого индикатора клавиатурного фокуса. Эта задача отличается от оформления наведения мыши. :hover может существовать одновременно с фокусом или не появляться вовсе; подсвечивать только наведённую ссылку недостаточно для маршрута Tab.

Цвет в примере — учебный фиолетовый акцент, а не отчёт контраста всех элементов ProfessorWeb. Выбор окончательного сочетания требует учитывать соседние цвета, фон ссылки и режим отображения. Толстая рамка, которая совпадает с окружающей панелью, всё равно может теряться. В следующем блоке курса разберём измерение контраста и смысл, передаваемый помимо цвета.

Закреплённая область меняет свободное пространство

Добавим к стилям хедера небольшое правило компоновки и условие закрепления. Основная навигация остаётся обычными ссылками; новый CSS не меняет её порядок в HTML:

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  background: #fff;
}

.site-header nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
}

@media (min-width: 64rem) and (min-height: 42rem) {
  .site-header {
    position: sticky;
    top: 0;
    z-index: 20;
  }
}

html {
  scroll-padding-block-start: calc(var(--header-offset) + 1rem);
}

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

scroll-padding-block-start описывает желаемый отступ в области прокрутки. Для якорного перехода к #descendants он помогает оставить место над целевым заголовком. Параметр не создаёт отступ в обычной вёрстке и не уменьшает физическую высоту хедера. Его действие относится к позиционированию при прокрутке, поэтому один и тот же текст страницы до перехода не обязан менять положение. Область применения и синтаксис приведены в MDN.

Если вместо измеренной высоты записать постоянные 80px, результат будет зависеть от удачного совпадения. Навигация может перенестись на другую строку, шрифт — измениться, а перевод — увеличить подпись. Число останется прежним. Для нашего стенда удобнее связать переменную с фактической высотой закреплённого элемента.

Измеряем панель, а не размер предположения

Добавьте следующий фрагмент в конец существующего lesson.js. Обработчик специальной ссылки из прошлого урока сохраняется. Здесь используется тот же .site-header, который уже присутствует в HTML:

const siteHeader = document.querySelector('.site-header');

function updateHeaderOffset() {
  const isSticky = getComputedStyle(siteHeader).position === 'sticky';
  const height = isSticky ? siteHeader.getBoundingClientRect().height : 0;
  document.documentElement.style.setProperty('--header-offset', `${height}px`);
}

updateHeaderOffset();
window.addEventListener('resize', updateHeaderOffset);

if ('ResizeObserver' in window) {
  const headerObserver = new ResizeObserver(updateHeaderOffset);
  headerObserver.observe(siteHeader);
}

Функция сначала определяет, закреплён ли хедер в текущем представлении. Для обычного потока дополнительная компенсация равна нулю. Для закреплённого варианта берётся высота прямоугольника элемента. ResizeObserver отслеживает изменение его размера, а обработчик окна учитывает смену условия media query. Без поддержки наблюдателя остаются начальное измерение и реакция на изменение окна.

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

Индикатор тоже может быть обрезан родительским контейнером с overflow: hidden. В такой ситуации увеличение толщины рамки не устраняет причину. Нужно понять, зачем контейнер обрезает содержание, можно ли перенести рамку внутрь или изменить область. Приём, полезный для скруглённой иллюстрации, не обязательно подходит для контейнера управляющих ссылок.

Проверяем разные причины скрытия

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

Критерий Focus Not Obscured (Minimum) уровня AA касается ситуации, когда сфокусированный компонент полностью скрыт авторским содержимым. Не следует смешивать этот минимум с более строгим пожеланием видеть абсолютно весь компонент в любых обстоятельствах. Для учебного сайта разумно стремиться к понятному полному представлению, но в отчёте важно точно назвать проверенное условие.

Ожидаемый результат нашего изменения: при широком окне хедер закреплён, переменная отражает его высоту, переход по оглавлению оставляет место над разделом, а клавиатурный индикатор сохраняется. При узком или низком окне хедер перестаёт быть закреплённым, компенсация сбрасывается. Выполнение этих сценариев потребуется наблюдать в выбранных браузерах, отдельно от чтения кода.

Теперь маршрут имеет визуальную опору и явную модель верхнего перекрытия. Дальше внимание перейдёт от управления к содержанию: скриншоты и схемы должны передавать существенный смысл, когда читатель получает его другим способом. Различимый фокус помогает добраться до материала, но не заменяет текстовое объяснение результата программы.