Сценарій майже завжди однаковий, тобто таблиця даних у контейнері, який можна прокручувати. У кожному рядку є меню дій, невелике спадне меню з деякими параметрами, як-от «Редагувати», «Дублювати» та «Видалити». Ви створюєте його, здається, що він ідеально працює в ізоляції, а потім хтось поміщає його в цей прокручуваний div, і все розвалюється. Я бачив цю точну помилку в трьох різних кодових базах: контейнері, стеку та фреймворку, всі різні. Однак помилка абсолютно ідентична. Випадаюче меню обрізається на краю контейнера. Або він відображається за вмістом, який за логікою має бути під ним. Або він працює добре, доки користувач не прокручує, а потім дрейфує. Ви досягаєте z-індексу: 9999. Іноді це допомагає, але іноді абсолютно нічого. Ця невідповідність є першою підказкою того, що відбувається щось глибше. Причина, чому це постійно повертається, полягає в тому, що задіяні три окремі системи браузера, і більшість розробників розуміють кожну окремо, але ніколи не замислюються про те, що відбувається, коли всі три стикаються: переповнення, контексти стекування та блоки, що містять.

Коли ви зрозумієте, як усі три взаємодіють, режими збоїв перестануть здаватися випадковими. Насправді вони стають передбачуваними. Три речі, які насправді спричиняють це Розглянемо кожен із цих пунктів докладніше. Проблема переповнення Коли ви встановлюєте для елемента overflow: hidden, overflow: scroll або overflow: auto, браузер відсікатиме все, що виходить за його межі, включаючи абсолютно позиціоновані нащадки. .scroll-container { перелив: авто; висота: 300 пікселів; /* Це вирізає спадне меню, крапка */ }

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

Це мене здивувало, коли я вперше зіткнувся з цим. Я припустив позицію: absolute дозволить елементу уникнути відсікання контейнера. Це не так. На практиці це означає, що абсолютно позиціоноване меню може бути відсічене будь-яким предком, який має невидиме значення переповнення, навіть якщо цей предок не є блоком, що містить меню. Відсікання та позиціонування є окремими системами. Вони просто випадково стикаються способами, які виглядають абсолютно випадковими, поки ви не зрозумієте обидва.

Ось приклад React з використанням createPortal:

import { createPortal } from 'react-dom'; import { useState, useEffect, useRef } from 'react';

функція Dropdown({ anchorRef, isOpen, діти }) { const [position, setPosition] = useState({ top: 0, left: 0 });

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

if (!isOpen) повертає null;

повернути createPortal(

, документ.тіло ); }

І, звичайно, ми не можемо ігнорувати доступність. Фіксовані елементи, які з’являються поверх вмісту, мають бути доступними з клавіатури. Якщо порядок фокусування не переміщується у фіксований спадний список, вам потрібно буде керувати ним за допомогою коду. Варто також перевірити, чи він не знаходиться над іншим інтерактивним вмістом і не має можливості його відхилити. Це кусає вас під час тестування клавіатури. Позиціонування прив’язки CSS: куди, я думаю, це веде CSS Anchor Positioning – це напрямок, який мене зараз найбільше цікавить. Коли я вперше подивився на нього, я не був упевнений, наскільки технічні характеристики дійсно придатні для використання. Це дозволяє оголосити зв’язок між спадним меню та його тригером безпосередньо в CSS, а браузер обробляє координати. .trigger { anchor-name: --my-trigger; }

.dropdown-menu { позиція: абсолютна; position-anchor: --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 все ще потребує polyfill у той час, коли я пишу це. З ним я досягав крайових випадків компонування, які вимагали запасних варіантів, яких я не очікував. Якщо сьогодні вам потрібна узгоджена поведінка в усіх веб-переглядачах, ви все ще використовуєте JavaScript для складних частин. Доповненням, для якого я фактично змінив свій робочий процес, є HTML Popover API, тепер доступний у всіх сучасних браузерах. Елементи з атрибутом popover відображаються у верхньому шарі веб-переглядача, над усім, без позиціонування JavaScript.

Обробка виходу, відхилення при натисканні назовні та надійна семантика доступності надаються безкоштовно для таких речей, як спливаючі підказки, віджети розкриття та прості накладання. Це перший інструмент, до якого я зараз дотягнувся. Тим не менш, це не вирішує позиціонування. Це вирішує нашарування. Вам все ще потрібне позиціонування прив’язки або JavaScript, щоб вирівняти спливаюче вікно за його тригером. Popover API обробляє шарування. Позиціонування прив’язки керує розміщенням. Якщо їх використовувати разом, вони охоплюють більшість того, що ви раніше хотіли робити в бібліотеці. Посібник для прийняття рішень у вашій ситуації Пройшовши через усе це важким шляхом, ось як я насправді думаю про вибір зараз.

Використовуйте портал. Я б використовував це, коли тригер живе глибоко у вкладених контейнерах прокручування. Я використав цей шаблон для меню дій таблиці та поєднав його з відновленням фокусу та перевіркою доступності. Це найнадійніший варіант, але бюджетний час на додаткову проводку. Використовуйте фіксоване позиціонування. Це для тих випадків, коли ви використовуєте мінімальний JavaScript або спрощений фреймворк і можете переконатися, що жоден предок не застосовує трансформації чи фільтри. Його легко налаштувати та легко налагодити, доки виконується це одне обмеження. Використовуйте CSS Anchor Positioning. Досягайтеся цього, коли це дозволяє підтримка вашого браузера. Якщо потрібна підтримка Firefox, поєднайте її з @oddbird polyfill. Це те, куди платформа зрештою прямує, і з часом стане вашим підходом. Реструктуруйте DOM. Використовуйте це, коли це дозволяє архітектура, і вам потрібна нульова складність виконання. Я вважаю, що це найбільш недооцінений варіант. Комбінуйте шаблони. Зробіть це, якщо ви бажаєте позиціонування прив’язки як основний підхід у поєднанні з резервним JavaScript для непідтримуваних браузерів. Або портал для розміщення DOM у поєднанні з getBoundingClientRect() для точності координат.

Висновок Раніше я розглядав цю помилку як одноразову проблему — щось, що потрібно виправити та рухатися далі. Але як тільки я посидів з ним достатньо довго, щоб зрозуміти всі три задіяні системи — відсікання переповнення, контексти стекування та блоки, що містять — це перестало здаватися випадковим. Я міг би подивитися на несправний спадний список і відразу відстежити, який предок відповідальний. Ця зміна в тому, як я читаю DOM, стала справжнім висновком. Немає єдиної правильної відповіді. Те, до чого я прагнув, залежало від того, що я міг контролювати в кодовій базі: портали, коли дерево предків було непередбачуваним; фіксоване позиціонування, коли воно було чистим і простим; рухати елемент, коли мене ніщо не зупиняло; і позиціонування якоря зараз,де можу. Що б ви не вибрали, не розглядайте доступність як останній крок. З мого досвіду, саме тоді його пропускають. Зв’язки ARIA, керування фокусом, поведінка клавіатури — це не досконало. Вони є частиною того, що змушує справу насправді працювати. Перегляньте повний вихідний код у моєму сховищі GitHub. Подальше читання Це посилання, до яких я постійно повертався, працюючи над цим:

Контекст стекування (MDN) «Посібник із позиціонування прив’язки CSS», Хуан Дієго Родрігес «Початок роботи з Popover API», 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