Замена бібліятэкаў падказак на API Popover і пазыцыяванне анкера ў CSS
Чаму патрэбны інструментальныя панелі: Popper і Floating UI, якія ўжо є вбудованымі функцыямі для рашырання, пазначэння положэння і закрыцьця, а таксама калі ўжо мае сэнс викорыстоўваць бібліятэку JavaScript.
Адказанне тексту празле кнапкі выглядае простаю задачай, але існуюць Popper.js, Floating UI і множыця пакетаў-обгорткаў, таму што раней гэта не было можлівым. Падказка на самай працэ ўсьмо тры адна з другой незалежныя проблемы, якія нарадзіліся ўнаследак адсутнасці належных рэшэнняў, і да нядаўна платформа не мела чыткага спосабу адпаведзець ні на адну з іх. У гэтым кялеце проблемы розбіваюцца на часткі, паказваецца, сколькі месца займае кожная з іх у готовым JavaScript-кодзе, а таксама паказваюцца два функцыяналу браузера, якія зараз рашаюць стандартныя кейсы: Popover API і CSS Anchor Positioning. У канцы вы зразумеете, якія часткі бібліятэкі падказак можна выдаліць, а якія ўсё ж можа знадобіцца.
Тры проблемы, якія хаваюцца ўнутрь адной падказкі
Кожная з іх ёсць давняя, добра вядомая абмежэнасць у працэ браузера да выявлення новых стандатаў:
- Размешчэнне. Чы будзе падказка апісвацца вышэй за ўсё інше, чы яе будзе адрэжваць параметр
overflow: hiddenякога-небудзь предка? - Пазыціяванне. Чы падказка ведае, дзе на экране знаходзіцца элемент, які ўвёлі ёй працю, і чы яна следуе за гэтай пазыціяй пад час прасування та змены размеру?
- Закрыцце. Чы яна сама закрываецца, калі корыстнік нажымае на іншы элемент чы клавішу Escape?
Працуючы большую частку історіі Інтернету, кожны проект, якому былі патрэбны падказкі, выпадаючыя спискі чы список автодапамогі, рашыла ўсе тры проблемы за дапамогою JavaScript. Способы рашэння разныя для кожной з іх, і самэ гэта — прычына, чаму незначны деталек інтэрфейса ператворыўся на залежнасць. Таму карэткі ўважна рассмотрыць іх па аднам.
Размешчэнне: чаму з-індекс не можа выйсці за межы свага контэксту
Кожны элемент належыць да абстрактнага контэкста складвання, який вяршынюе, што будзе намальована ўсередзіне яго. z-index аранжуе элементы ў межах аднаго контэкста складвання; ён не можа вывесіць элемент за межы контэксту, да якога ён належыць.
Разместіце падказку ў контейнеры з атрыбутам overflow: hidden або ў модальныя вікна, якіе ствараюць сабе ўласны абстрактны контэкст складвання, і жадна значэнне, нават 999999, не дазволіць яй праказацца вышэй за гэтыя межы. Падказка обмежваецца правіламі атрыбутаў яе предкав, і z-index не мае ўплыву за межы гэтых правілаў.
Традыцыйны спосаб выхаду быў порталам: маркап тэйпа-інструкцыі трэба было размесціваць у іншай часткі дакумента, зазвычай пасля <body>, ўсёле гэта не даўало яму спадзявацца на параметры кліпання і распакоўкі ад бацькі елемента. Функцыя React createPortal існуе галоўная меркай самэй прычыны. Гэта скорей спосаб адраджэння, чым функцыя React, для рашэння проблемы, якую CSS не могла вырашыць.
Размешчэнне: абсалютнае размешчэнне розумее толькі абяцелей
position: absolute размешчае элемент адносна яго найбліжэйшага абяцеленага элемента, тыям самым — найбліжэйшага элемента ў дрэве, якога position ёсць relative, absolute, fixed або sticky. Ключовым словам ёсць «абяцела»: элемент-рэферэнс павінен знаходзіцца ў той жа галузі DOM, дзе-небудзь вышэй за тэйп-інструкцыю.
Калі падказка і елемент, які ўзмоцавляюць яе аплыв, стаюць братамі-сестрамі, або калі падказку переносяць у <body> для адзначэння проблемы наслаўвання, элемент-узмоцавальнік больш не ўтварае абяцаў. CSS не мела можлівасці сказаць "разместіце гэта ў адносе да таго некаляжнага элемента там". Слабасцю была не пазіцыяванне ў CSS загалом, а відсутнасць якога-небудзь спосабу пазіцыявання, які бы ігнараваў структуру докумэнта.
Бібліятекі заполнялі гэты прыём праз вимераванне. Яны вызываюць getBoundingClientRect(), каб атрыбуаваць элемент-узмоцавальнік рамку, адносную да вікна прэвью, вырахоўваюць координаты падказкі і перарабоўваюць гэтыы расчытак праз кожны прасуванне і змену размеру, таму што цыфры постаўляюцься змяніцца. Гэтый непаўзлівы цикл вимеравання і размешчэння ёсць галоўным функцыяналам бібліятэк пазіцыявання пад час выконання.
Закрыцце: прыменэ, якога нельга было апісаць за дапамогою маркапаў
Даўнае раней чым з’явілася API для падказак, HTML і CSS не мелі можнасці «закрыць падказку, калі корыстнік нажме за межамі яе або клавішу Escape». Усё гэта рэалізавалася за дапамою скрыптавання: аслухальнік на клік у document, який пераканальваўся, чы не знаходзіцца цэль змагання за межамі падказкі, аслухальнік keydown, який чакаў на клавішу Escape, а таксама код для чысткі пасля таго, калі компонент знімалі, ўбліжчаючы тады, каб не было витоку ресурсоў. На адзінку з першымі двумя проблемамі, гэтае пытанне не мае геаметрычнага аспекту. Цэлая гэта проблема стосуецца толькі поведзення, але ён застаўся трэцім элементам коду часу выканання, якога браузер не з’явіў.
Падказкі проты модалых вікнаў
Дапамагае прызначэнне тэрмінаў перад адглядам сынтаксу. Popover — гэта ўсё, што адображаецца вышэй за рэшту стораніцы, размешчаецца адносна кантролера і зникае пасля кліка зза меж або нажатня клавішы Escape. Падказкі, меню з выпадаючымі элементамі, спісы автодаполнення і контекстныя меню — усе гэта popovers з разным дизайном, але з тымі ж трымая проблемамі.
Modal таксама сталкнуецца з проблемай нараджэнняя слоёў, але не ёсць popover, таму што блакуе доступ да іншых элементаў. Калі modal ачыны, контэнт за ям ледзь можа быць вядомы: корыстувальнікі не можаць перейсці на яго, клікнуць або праскроліць яго, і зазвычай є фонавая плята, якая залишаецца актыўной даколі modal не будзе закрыты. Падумайце пра запит «Пאўтарыць выдаленне»: нічога іншага на стораніцы не можна викорыстоўваць даколі на яго не будзе дана адпаведнасць. Popover нічога не блакуе; стораніца застаецца цалкам інтэрактыўной, а popover проста закрываецца, калі корыстувальнік пераходзіць да іншага.
Спефікацыі прыметна паказваюць гэтае разлікаванне:
popover="auto"дазваляе лёгкая адчыненне і закрыцце без блакавання: ён закрываецца праз клік за межы або нажатые клавішу Escape і не блакае фокус. У паўзы наступаюць інфармацыйныя панелі та выпадаючыя меню.popover="manual"застаецца адчытым пакуль ваш скрыпт яго не закрое, без лёгкага адчыненне, што падходзіць для стацыонарных упавядаючых паведамленняў.<dialog>, адчыненый за дапамой.showModal(), ўжо ёсць варіант з блакаванням: ён выступае на верхнім слое, мае фон, зберагае фокус і робіць усё, што знаходзіцца за ўпорам, недзейным.- Адчыненне таго ж
<dialog>за дапамой.show()дае элемент без блакавання, які працуе падобна до popover.
Якія є наследкі викорыстання JavaScript
Наведзеныя выкручаныя розмеры былі взяты з npm і апавярнююць толькі самыя бібліятэкі:
popper.js (v1, now deprecated) 7.1 KB
Tippy.js (bundles @popperjs/core) 14.1 KB
Floating UI, vanilla (@floating-ui/dom) 8.1 KB
Floating UI, React bindings 30.1 KB
react-tooltip (@floating-ui/dom + clsx) 14.1 KB
Этыя цыфры не включаюць нічога, што вы дадзеце дапамою: настройкі, компонент-обгорак, CSS для стрэлак і тэм. Гэта базовая вартасць даўнае раней, чым працюе ваша сябе логіка, а проект, які спалюе, скажамо, пакет падказак і аддзінальны пакет выпадаючых меню, плаціць яе два разы.
Нічога з гэтага не адбіваецца да паслабленай інжынерыі. Узоркаваная UI ў частыцы настолькі рэтельна створаная; яе галоўная задача — правільна працаваць з переплывам у всіх браузерах, незалежна ад іх особлівасцяў. Выкладкі выйшлі з таго, што патрэбна была адразу рашыць тры незв’язаныя проблэмы ў JavaScript, калі платформа не запрошвала нічога іншага.
API Popover карыцца стакаваннем і закрыццем
Две аддзінальныя спэцыфікацыі заменілі бібліятэку, і яны не дзеляць работу так, як можна было б спадзявацца. Атрыбут popover адразу карыцца стакаваннем і закрыццем, практычна без жадных скрыптав.
<button popovertarget="my-tooltip">Hover me</button>
<div id="my-tooltip" popover="auto">
This is the tooltip content.
</div>
Атрыбут popovertarget споўяе кантакт між кнопкай і элементам з адпаведным id. Калі выкарыстоўваецца popover="auto", элемент пераводзіцца на верхній шар браузера пад час адкрывання паўтарнага паведамлення, што якраз ёсць выхад з режыму overflow: hidden і контэкстаў складання, описаных раней; кроме таго, такі элемент можа легка быць закрыты без додатковага коду: клікі зза меж і нажатыя клавішы Escape закрываюць яго без неабходнасці настаўлення адпаведных слухачаў.
Адзін парадакс у формулаванні маркапа: popovertarget актывацыяееся праз адкрыцье — тэбто праз клік, натысканне або нажатые на клавіатуру, а не праз перамешчанне курсора над элементам. Справжняя падказка, якая актывацыяеся праз перамешчанне курсора, все ж такі трэбуе некалькіх рэгулярных выразаў, каб вызваць showPopover() і hidePopover() праз змяны стану курсора і фокуса, або ж новейшаг декларатыўнага механізма, калі вашы браузеры яго падтрымаюць. Нават у такім случыку для стакавання і закрыцьця падказкі больш не трэбуе бібліятэк. Жыцзёбнае распаложэнне падказкі застаецца адным з нерашаных пытанняў, і яно належыць да іншай спефікацыі.
Распаложэнне элементаў праз анкеры
CSS Anchor Positioning рашае тую адну проблему, якую API Popover застаўляе без увагі. Ён дазволяе будзь-колькім двум элементам у докуменце асоўвацца адны з другім праз імена, а не через структуру «родык-дзеці».
.trigger {
anchor-name: --my-anchor;
}
.tooltip {
position: absolute;
position-anchor: --my-anchor;
top: anchor(--my-anchor bottom);
left: anchor(--my-anchor left);
}
anchor-name рэгіструе цей трыгар пад знакаментаваным ідэнтыфікатаром, той самы синтаксіс, які выкарыстоўваюць спецыяльныя атрыбуты. position-anchor у падказцы паказывае на гэты ідэнтыфікатар, а функцыя anchor() чытае конкрэтную грань анкара (top, right, bottom, left або center) каб падказка могла выраўнявацца па ёй.
Важна зразумець, што ніякі з элементаў не павінен мець другі ў сабе. Браузер тепер натыўна выконвае тое, што раней getBoundingClientRect() павінен быў выраховваць вручную на кожным кроцы прасування. Калі вы союзаваеце гэта з падказкай, якая апускаецца, памятайце, што стаяльнік стылю для прыемніка корыстувача задае элементам [popover] значэння inset: 0 і margin: auto, каб ўсё было выраўнавана; якщо падказка ігнаруе вашы зсувы анкара, перазначэнне гэтых атрыбутаў зазвычай ёсць рашэнням.
Актывацыя функцыі перапраўлення без адчувальніка прасування
Частка бібліятэкі пазыцыявання, якая мае большую частку логікі, — это карыстоўванне функцыяй для рэшэння проблемы перапраўлення: выявленне таго, што падказка зачыніцца за межы вікна прэд'явлення, і выбіранне іншага месца размешчэння. Функцыя пазыцыявання за анкерам рэшае гэту проблему за дапамою position-try-fallbacks.
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center;
position-try-fallbacks: flip-block, flip-inline;
}
Тут position-area: top center задае стандартнае месца размешчэння, а position-try-fallbacks пераказвае альтернатывы, якія браузер пробуюе ў пэўнай спраўе, калі гэта месца размешчэння можа спрычыніць перапраўлення падказкі за межы ўсёй блок-структуры чы вікна прэд'явлення. flip-block відбивае падказку па осі блока, так што верх становіцца ніжом, а flip-inline — па осі інлайн-элементаў, так што левы канец становіцца правым. Браузер пераацэнюе гэта пад час формавання лейауту, без адчувальніка прасування і без скрыпты на галоўнай вясі, якая б выявляла проблему перапраўлення.
Калі простага дзеркала недасты, правило @position-try дазваляе задаць іменаваныя альтернатыўныя месца размешчэння — кожна з яых ёсць невялікі блакат з декларацыямі пазычэння, якія браузер можа пераходзіць па спрабах.
@position-try --below {
position-area: bottom center;
margin-top: 8px;
}
@position-try --above {
position-area: top center;
margin-bottom: 8px;
}
.tooltip {
position-anchor: --my-anchor;
position-try-fallbacks: --above, --below;
}
Гэта тая ж самая прымета, яку выконвае Floating UI ў JavaScript пад час кожнага зместу, пры якой даныя заздалегідь задаюцца як інформацыя, яку аналізуе механізм макетавання. Заўважыце, што правіло .tooltip у гэтым фрагменте прыпускае, што элемент вялікі час яўляецца абсалютна адназначаным або фіксаваным, як у пакульшых прыкладах; пазычэнне за анкерам не вплывае на элементы, якія знаходзяцца у статычным положэнні.
Калі бібліятэка пазычэння все ўсё прыемна
Для падказкі, простага выпадаючага меню або списка з автодапамогай натыўнае поўнаважэнне ёсць разумным стандартам. Ролі бібліятэкі скасалася, хоць яна не знікла.
Падкоснае падтрыманне прыстрою ўжо з самага пачатку являецца аднай з галоўных прыганочак. Браузеры Chromium падтрымліваюць пазычэнне элемента anchor з версіі 125, а @position-try пачаў выкорыстоўваны ў стандарце Baseline пазней, чым сам anchor(). Падтрымка ў браузерах Safari і Firefox з’явілася пазней, а цифры, якія паводзяцца ў Інтернете, разлічаюцся, таму кращэ адразу пераканацца ў актуальной табелі сумяшчэння, чым пакладацца на які-небудзь адзіны список версій. Калі падтрымка відсутня, не існуе гракціязнае рашэння, якэ бы выкарыстоўвалася толькі CSS: браузер, які не розумее anchor(), проста не можа правільна пазычыць элемент. Якщо вам неабходна падтрымка старэйшых версій Safari або мобільных браузераў на старэйшых двойчыках, трэба маты запасны варіант; наша інструкцыя па безпечной рэалізацыі сучаснага CSS раскрывае методы выявлення функцый і поступовага пасілкання для элементаў anchor.
Другі варыянты — цэлкам іншыя правіла размешчання, якія выходзяць за межы простага відбівання пад переплненне: плаваючая панель, якая містіць віртуалізаваный спіс, перакрыцця, якія пераглядаюцца адночасна з калькуляцыямі па колькіх межах, або размешчання, якое керуецца даннымі прыемплэва, а не його структурой. Скрыпты можаюць реагаваць на будзь-які стан прыемплэва, тады калі фіксаваны спіс для альтернатыўных сцэнарыяў у CSS ведае толькі пра структуру.
Для звычнага варыянта, який стосуецца большасці проектаў, вельмі складна практычна абясніць неабходнасць викорыстоўвання 8-30 КБ JavaScript для задач, якія браузер можа выконваць сам.
Ключовыя выводы
- Інструмент падказак мае тры проблемы: структураяванне, пазіцыонаванне і закрыцце. Бібліятэкі існавалі таму, што всі тры прыбліжэнняя трэба было рашаць за дапамою скрыптов.
popover="manual" і <dialog> з методам .showModal() адпаведаюць для випадкаў стойкага прадстаўлення і блакання актуальнай інтэрфейс-системы.position-try-fallbacks і @position-try заменяюць логіку перекрыцча элементаў, якая ранейшыя бібліятэкі выкарыстоўвалі.