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

Привязка справки к кнопке через CSS

Всплывающая справка уже открывается и закрывается средствами браузера. Сейчас привяжем её к кнопке, чтобы связь стала заметной и визуально. CSS anchor positioning позволяет описать такую геометрию без чтения getBoundingClientRect после каждого изменения окна. Для нашего каталога нужен один якорь и одна панель. Начальное размещение остаётся доступным запасным вариантом.

Кнопка получает anchor-name: --catalog-help. Два дефиса отличают пользовательское имя от обычного ключевого слова. Панель указывает такое же значение в position-anchor. Это явная связь: не нужно полагаться на то, какой элемент движок автоматически выберет в сложной разметке. При появлении нескольких карточек с одинаковым именем область и порядок якорей потребуют отдельного проектирования.

Состояние панели и её положение

Позиционирование включается внутри @supports, который проверяет anchor-name и position-area. Проверка говорит о распознавании синтаксиса, а не подтверждает отсутствие любых ошибок реализации. Поэтому сама панель и обычный текстовый маршрут не зависят от её успеха. В неподдерживаемом браузере правило пропускается и остаётся стандартный popover.

Для панели задаём position: fixed, снимаем исходные inset и margin, затем выбираем область bottom. Если оставить центрирующие правила всплывающего элемента, они могут продолжить влиять на геометрию и затруднить объяснение результата. Полный набор деклараций показывает намерение: разместить панель под якорем, оставить небольшой промежуток и ограничить ширину доступным экраном.

Здесь position-area описывает область относительно якоря. Это не ещё один вариант display: grid, хотя модель областей помогает мысленно разделить пространство вокруг кнопки. Не пытайтесь вывести из названия свойства поведение обычной сетки карточек. Для верхнего слоя и фиксированного блока важны правила конкретного механизма позиционирования.

Ширина min(25rem, calc(100vw - 2rem)) ограничивает панель даже тогда, когда текст короткий. Сам якорь не определяет подходящую ширину справки. Поле выбора размера остаётся частью дизайна: слишком узкая панель превращает небольшой абзац в длинный столбец, а слишком широкая перекрывает связь с кнопкой. Содержание и доступное пространство должны рассматриваться вместе.

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

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

Полный файл 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><button type="button" id="open-lesson" hidden>О проекте урока</button>
<dialog id="lesson-dialog" aria-labelledby="dialog-title"><h2 id="dialog-title">Учебная библиотека</h2><p>Три урока с постоянными ссылками. Карточка дополняет каталог.</p><form method="dialog"><button value="close" autofocus>Закрыть</button></form></dialog><button type="button" id="help-button" popovertarget="help" hidden>Как читать каталог</button>
<aside id="help"><h2>Чтение каталога</h2><p>Откройте название урока. Справка не закрывает доступ к другим ссылкам.</p></aside><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';
const lessonDialog = document.querySelector('#lesson-dialog');
const openLesson = document.querySelector('#open-lesson');
if (typeof lessonDialog.showModal === 'function') {
  openLesson.hidden = false;
  openLesson.addEventListener('click', () => {
    if (!lessonDialog.open) lessonDialog.showModal();
  });
  lessonDialog.addEventListener('close', () => openLesson.focus());
}
if ('popover' in HTMLElement.prototype) {
  document.querySelector('#help').setAttribute('popover', 'auto');
  document.querySelector('#help-button').hidden = false;
}

Полный файл 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); }

@supports (anchor-name: --catalog-help) and (position-area: bottom) {
  #help-button { anchor-name: --catalog-help; }
  #help[popover] { position: fixed; position-anchor: --catalog-help; position-area: bottom; inset: auto; margin: .5rem; width: min(25rem, calc(100vw - 2rem)); }
}

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

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

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

Для самостоятельного варианта разместите блок справки в другом месте DOM, сохранив ID и связь кнопки. Посмотрите, почему верхний слой освобождает панель от обычного соседства, а имя якоря сохраняет геометрическое отношение. Затем уберите position-anchor. Такое изменение помогает отделить механизм открытия от механизма размещения и не считать popover автоматической подсказкой возле кнопки.

Геометрия может измениться после загрузки шрифта или длинной локализованной надписи. В координатном JavaScript-решении пришлось бы заново читать размеры. Anchor positioning выражает связь декларативно, но это не означает, что любое содержание автоматически станет удобным. Ширина кнопки, длина текста и выбранная область всё ещё влияют на результат, поэтому реальные данные нужны при просмотре.

Вариант с отдельным anchor() позволяет описывать конкретные края и размеры, однако в этом этапе он не требуется. Мы используем position-area, чтобы читатель видел один ясный договор. Не объединяйте несколько способов размещения без понимания их взаимодействия с inset и margin. Получившийся стиль может содержать взаимоисключающие намерения и выглядеть случайным набором исправлений.

Проверка CSS.supports в сценарии тоже могла бы распознать нужную декларацию. Здесь условие находится в самом CSS, потому что JavaScript не обязан знать декоративное положение панели. Не делайте вывод, что все проверки должны быть вынесены в одну глобальную функцию. Место проверки зависит от потребителя: состояние панели включает сценарий, геометрию применяет каскад.

Смысл текущего результата можно объяснить без чисел координат: эта панель принадлежит этой кнопке и предпочитает область под ней. Если для передачи компонента нужны десятки вычисленных пикселей, возможно, исходный договор стоит упростить. Следующий урок добавляет конечный набор запасных положений, а не превращает размещение в непрозрачный поиск любой свободной точки.

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

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

Условие Ожидаемое состояние и его граница
Якорь в середине Панель должна занять область под кнопкой и сохранить ограничение собственной ширины.
Неизвестное CSS-свойство Сохраняется базовое положение popover; управление открытием не исчезает.
Кнопка перемещена Связь задаётся именем якоря, а не запомненным значением координаты.
У окна узкая ширина Панель ограничивается viewport и оставляет предусмотренный отступ.
У кнопки нет имени Явная связь position-anchor не найдёт назначенный якорь; требуется исправление разметки.
Несколько одинаковых имён Для повторяемых компонентов необходимо определить область или уникальные связи якорей.

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

Первичные источники: Anchor positioning.

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