Да ли знате онај осећај када креирате сопствену компоненту уместо да побољшате подразумеване ХТМЛ компоненте? Ваш дизајнерски тим је направио нешто прелепо, али прегледачи то неће подржати одмах након што га поправљате, а поправљање свуда постаје ноћна мора. Сви знамо овај бол, али ови изазови чине наш посао занимљивим.
Данас сам желео да причам о једној замци која нас чека током овог узбудљивог путовања: постављању падајућих елемената, као што су изборни менији или бирачи датума.
Апсолутно погрешно
У почетку то изгледа као position: absolute
решава све наше проблеме, и донекле и јесте. Али онда, модални прозори уништавају све.
Ако се падајући мени препуни, он се прекида. Наравно, можете да скролујете надоле и видите, али боље је да се молите да вас дизајнер не може дохватити оштрим предметом.
То је далеко од савршеног – можемо боље.
Поправи са 'fixed'
Ако желимо да прикажемо садржај преко свега, потребна нам је position: fixed
. Једини проблем је што ћемо изгубити координате надређеног елемента: фиксни елементи су прилично независни по природи. То значи да једино што треба да урадимо је да одредимо тачне координате падајућег елемента у овим ситуацијама:
- Када га прикажемо.
- Када се промени његов садржај.
- Када скролујемо прозор и/или родитељ који се може померати.
- Када променимо величину прозора и/или родитеља који се може померати.
Такође морамо да одлучимо да ли да га прикажемо изнад прекидача ако је преблизу дну екрана. Осећа се изводљиво.
Користићу Вуе.јс, али би требало да буде лако пратити чак и ако више волите Реацт или Ангулар.
Лет'с Роцк Тхис Јоинт
Ево структуре коју ћемо користити:
export const useDropdownAttributes = () => { const dropdownWidth = ref(''); const dropdownTop = ref(''); const dropdownBottom = ref(''); const dropdownLeft = ref(''); const isDirectedUpwards = ref(false); const togglerRect = ref<DOMRect>(); const dropdownRect = ref<DOMRect>(); const autodetectPosition = ( isDropdownDisplayed: Ref<boolean>, togglerElement: HTMLElement | null = null, dropdownElement: HTMLDivElement | null = null, dropdownContent: Ref<unknown> | ComputedRef<unknown> = ref([]), isUpwardPreferred = false, ) => { // ... } return { autodetectPosition, dropdownTop, dropdownBottom, dropdownLeft, dropdownWidth, isDirectedUpwards, togglerRect, dropdownRect, }; };
Постоје четири варијабле за падајућу позицију плус заставица isDirectedUpwards
и функција која их све ажурира. Такође враћамо две променљиве за Рецтс преклопника и падајућег менија: ово би могло бити згодно, на пример, за описе алатки које треба да буду поравнате са средином садржаја.
Као што се можда сећате, такође морамо да управљамо померањем и променом величине родитеља који се може померати, па хајде да направимо функцију да га пронађемо:
const getFirstScrollableParent = (element: HTMLElement | null): HTMLElement => { const parentElement = element?.parentElement; if (!parentElement) return document.body; const overflowY = window.getComputedStyle(parentElement).overflowY; if (overflowY === 'scroll' || overflowY === 'auto') return parentElement; return getFirstScrollableParent(parentElement); };
Сада, додајмо главну функцију:
const autodetectPosition = ( isDropdownDisplayed: Ref<boolean>, togglerElement: HTMLElement | null = null, dropdownElement: HTMLElement | null = null, dropdownContent: Ref<unknown> | ComputedRef<unknown> = ref([]), isUpwardPreferred = false, ) => { if (!togglerElement || !dropdownElement) return; const updateDropdownAttributes = () => { togglerRect.value = togglerElement.getBoundingClientRect(); dropdownRect.value = dropdownElement.getBoundingClientRect(); dropdownWidth.value = `${togglerRect.value.width}px`; dropdownBottom.value = `${window.innerHeight - togglerRect.value.top}px`; dropdownTop.value = `${ window.innerHeight - togglerRect.value.bottom - dropdownRect.value.height }px`; dropdownLeft.value = `${togglerRect.value.left}px`; }; const handleResize = () => { requestAnimationFrame(updateDropdownAttributes); }; const handleScroll = () => { requestAnimationFrame(updateDropdownAttributes); }; watch( [isDropdownDisplayed, dropdownContent], ([newVal, _]) => { const scrollableParent = getFirstScrollableParent(togglerElement); if (!newVal) { window.removeEventListener('resize', handleResize); window.removeEventListener('scroll', handleScroll); scrollableParent.removeEventListener('resize', handleResize); scrollableParent.removeEventListener('scroll', handleScroll); return; } requestAnimationFrame(() => { const distanceFromBottom = window.innerHeight - togglerElement.getBoundingClientRect().bottom; const distanceFromTop = togglerElement.getBoundingClientRect().top; const dropdownHeight = dropdownElement.offsetHeight; isDirectedUpwards.value = isUpwardPreferred ? distanceFromTop > dropdownHeight : distanceFromBottom < dropdownHeight && distanceFromTop > dropdownHeight; updateDropdownAttributes(); window.addEventListener('resize', handleResize); window.addEventListener('scroll', handleScroll); scrollableParent.addEventListener('resize', handleResize); scrollableParent.addEventListener('scroll', handleScroll); }); }, { deep: true }, ); };
Проследимо isDropdownDisplayed
и dropdownContent
да бисмо могли да реагујемо на њихова ажурирања.
Такође прослеђујемо togglerElement
и dropdownElement
који нам требају да израчунамо позицију.
Коначно, постоји isUpwardPreferred
у случају да желите подразумевано падајући мени изнад прекидача.
Време је за опуштање и уживање
У вашој компоненти ће вам требати нешто овако (претпостављам да сте додали референце у свој прекидач и падајући мени у шаблону):
const { autodetectPosition, dropdownTop, dropdownBottom, dropdownLeft, dropdownWidth, isDirectedUpwards, } = useDropdownAttributes(); const togglerRef = ref<HTMLElement>(); const dropdownRef = ref<HTMLElement>(); const isDropdownShown = ref(false); onMounted(() => { autodetectPosition(isDropdownShown, togglerRef.value?.$el, dropdownRef.value?.$el); });
А ЦСС ће изгледати овако:
.dropdown { position: fixed; bottom: v-bind('isDirectedUpwards ? dropdownBottom : dropdownTop'); left: v-bind('dropdownLeft'); width: v-bind('dropdownWidth'); min-width: 0; }
Воила. Падајући мени се правилно приказује чак и када је препун и помера се изнад прекидача ако испод нема довољно простора.
А пошто смо на крају чланка, волео бих да вам оставим нешто весело — али немам идеја. Дакле, бојим се да је "срећно" све што имам овог пута. Срећно. 👋
Комплетан код можете пронаћи на ГитХуб-у .