Czy znasz to uczucie, gdy tworzysz własny komponent zamiast ulepszać domyślne komponenty HTML? Twój zespół projektowy stworzył coś pięknego, ale przeglądarki nie będą tego obsługiwać od razu, a naprawa wszędzie staje się koszmarem. Wszyscy znamy ten ból, ale te wyzwania sprawiają, że nasza praca jest interesująca. Dziś chciałbym omówić jedną pułapkę, jaka na nas czyha podczas tej ekscytującej podróży: rozmieszczenie elementów rozwijanych, takich jak menu wyboru czy selektor daty. Całkowicie źle Na pierwszy rzut oka wygląda na to, że rozwiązuje wszystkie nasze problemy i w pewnym stopniu tak jest. Ale potem okna modalne wszystko psują. position: absolute Jeśli rozwijana lista się przepełni, zostanie ona odcięta. Jasne, możesz przewinąć w dół i zobaczyć ją, ale lepiej módl się, aby projektant nie mógł dosięgnąć cię ostrym przedmiotem. Daleko nam do ideału — możemy zrobić to lepiej. Napraw to za pomocą 'fixed' Jeśli chcemy wyświetlić zawartość na wszystkim, potrzebujemy . Jedynym problemem jest to, że stracimy współrzędne elementu nadrzędnego: elementy fixed są z natury dość niezależne. Oznacza to, że jedyne, co musimy zrobić, to określić dokładne współrzędne elementu rozwijanego w następujących sytuacjach: position: fixed Kiedy to wyświetlamy. Gdy jego zawartość ulegnie zmianie. Kiedy przewijamy okno i/lub przewijalny element nadrzędny. Gdy zmieniamy rozmiar okna i/lub przewijalnego elementu nadrzędnego. Musimy również zdecydować, czy wyświetlić go nad przełącznikiem, jeśli jest zbyt blisko dołu ekranu. Wydaje się to wykonalne. Użyję Vue.js, ale powinno być ono łatwe do zrozumienia nawet jeśli wolisz Reacta lub Angulara. Rozbujajmy to połączenie Oto struktura, którą wykorzystamy: 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, }; }; Istnieją cztery zmienne dla pozycji rozwijanej, a także flaga i funkcja, która je wszystkie aktualizuje. Zwracamy również dwie zmienne dla prostokątów przełącznika i rozwijanej listy: może to być wygodne, na przykład dla podpowiedzi, które muszą być wyrównane do środka zawartości. isDirectedUpwards Jak zapewne pamiętasz, musimy także obsłużyć przewijanie i zmianę rozmiaru przewijalnego elementu nadrzędnego, więc utwórzmy funkcję, która je znajdzie: 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); }; Teraz dodajmy funkcję główną: 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 }, ); }; Przekazujemy i , abyśmy mogli reagować na ich aktualizacje. isDropdownDisplayed dropdownContent Przekazujemy również i , których potrzebujemy do obliczenia pozycji. togglerElement dropdownElement Na koniec mamy opcję , jeśli chcesz, aby lista rozwijana domyślnie znajdowała się nad przełącznikiem. isUpwardPreferred Czas na relaks i przyjemność W swoim komponencie będziesz potrzebować czegoś takiego (zakładam, że dodałeś odwołania do przełącznika i listy rozwijanej w szablonie): 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); }); A kod CSS będzie wyglądał tak: .dropdown { position: fixed; bottom: v-bind('isDirectedUpwards ? dropdownBottom : dropdownTop'); left: v-bind('dropdownLeft'); width: v-bind('dropdownWidth'); min-width: 0; } Voilà. Lista rozwijana wyświetla się poprawnie, nawet gdy jest przepełniona i przesuwa się nad przełącznikiem, jeśli nie ma wystarczająco dużo miejsca poniżej. A skoro jesteśmy już przy końcu artykułu, chciałabym zostawić Was z czymś radosnym — ale nie mam już pomysłów. Obawiam się, że tym razem mogę powiedzieć tylko „powodzenia”. Powodzenia. 👋 Pełny kod znajdziesz na . GitHub