Сценарий почти всегда один и тот же: таблица данных внутри прокручиваемого контейнера. В каждой строке есть меню действий — небольшой раскрывающийся список с такими опциями, как «Редактировать», «Дублировать» и «Удалить». Вы создаете его, и кажется, что он отлично работает изолированно, а затем кто-то помещает его в этот прокручиваемый div, и все разваливается. Я видел именно эту ошибку в трех разных базах кода: контейнере, стеке и фреймворке, причем все они разные. Однако ошибка абсолютно идентична. Раскрывающийся список обрезается по краю контейнера. Или он появляется за контентом, который по логике должен быть ниже него. Или он работает нормально, пока пользователь не прокручивает страницу, а затем сбивается. Вы набираете z-index: 9999. Иногда это помогает, но иногда абсолютно ничего не дает. Эта несогласованность является первым признаком того, что происходит нечто более глубокое. Причина, по которой он продолжает возвращаться, заключается в том, что задействованы три отдельные системы браузеров, и большинство разработчиков понимают каждую из них по отдельности, но никогда не задумываются о том, что происходит, когда все три сталкиваются: переполнение, стекирование контекстов и содержание блоков.

Как только вы поймете, как взаимодействуют все три, режимы отказа перестанут казаться случайными. Фактически, они становятся предсказуемыми. Три вещи, которые на самом деле вызывают это Давайте рассмотрим каждый из этих пунктов подробно. Проблема переполнения Когда вы устанавливаете для элемента overflow:hidden, overflow:scroll или overflow:auto, браузер будет обрезать все, что выходит за его границы, включая абсолютно позиционированные потомки. .scroll-контейнер { переполнение: авто; высота: 300 пикселей; /* Это обрежет раскрывающийся список, точку */ }

.dropdown { позиция: абсолютная; /* Не имеет значения — все равно обрезается .scroll-container */ }

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

Вот пример React с использованием createPortal:

импортировать { createPortal } из 'реагировать-дом'; импортировать {useState, useEffect, useRef} из «реагировать»;

function Dropdown({anchorRef, isOpen, Children }) { const [позиция, setPosition] = useState({верх: 0, слева: 0});

useEffect(() => { if (isOpen && anchorRef.current) { const rect = anchorRef.current.getBoundingClientRect(); setPosition({ вверху: rect.bottom + window.scrollY, слева: rect.left + window.scrollX, }); } }, [isOpen, anchorRef]);

if (!isOpen) возвращает ноль;

вернуть createPortal( <дел id="выпадающий-демо" роль="меню" className="выпадающее меню" стиль = {{ позиция: 'абсолютный', сверху: позиция.верх, слева: позиция.слева }} > {дети} , документ.тело ); }

И, конечно же, мы не можем игнорировать доступность. Фиксированные элементы, которые появляются поверх содержимого, по-прежнему должны быть доступны с клавиатуры. Если порядок фокуса не перемещается естественным образом в фиксированный раскрывающийся список, вам придется управлять им с помощью кода. Также стоит проверить, не находится ли он поверх другого интерактивного контента, без возможности его отклонения. Это укусит вас при тестировании клавиатуры. Позиционирование привязки CSS: к чему, по моему мнению, это приведет CSS Anchor Positioning — это направление, которое меня сейчас больше всего интересует. Когда я впервые взглянул на нее, я не был уверен, насколько эта спецификация действительно пригодна для использования. Он позволяет объявить связь между раскрывающимся списком и его триггером непосредственно в CSS, а координаты обрабатывает браузер. .триггер { имя-якоря: --my-trigger; }

.dropdown-меню { позиция: абсолютная; позиция-якорь: --my-trigger; вверху: якорь (внизу); слева: якорь (слева); позиционные попытки-резервные варианты: флип-блок, флип-инлайн; }

Свойство Position-try-fallbacks делает его выгодным для использования вместо ручного расчета. Браузер пробует альтернативные места размещения, прежде чем сдаться, поэтому раскрывающийся список в нижней части области просмотра автоматически переворачивается вверх, а не обрезается. Поддержка браузеров стабильна в браузерах на базе Chromium и расширяется в Safari. Firefox нуждается в полифиле. Пакет @oddbird/css-anchor-positioning охватывает основную спецификацию. С его помощью я столкнулся с крайними случаями макета, которые требовали отступлений, которых я не ожидал, поэтому рассматривайте его как прогрессивное улучшение или сочетайте его сРезервный вариант JavaScript для Firefox. Короче говоря, перспективно, но пока не универсально. Протестируйте в целевых браузерах. А что касается доступности, то объявление визуальных отношений в CSS ничего не говорит дереву доступности. aria-controls, aria-expanded, aria-haspopup — эта часть все еще на вас. Иногда исправление заключается в простом перемещении элемента Прежде чем добраться до портала или выполнить расчет координат, я всегда сначала задаю один вопрос: действительно ли этот раскрывающийся список должен находиться внутри контейнера прокрутки? Если это не так, перемещение разметки в оболочку более высокого уровня полностью устраняет проблему без использования JavaScript и вычислений координат. Это не всегда возможно. Если кнопка и раскрывающийся список инкапсулированы в одном компоненте, перемещение одного без другого означает переосмысление всего API. Но когда вы можете это сделать, отлаживать нечего. Проблема просто не существует. Чего современный CSS все еще не решает CSS прошел долгий путь в этом отношении, но все еще есть места, где он вас подводит. Позиция: проблемы исправления и преобразования все еще существуют. Это намеренно включено в спецификацию, а это означает, что обходного пути CSS не существует. Если вы используете библиотеку анимации, которая оборачивает ваш макет в преобразованный элемент, вам снова нужны порталы или позиционирование привязки. CSS Anchor Positioning — многообещающий, но новый подход. Как упоминалось ранее, на момент написания этой статьи Firefox все еще нуждался в полифиле. С его помощью я столкнулся с крайними случаями макета, которые требовали отступлений, которых я не ожидал. Если сегодня вам нужно единообразное поведение во всех браузерах, вам все равно придется использовать JavaScript для решения сложных задач. Добавление, ради которого я фактически изменил свой рабочий процесс, — это HTML Popover API, который теперь доступен во всех современных браузерах. Элементы с атрибутом popover отображаются в верхнем слое браузера, над всем остальным, без необходимости позиционирования JavaScript.

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

Используйте портал. Я бы использовал его, когда триггер находится глубоко во вложенных контейнерах прокрутки. Я использовал этот шаблон для меню действий с таблицами и соединил его с восстановлением фокуса и проверками доступности. Это самый надежный вариант, но стоит потратить время на дополнительную проводку. Используйте фиксированное позиционирование. Это подходит для случаев, когда вы используете стандартный JavaScript или облегченную структуру и не можете проверить, что ни один предок не применяет преобразования или фильтры. Его легко настроить и легко отладить, пока сохраняется это одно ограничение. Используйте для этого CSS Anchor Positioning.Reach, если поддержка вашего браузера позволяет это. Если требуется поддержка Firefox, соедините ее с полифилом @oddbird. Именно к этому в конечном итоге движется платформа и в конечном итоге станет вашим основным подходом. Реструктурируйте DOM. Используйте это, когда архитектура позволяет это, и вы хотите, чтобы сложность времени выполнения была нулевой. Я считаю, что это, вероятно, самый недооцененный вариант. Комбинируйте шаблоны. Сделайте это, если вы хотите, чтобы позиционирование привязки было основным подходом в сочетании с резервным вариантом JavaScript для неподдерживаемых браузеров. Или портал для размещения DOM в сочетании с getBoundingClientRect() для точности координат.

Заключение Раньше я относился к этой ошибке как к разовой проблеме — к чему-то, что нужно исправить и от чего двигаться дальше. Но как только я посидел с ним достаточно долго, чтобы понять все три задействованные системы — отсечение переполнения, укладку контекстов и содержание блоков — оно перестало казаться случайным. Я мог посмотреть на сломанный раскрывающийся список и сразу же отследить, какой предок был ответственен за это. Этот сдвиг в том, как я читаю DOM, стал настоящим выводом. Нет единственного правильного ответа. То, чего я добивался, зависело от того, чем я мог управлять в кодовой базе: порталы, когда дерево предков было непредсказуемым; исправлено позиционирование, когда оно было чистым и простым; перемещать элемент, когда меня ничего не останавливало; и позиционирование якоря сейчас,где я могу. Что бы вы в конечном итоге ни выбрали, не рассматривайте доступность как последний шаг. По моему опыту, именно тогда его пропускают. Отношения ARIA, управление фокусом, поведение клавиатуры — все это не идеально. Они являются частью того, что заставляет эту вещь работать. Полный исходный код можно найти в моем репозитории на GitHub. Дальнейшее чтение Вот ссылки, к которым я постоянно возвращался, работая над этим:

Контекст стекирования (MDN) «Руководство по позиционированию привязки CSS», Хуан Диего Родригес «Начало работы с API Popover», Godstime Aburu Плавающий пользовательский интерфейс (floating-ui.com) Переполнение CSS (MDN)

You May Also Like

Enjoyed This Article?

Get weekly tips on growing your audience and monetizing your content — straight to your inbox.

No spam. Join 138,000+ creators. Unsubscribe anytime.

Create Your Free Bio Page

Join 138,000+ creators on Seemless.

Get Started Free