24. Menu rozwijane w CSS

> Dodaj do ulubionych

W poprzednim rozdziale wykonaliśmy stylizację strony głównej naszej witryny, dzięki czemu wygląda ona już atrakcyjnie i jest w pełni funkcjonalna oraz będziemy mogli ją wykorzystać jako podstawę do utworzenia szablonu podstron. Tym jednak zajmiemy się w następnym rozdziale.

Teraz natomiast ulepszymy nawigację główną przez dodanie do niej funkcjonalności menu rozwijanego, jakie można spotkać na prawie każdej stronie internetowej. Działanie tego menu polega na tym, że kiedy najedziemy kursorem myszy na wybrany element, pojawia się lista dodatkowych opcji do wyboru. Spójrz na poniższy zrzut ekranu:

Finalna wersja menu rozwijane w CSS

Na naszej stronie każdy element menu głównego, oprócz odnośnika do strony głównej, będzie miał menu rozwijane prowadzące do poszczególnych części danej strony. To oczywiście tylko demonstracja techniki. W prawdziwej witrynie menu rozwijane zazwyczaj zawiera linki do podkategorii witryny.

Menu rozwijane na stronie internetowej można utworzyć na dwa sposoby – przy użyciu JavaScriptu lub przy użyciu samego kodu CSS. Technika z użyciem CSS jest bardzo prosta i efektywna, więc wybierzemy właśnie to rozwiązanie.

Aby utworzyć menu rozwijane w CSS wystarczy znać tylko kilku selektorów i własności tej technologii. Znasz już prawie wszystkie z nich. Do pełni szczęścia brakuje Ci jeszcze tylko znajomości technik pozycjonowania elementów za pomocą własności position. Nie są one skomplikowane, więc pora uzupełnić ten brak w wiedzy.

Pozycjonowanie elementów w CSS

Kiedy przeglądarka wczytuje naszą obecną stronę internetową, na której nie ma jeszcze żadnych pozycjonowanych elementów, to umieszcza wszystkie elementy w tzw. układzie normalnym (ang. normal flow).

Układ normalny elementów na stronie internetowej to po prostu ich rozmieszczenie zgodnie z ogólnymi zasadami rządzącymi elementami blokowymi i śródliniowymi. Umiejscowienie każdego elementu w odniesieniu do sąsiednich elementów jest określane na podstawie jego położenia w kodzie źródłowym.

Kiedy natomiast element jest pozycjonowany, to jego położenie jest wyznaczane w inny sposób, zależny od wybranej techniki.

Do pozycjonowania elementów w CSS służy własność position, która przyjmuje pięć wartości: absolute, fixed, relative, static i sticky.

Ustawienia te najłatwiej jest zrozumieć na podstawie konkretnych przykładów. Poniżej więc znajduje się podstawowy kod CSS i HTML, na którym oprzemy dalsze objaśnienia:

Kod CSS:

body {padding: 0; margin: 0}
p {margin: 20px; width: 300px;}
.a {
  background-color: gray;
}
.b {
  background-color: lightpink;
}
.c {
  background-color: lightgreen;
}

Kod HTML:

<p class="a">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer iaculis sit amet felis ac tincidunt. Vivamus sit amet dolor vulputate, pretium sem vel, imperdiet neque. Morbi semper odio in nisi.</p>
<p class="b">Cras lacinia diam tempus lacinia auctor. In lobortis dui a varius fermentum. Sed cursus commodo risus, non bibendum ligula dictum vitae. In auctor sagittis orci molestie vehicula. Sed egestas.</p>
<p class="c">Integer ligula lectus, sodales ut faucibus nec, varius sed dolor. Mauris egestas vitae eros nec ultricies. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>

Pozycjonowanie statyczne, włączane za pomocą wartości static, potrzebnej tylko, jeśli wcześniej zdefiniowano inną i teraz chcemy ją przywrócić, to domyślny sposób pozycjonowania elementów w układzie normalnym, które już znasz. W związku z tym bez trudu przewidzisz, że akapity zostaną po prostu wyświetlone w oknie przeglądarki jeden pod drugim w takiej kolejności, w jakiej znajdują się w kodzie źródłowym:

Ilustracja pozycjonowania statycznego

Pozycjonowanie absolutne lub bezwzględne

Pozycjonowanie absolutne lub bezwzględne, włączane za pomocą wartości absolute, powoduje „wyjęcie” elementu z normalnego układu i ustalenie jego pozycji względem najbliższego mu nadrzędnego elementu pozycjonowanego, a jeśli takiego nie ma, to względem elementu body. Dla uproszczenia u nas na razie jest tylko element nadrzędny body, więc pozycja będzie ustalana względem niego.

Kiedy pozycjonujemy element absolutnie, to przestaje on „oddziaływać” z innymi elementami na stronie, tzn. jego położenie nie wpływa na położenie innych elementów. Można powiedzieć, że „unosi się” on nad nimi, nie mając z nimi kontaktu i je zasłaniając.

Sama deklaracja position: absolute powoduje tylko zmianę trybu wyznaczania położenia elementu. Aby jednak rzeczywiście go przesunąć, potrzebne są dodatkowe własności.

Do określania położenia wszystkich elementów pozycjonowanych służą cztery własności: top, right, bottom i left. Działają one tylko wtedy, gdy element ma zdefiniowaną własność position o innej wartości niż static.

Własność top określa odległość od górnej krawędzi kontenera, względem którego jest pozycjonowany element. Własności right, bottom i left robią to samo w odniesieniu do odpowiednio prawej, dolnej i lewej krawędzi kontenera.

Powiedzmy na przykład, że chcemy pozycjonować absolutnie akapit z klasą c w następujący sposób:

.c {
  background-color: lightgreen;
  position: absolute;
  top: 40px;
  left: 100px;
}

Teraz akapit c zostanie wyjęty z normalnego układu elementów na stronie i jego położenie zostanie określone w odniesieniu do krawędzi elementu body (ponieważ jest to jego jedyny element nadrzędny) zgodnie z ustawieniami własności top i left:

Ilustracja pozycjonowania absolutnego

Teraz interesujący nas element nie oddziałuje w żaden sposób z pozostałymi elementami, a jego położenie zostało ustalone w taki sposób, że jego lewy górny róg znajduje się w odległości 40 pikseli od górnej krawędzi elementu body i 100 pikseli od lewej krawędzi elementu body. Dodatkowo w grę wchodzą marginesy, więc do tych wartości trzeba dodać jeszcze po 20 pikseli.

Teraz wyjaśnimy sobie dokładnie, co znaczy „ustalanie pozycji względem najbliższego elementu pozycjonowanego”. Powiedzmy, że mamy element span o treści „To jest element span”, który znajduje się za akapitami:

<span class="d">To jest element span</span>

Jeśli ten element wypozycjonujemy bezwzględnie, to jego położenie zostanie wyznaczone w odniesieniu do elementu body:

.d {
  position: absolute;
  background-color: lightblue;
  top: 30px;
  left: 40px;
}
Span pierwszy

Zgodnie z oczekiwaniami położenie elementu span zostało ustalone w odniesieniu do elementu body, ponieważ nie ma on żadnego nadrzędnego elementu pozycjonowanego.

A teraz umieścimy ten element span w akapicie b:

<p class="c">Integer ligula lectus, sodales ut faucibus nec, varius sed dolor. Mauris egestas vitae eros nec ultricies. <span class="d">To jest element span</span> Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>

Efekt tej zmiany będzie następujący:

Span wewnątrz akapitu c

Teraz położenie elementu span jest wyznaczane względem lewego górnego rogu elementu c, ponieważ element ten jest najbliższym pozycjonowanym przodkiem elementu span.

Możesz pomyśleć, że położenie elementu span się zmieniło, ponieważ zmieniliśmy jego umiejscowienie w kodzie źródłowym. Poniższy zrzut ekranu pokazuje umiejscowienie elementu span po usunięciu własności position: absolute z reguły CSS akapitu c.

Span poza akapitem c

Umiejscowienie elementu span w kodzie źródłowym się nie zmieniło, a mimo to nadal znajduje się on w tym samym położeniu na stronie, co wtedy, gdy w kodzie źródłowym znajdował się pod akapitami.

Jako że elementy pozycjonowane można swobodnie przemieszczać po stronie, powstaje problem ich nakładania się na siebie, tzn. możemy spowodować, że jeden element będzie częściowo lub całkowicie zakrywał inny.

Jeśli w żaden sposób tego nie zmienimy, to przeglądarka stosuje kolejność zgodną z kolejnością występowania elementów w kodzie źródłowym, tzn. „na wierzchu” jest ten, który został wpisany ostatni.

Za pomocą własności CSS z-index można zmienić kolejność elementów na stosie (ang. stacking context). Jako wartość przyjmuje ona ujemną lub dodatnią liczbę całkowitą. Im wyższa wartość z-index, tym wyższa pozycja na stosie, czyli tym bliżej wierzchu.

Weźmy na przykład poniższy znany nam już kod HTML:

<p class="c">Integer ligula lectus, sodales ut faucibus nec, varius sed dolor. Mauris egestas vitae eros nec ultricies. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
<span class="d">To jest element span</span>

Jeśli żadnemu z tych elementów nie zdefiniujemy własności z-index oraz wypozycjonujemy je tak, że ich położenia będą się ze sobą pokrywać, to element span będzie częściowo zakrywał akapit, ponieważ w kodzie źródłowym HTML jest zdefiniowany później:

Span zakrywający akapit

Jeśli jednak akapitowi przypiszemy wyższą wartość własności z-index niż elementowi span, to na wierzchu będzie znajdował się ten pierwszy. Spójrz na poniższy kod CSS:

.c {
  background-color: lightgreen;
  position: absolute;
  top: 40px;
  left: 100px;
  z-index: 2;
}
.d {
  position: absolute;
  background-color: lightblue;
  top: 60px;
  left: 40px;
  z-index: 1;
}

Przy takich ustawieniach osiągniemy następujący efekt:

Span pod akapitem

Pozycjonowanie relatywne lub względne

Pozycjonowanie względne, włączane za pomocą wartości relative, pozwala określić położenie elementu względem jego położenia, jakie przyjąłby w układzie normalnym strony. Inaczej mówiąc, najpierw przeglądarka wstawia dany element w miejscu, w którym znajdowałby się, gdyby nie był w ogóle pozycjonowany, a następnie przesuwa go zgodnie z ustawieniami własności top, right, bottom i left.

Przesunięcie względne elementu nie wpływa na otaczające go elementy, przez co pozostaje po nim „dziura” na stronie. Przyjrzymy się temu na przykładzie. Powiedzmy, że mamy na stronie taki akapit:

<p class="b">Cras lacinia diam tempus lacinia auctor. In lobortis dui a varius fermentum. Sed cursus commodo risus, non bibendum ligula dictum vitae. <span class="d">To jest element span"/span" In auctor sagittis orci molestie vehicula. Sed egestas.</p>

Tym dwóm elementom odpowiadają następujące reguły CSS:

.b {
  background-color: lightpink;
}
.d {
  background-color: lightblue;
}

Na razie oba te elementy mają tylko określone kolory tła (oraz szerokość i margines we wcześniejszej regule). Nie pozycjonujemy ich w żaden sposób, więc w oknie przeglądarki wyglądają tak:

Ilustracja pozycjonowania względnego bez pozycjonowania

Teraz zastosujemy pozycjonowanie względne do elementu span i zobaczymy, co się stanie. Zamień powyższe reguły CSS na poniższe:

.b {
  background-color: lightpink;
}
.d {
  background-color: lightblue;
  position: relative;
  top: 40px;
  left: 80px;
}

Teraz nasze elementy prezentują się następująco:

Pozycjonowanie względne - przesunięcie span

Element span został przesunięty względem swojego statycznego położenia o liczbę pikseli określoną przez własności top i left. Zwróć uwagę na puste miejsce pozostałe tam, gdzie powinien się normalnie znajdować oraz na to, że jego element nadrzędny nie jest pozycjonowany – w przypadku pozycjonowania względnego nie jest to wymogiem.

W ramach szybkiego ćwiczenia sprawdź, co się stanie, kiedy zmienisz pozycjonowanie względne elementu span na bezwzględne oraz kiedy przypiszesz własność pozycjonowania zawierającemu go akapitowi.

Pozostałe dwa typy pozycjonowania to pozycjonowanie „kleiste” (wartość sticky) i na stałe (wartość fixed).

Pozycjonowanie kleiste i na stałe

Elementy pozycjonowane na stałe (za pomocą wartości fixed) mają położenie określane w podobny sposób, jak elementy pozycjonowane bezwzględnie, ale dodatkowo pozostają w danym miejscu bez względu na przewijanie treści.

Natomiast element kleisty (pozycjonowany przy użyciu wartości sticky) zachowuje się częściowo jak element pozycjonowany względnie, a częściowo jak element pozycjonowany na stałe.

Najpierw przeglądarka określa jego położenie w taki sam sposób, jak położenie elementu pozycjonowanego względnie, a następnie zatrzymuje go w miejscu określonym przez własności top, right, bottom, left podczas przewijania treści.

W poniższym okienku znajduje się przykładowy kod pozwalający wypróbować te dwa rodzaje pozycjonowania. Najpierw przetestuj pozycjonowanie na stałe, a następnie zmień wartość fixed w regule dotyczącej akapitu na sticky i sprawdź, jak zmieni się zachowanie elementów w okienku po prawej stronie.

See the Pen Pozycjonowanie sticky i fixed by Łukasz Piwko (@shebangpl) on CodePen.

Budowa menu rozwijanego w CSS

Znając techniki pozycjonowania elementów CSS, możemy przejść do tworzenia menu rozwijanego. Najpierw utworzymy sam komponent, który będzie pojawiał się po najechaniu kursorem na element menu, a potem dodamy selektor odpowiadający za interaktywność.

Przypomnijmy, jak obecnie wygląda struktura HTML naszego menu głównego:

<nav>
  <ul>
    <li><a href="index.html">Główna</a></li>
    <li><a href="erdos.html">Paul Erdős</a></li>
    <li><a href="feynman.html">Richard Feynman</a></li>
    <li><a href="why-the-lucky-stiff.html">why the lucky stiff</a></li>
  </ul>
</nav>

Komponenty pojawiające się w wyniku rozwinięcia menu umieścimy w elementach listy za odnośnikami. Zdefiniujemy je w postaci elementu div, który jest ogólnym kontenerem blokowym bez żadnego semantycznego znaczenia – czymś w rodzaju elementu span, tylko w wersji blokowej. Używa się go zawsze wtedy, gdy potrzebny jest element blokowy, ale nie pasuje żaden element semantyczny.

W ramach przykładu dodamy funkcję rozwijania do pozycji menu Paul Erdős. Odnośniki w tym podmenu będą prowadziły do wybranych sekcji na stronie o Erdősie.

<li>
  <a href="erdos.html">Paul Erdős</a>
  <div>
    <a href="#">Studia</a>
    <a href="#">Osiągnięcia naukowe</a>
    <a href="#">Ciekawostki</a>
    <a href="#">Projekt Manhattan</a>
    <a href="#">Liczba Erdősa</a>
  </div>
</li>

Wysuwany komponent w naszym menu będzie składał się z elementu div zawierającego elementy a reprezentujące linki. Jak widać struktura HTML tego komponentu jest bardzo prosta. Teraz pora dodać odpowiednie reguły CSS. Zaczniemy od kosmetyki:

#main-header nav li div {
  min-width: 190px;
  text-align: left;
  border: 1px solid #dddddd;
  background-color: #ffffff;
  padding: 15px 0;
  font-size: 90%;
}

Za pomocą tej reguły określiliśmy, że element div znajdujący się w elemencie li znajdującym się w elemencie nav, który znajduje się w głównym nagłówku (taki długi selektor pozwala nam odnieść się do wybranego elementu bez stosowania klas, dzięki czemu kod HTML jest bardziej przejrzysty) ma mieć minimalną szerokość 190 pikseli, tekst wyrównany do lewej, jednolite szare obramowanie o grubości jednego piksela, biały kolor tła, dopełnienie górne i dolne o szerokości 15 pikseli oraz rozmiar pisma wynoszący 90% rozmiaru pisma elementu nadrzędnego. Nie opisuję już szczegółowo tych ustawień, ponieważ nie ma tu dla nas niczego nowego.

W następnej kolejności zajmiemy się formatowaniem odnośników, które jak wiadomo są elementami śródliniowymi, więc domyślnie nie będą ustawiać się jeden nad drugim, tylko w jednej linii. W związku z tym dodajemy następujące reguły CSS:

#main-header nav li div a {
  display: block;
  margin: 0 15px 12px 20px;
}
#main-header nav li div a:last-child {
  margin-bottom: 0;
}

Podobnie jak poprzednio stosujemy dość długie selektory, które pozwalają nam jednoznacznie odnieść się do odpowiednich elementów bez stosowania klas.

Najpierw zamieniamy łącza w elementy blokowe i nadajemy im ustawienia marginesów, aby ładnie wyglądały.

Następnie, w drugiej regule, likwidujemy dolny margines ostatniego elementu a w naszym komponencie, aby na dole nie było za dużo pustego miejsca (działa tam już dopełnienie dolne ustawione dla elementu div, które bez tej reguły zsumowałoby się z marginesem).

To wszystko jeśli chodzi o kwestię wyglądu. Teraz nasz komponent podmenu wygląda tak:

Pierwsza wersja podmenu

Komponent wygląda już dobrze, ale po pierwsze najpierw musimy sprawić, aby domyślnie był niewidoczny i pojawiał się dopiero po najechaniu kursorem na pozycję menu, a po drugie musimy sprawić, aby wyświetlał się w odpowiednim miejscu.

Aby element był niewidoczny na stronie i w żaden sposób nie wpływał na położenie innych elementów, należy użyć deklaracji display: none;. Dodajemy więc ją do reguły dotyczącej elementu div i sprawdzamy efekt:

Podmenu z display none

Doskonale, element zniknął i nie zaburza układu strony.

Wszystko mamy gotowe, wysuwany element jest ukryty, więc pozostaje nam już tylko sprawić, aby pojawiał się po najechaniu kursorem na pozycję menu. To bardzo proste. Wystarczy odpowiednio użyć znanej nam już pseudoklasy :hover. Przypomnę, że ta pseudoklasa pozwala zastosować ustawienia CSS do elementu, nad którym znalazł się kursor – czyli robi dokładnie to, czego nam potrzeba.

W tym przypadku chcemy, aby po najechaniu na wybrany element li (reprezentujący pozycję menu) zmieniały się właściwości wyświetlania wybranego elementu div (reprezentującego podmenu). W związku z tym napiszemy dla przeglądarki instrukcję nakazującą jej zastosować określone ustawienia CSS do elementu div, kiedy nad zawierającym go elementem li pojawi się kursor:

#main-header nav li:hover div {
  display: block;
}

To wszystko. Kiedy nad elementem li w menu głównym pojawi się kursor myszy, do znajdującego się w nim elementu div zostanie zastosowana deklaracja display: block;. Wcześniej zastosowaliśmy ustawienie display: none;, aby schować podmenu, a teraz je zmieniamy, aby element pojawiał się w danej sytuacji.

Dodaj tę regułę na końcu poprzednich reguł dotyczących menu i sprawdź efekt, najeżdżając kursorem na pozycję Paul Erdős. Okazuje się, że to jeszcze nie to, o co nam chodziło. Wprawdzie podmenu się pojawia, ale jest ustawione w nieodpowiednim miejscu i na dodatek robi bałagan wśród innych elementów. Musimy znaleźć sposób, na wyjęcie go z normalnego układu i sprawienie, aby unosiło się nad innymi elementami… Coś już powinno Ci świtać w głowie.

Efekt hover bez position

Problem z pozycjonowaniem podmenu załatwimy za pomocą dwóch deklaracji CSS: position: absolute; oraz position: relative;.

Deklarację position: absolute; dodamy do reguły CSS dotyczącej elementu div, ponieważ chcemy, aby „unosił” się nad innymi elementami.

Natomiast deklarację position: relative; zdefiniujemy dla elementów li zawierających elementy div reprezentujące podmenu, ponieważ chcemy, aby nasze podmenu były pozycjonowane w odniesieniu do położenia ich kontenerów, którymi są właśnie elementy listy.

Co ciekawe, w tym przypadku nie musimy nawet używać własności top, right, bottom i left, ponieważ tak się złożyło, że domyślne położenie akurat nam pasuje.

Poniżej znajduje się kompletny arkusz stylów naszego menu rozwijanego:

#main-header nav li {
  position: relative;
}
#main-header nav li div {
  display: none;
  position: absolute;
  min-width: 190px;
  text-align: left;
  border: 1px solid #dddddd;
  background-color: #ffffff;
  padding: 15px 0;
  font-size: 90%;
}
#main-header nav li div a {
  display: block;
  margin: 0 15px 12px 20px;
}
#main-header nav li div a:last-child {
  margin-bottom: 0;
}
#main-header nav li:hover div {
  display: block;
}

Dopieściliśmy już naszą stronę główną i menu, więc pora zająć się podstronami. To będzie tematem następnego rozdziału.

Podsumowanie

  • Elementy na stronie internetowej można pozycjonować za pomocą własności position.
  • Dostępnych jest pięć rodzajów pozycjonowania: static, absolute, relative, sticky, fixed.
  • Deklaracja display: none; sprawia, że element nie jest uwzględniany w drzewie dokumentu.
  • Deklaracja visibility: hidden; sprawia, że element znajduje się na stronie, ale jest niewidoczny.
  • Kluczowym składnikiem budowy menu rozwijanego w CSS jest pseudoklasa :hover.

Ćwiczenia

  1. Wybierz dowolny kod testowy z podrozdziału o pozycjonowaniu i sprawdź działanie różnych ustawień własności top, right, bottom i left. Szczególnie wypróbuj różne ustawienia przeciwstawnych wartości, tzn. zdefiniuj własności left i right oraz top i bottom.
  2. Odnośniki w podmenu Erdősa nie zawierają prawdziwych adresów, tylko krzyżyki. Zamień je na identyfikatory fragmentów, aby prowadziły do konkretnych części strony. Będzie to wymagało nadania identyfikatorów odpowiednim elementom.
  3. Dodaj podmenu do pozostałych pozycji menu, oprócz strony głównej.
  4. W regule CSS dotyczącej elementu div reprezentującego podmenu dodaj deklarację top: 1px; i sprawdź, co się stanie. Umiesz wyjaśnić dlaczego tak się stało? Umiesz za pomocą własności pozycjonowania ustawić podmenu we właściwym miejscu?
  5. Na wielu stronach internetowych elementy menu, które mają rozwijane podmenu, zawierają dodatkowy element (np. małą strzałkę) wskazujący, że można je rozwinąć. Dodaj taki element do swojego menu.