Сценарият е почти винаги един и същ, което е таблица с данни в превъртащ се контейнер. Всеки ред има меню с действия, малко падащо меню с някои опции, като Редактиране, Дублиране и Изтриване. Изграждате го, изглежда, че работи перфектно изолирано, а след това някой го поставя в този превъртащ се div и нещата се разпадат. Виждал съм този точен бъг в три различни кодови бази: контейнер, стек и рамка, всички различни. Грешката обаче е напълно идентична. Падащото меню се подрязва на ръба на контейнера. Или се показва зад съдържание, което логично трябва да е под него. Или работи добре, докато потребителят превърти, и след това се отклонява. Посягате към z-index: 9999. Понякога помага, но друг път не прави абсолютно нищо. Тази непоследователност е първата улика, че се случва нещо по-дълбоко. Причината, поради която продължава да се връща, е, че са включени три отделни системи на браузъри и повечето разработчици разбират всяка от тях сама по себе си, но никога не мислят какво се случва, когато и трите се сблъскат: препълване, подреждане на контексти и съдържащи блокове.
След като разберете как си взаимодействат и трите, режимите на отказ престават да се чувстват случайни. Всъщност те стават предсказуеми. Трите неща, които всъщност причиняват това Нека разгледаме подробно всеки от тези елементи. Проблемът с препълването Когато зададете overflow: hidden, overflow: scroll или overflow: auto на елемент, браузърът ще изреже всичко, което се простира извън неговите граници, включително абсолютно позиционирани наследници. .scroll-container { преливане: авто; височина: 300px; /* Това ще изреже падащото меню, точка */ }
.dropdown { позиция: абсолютна; /* Няма значение -- все още се изрязва от .scroll-container */ }
Това ме изненада първия път, когато се натъкнах на него. Бях приел позиция: absolute ще позволи на елемент да избегне изрязването на контейнер. Не става. На практика това означава, че абсолютно позиционирано меню може да бъде отрязано от всеки предшественик, който има невидима стойност на препълване, дори ако този предшественик не е блокът, съдържащ менюто. Изрязването и позиционирането са отделни системи. Те просто се сблъскват по начини, които изглеждат напълно произволни, докато не разберете и двете.
Ето пример за React с използване на createPortal:
import { createPortal } from 'react-dom'; import { useState, useEffect, useRef } from 'react';
функция 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) върне null;
връщане createPortal(
И, разбира се, не можем да пренебрегнем достъпността. Фиксираните елементи, които се появяват над съдържанието, все още трябва да са достъпни от клавиатурата. Ако редът на фокуса не се премести естествено във фиксираното падащо меню, ще трябва да го управлявате с помощта на код. Също така си струва да проверите дали не стои над друго интерактивно съдържание без начин да го отхвърлите. Този ви хапе при тестването на клавиатурата. Позициониране на CSS Anchor: Накъде смятам, че отива това CSS Anchor Positioning е посоката, която ме интересува най-много в момента. Не бях сигурен каква част от спецификацията всъщност е използваема, когато я погледнах за първи път. Той ви позволява да декларирате връзката между падащо меню и неговия тригер директно в CSS, а браузърът обработва координатите. .trigger { име на котва: --my-тригер; }
.падащо меню { позиция: абсолютна; позиция-котва: --my-тригер; отгоре: котва (отдолу); ляво: котва (ляво); позиция-опит-резервни: flip-block, flip-inline; }
Свойството 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. Достигнете до това, когато поддръжката на вашия браузър го позволява. Ако се изисква поддръжка на Firefox, свържете я с @oddbird polyfill. Това е накъдето платформата в крайна сметка се насочва и в крайна сметка ще се превърне във вашия подход. Преструктурирайте DOM. Използвайте това, когато архитектурата го позволява и искате нулева сложност по време на изпълнение. Вярвам, че вероятно е най-недооценената опция. Комбинирайте шаблони. Направете това, когато искате позициониране на котва като основен подход, съчетан с резервен JavaScript за неподдържани браузъри. Или портал за разположение на DOM, съчетан с getBoundingClientRect() за точност на координатите.
Заключение Отнасях се към този бъг като към еднократен проблем — нещо, от което да закърпя и да продължа. Но след като седях с него достатъчно дълго, за да разбера всичките три включени системи - изрязване на препълване, подреждане на контексти и съдържащи блокове - спря да се чувства случайно. Мога да погледна счупено падащо меню и веднага да проследя кой предшественик е отговорен. Тази промяна в начина, по който чета DOM, беше истинският извод. Няма единствен правилен отговор. Това, към което стигнах, зависеше от това, което можех да контролирам в кодовата база: портали, когато дървото на предците беше непредсказуемо; фиксирано позициониране, когато е чисто и просто; преместване на елемента, когато нищо не ме спираше; и позициониране на котва сега,където мога. Каквото и да изберете в крайна сметка, не третирайте достъпността като последната стъпка. Според моя опит, точно тогава се пропуска. Връзките ARIA, управлението на фокуса, поведението на клавиатурата – това не е изящно. Те са част от това, което кара нещата да работят. Вижте пълния изходен код в моето хранилище на GitHub. Допълнително четене Това са препратките, към които се връщах, докато работех по това:
Контекстът на подреждане (MDN) „Ръководство за позициониране на CSS Anchor“, Хуан Диего Родригес „Първи стъпки с Popover API“, Godstime Aburu Плаващ потребителски интерфейс (floating-ui.com) CSS Overflow (MDN)