Responsywne menu rozwijane w JavaScript. UPDATE


23 listopada 2013 / Michał Załęcki


Strony, które poprawnie wyświetlają się na urządzeniach mobilnych z pewnego dobra luksusowego stały się artykułem pierwszej potrzeby dla przeciętnego internauty. Tak jak opanowanie jakiejś siatki jest proste i przyjemne tak menu może przysporzyć nam trochę kłopotów. Jest kilka popularnych typów responsywnych menu, lista wyboru (bardzo bolesne doświadczenia dla semantyki), wysuwane od boku i takie, które nazywam „harmonijkami”. Właśnie nad tym trzecim typem skupię się w tym poradniku, na końcu otrzymamy taki efekt. Pliki możecie od razu pobrać.

HTML

Parafrazując pierwszą polską encyklopedię powszechną, HTML jaki jest, każdy widzi. Tutaj bez większych niespodzianek, dostajemy to czego się spodziewaliśmy.

Mimo, że nazwy klas są bardzo opisowe, to pokrótce je opiszę.

  • menu – identyfikuje nasze menu
  • group – odpowiada za samooczyszczenie się opływania elementów listy
  • has-sub-menu – klasa dla elementów listy, które zawierają podmenu
  • show-sub-menu –pojemnik dla ikony do rozwijania menu, gdy mamy do czynienia z ekranem dotykowym, czyli tam gdzie nie możemy wykorzystać zdarzenia hover
  • icon-angle-circled-down – ikona z fontello.com
  • sub-menu – klasa podmenu
  • open – klasa dla podemu, które powinno być aktualnie rozwinięte, jest dodawana za pomocą JavaScriptu

CSS

Menu używa modelu pudełkowego „box sizing”, jest on bardzo wygodny i jeżeli jest Ci obcy to powinieneś nadrobić zaległości. W dużym skrócie powoduje to, że padding i obramowanie są zawarte w szerokości i wysokości elementu.

Modernizr dodaje do tagu html klasę touch, gdy urządzenie jest „uzbrojone” w wyświetlacz dotykowy. Niestety nie robi tego bezbłędnie, ale o tym w sekcji JavaScript.

Na koniec trochę kodu, który zmieni wygląd menu na urządzeniach mobilnych. Menu nie ma ograniczeń co do ilości poziomów zagnieżdżania, ale nie każcie swoim użytkownikom przechodzić do trzeciego czy czwartego poziomu. Jeżeli zachodzi taka potrzeba to warto pomyśleć o przeprojektowaniu waszego menu. Standardowo wcięcia są do czwartego poziomu, ale dodanie kolejnych klas indent-n (gdzie n to poziom zagnieżdżenia w menu).

Cały kod CSS:

JavaScript

W przypadku standardowych ekranów monitorów możemy trochę pooszukiwać, żeby sprawdzić jak nasz strona będzie działać na tabletach i smartfonach.

Uważam, że Modernizr do ideału ma naprawdę daleko, głownie za sprawą jego powolnego rozwoju i trochę olewackiego stosunku do użytkowników, ale i tak z niego korzystam, gdyż jest składnikiem wielu innych narzędzi, które sobie cenię (HTML5 Boilerplate, Zurb Foundation itd.). Jego popularność sprawia, ze każdy jego bug został już chyba poruszony w Sieci od kilku do kilkuset razy, tym bardziej dziwi, że błędy te są tak wolno poprawiane, gdyż często wystarcza jedna lub dwie linie kodu by sobie z nimi poradzić. Powyższy kod naprawia „rozpoznawanie” ekranów dotykowych (w praktyce to systemu operacyjnego) na urządzeniach z Windows Phone.

Przy menu warto zadbać o dostępność i umożliwić jego przechodzenie za pomocą tabulatora, co w przypadku menu rozwijanego wymaga zawszę paru sztuczek. Kod ten wyłącza menu po jego całkowitym przejściu.

Żeby nie zmuszać użytkowników do mozolnego zwijania nawigacji na ekranach dotykowych warto zamykać je po kliknięciu w dowolny, inny obszar strony.

Cały kod JavaScript:

UPDATE

Menu doczekało się swojej nowej, poprawionej wersji. W nowej wersji zaszły m. in. następujące zmiany:

  • Zastosowałem podejście Mobile First kosztem wsparcia dla IE8
  • Animacje rozwijania i zanikania
  • Kod JS i CSS (SASS) praktycznie przepisany na nowo
  • Bardziej elastyczny kod, większa ilość zmiennych konfiguracyjnych w SASS
  • Jeszcze lepsze działanie na urządzeniach mobilnych
  • Projekt przeniesiony na GitHub
  • Poprawiona dostępność
  • Poprawione mniejsze bugi (tzw. feature-y)

HTML

CSS

attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of elements
* that receive the

class.
* 2. The use of

rather than

is only necessary if using
*

to contain the top-margins of child elements.
*/
/* line 62, style.scss */
.clearfix:before,
.clearfix:after {
content: ” „;
/* 1 */
display: table;
/* 2 */
}

/* line 68, style.scss */
.clearfix:after {
clear: both;
}

/*
* For IE 6/7 only
* Include this rule to trigger hasLayout and contain floats.
*/
/* line 77, style.scss */
.clearfix {
*zoom: 1;
}

SASS

attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of elements
* that receive the

class.
* 2. The use of

rather than

is only necessary if using
*

to contain the top-margins of child elements.
*/

.clearfix:before,
.clearfix:after {
content: ” „; /* 1 */
display: table; /* 2 */
}

.clearfix:after {
clear: both;
}

/*
* For IE 6/7 only
* Include this rule to trigger hasLayout and contain floats.
*/

.clearfix {
*zoom: 1;
}

JavaScript

Przydatne linki



20 odpowiedzi na “Responsywne menu rozwijane w JavaScript. UPDATE”

  1. Comandeer pisze:

    Pytanie brzmi tak: jak zachowa się takie menu na małych ekranach w przypadku gdy JS jest niedostępny? Czy wówczas nie lepiej po prostu rozwinąć całość? Raptem kilka linijek CSS więcej.
    Zastanowiłbym się także nad tym czy nie bardziej by się opłacało zastosować podejście mobile first i najpierw napisać style dla harmonijki a w razie potrzeby podzielić to na ładne submenu.
    Też mam w miarę podstawowy kod dla takiego menu rozwijanego, ale nie jest wciąż gotowe. Niemniej jednak również pójdę w taką stronę, uwzględniając zastrzeżenia przedstawione powyżej ;)
    co do pomysłu z rozwinięciem przy TAB – jak najbardziej popieram! cholernie zwiększa to użyteczność.
    co do kodu JS – niestety, ale nie działa w strict mode. masz tzw. implied globals, czyli zapominasz deklarować zmienne przy użyciu var. nie widzę także sensu w przekazywaniu do funkcji window, document i (w sumie nieprzekazywaniu ;)) undefined. document i undefined w strict mode są read-only a więc nadpisać ich nie można (więc nie ma sensu tkać do funkcji ich lokalnych kopii). natomiast window nadpisać się nie da… więc tym bardziej nie widzę powodu jego wkładania do funkcji ;)
    btw „model pudełkowy border-box”. „box-sizing” to po prostu nazwa własności CSS.

    • Dodałem strict mode i var-y tam gdzie ich brakowało, w ogóle o strict mode zapomniałem, teraz śmiga jak należy. Kod uaktualnie później. O mobile first pomyślę i może zrobię 2. wersję. Co do linijki:

      ;(function ($, window, document, undefined) {

      Ludzie wezmą sobie ten plik js i wkleją go nie wiadomo gdzie i nie wiadomo jaki JS jest wykonywany obok niego. Tworzę w ten sposób odpowiednik czegoś na kształt odwróconego sandboxa i odizolowuję od siebie kod js każdego z plików. Taka konstrukcja jest bardzo znana. windows i document, przekazuję, gdyż pozytywnie wpływa to na szybkość działania skryptu (jeżeli nie to przynajmniej nie szkodzi), a undefined, nie zawsze jest undefined (ECMAScript 3), a tak mamy tego pewność. Kod podłapałem z jQuery Boilerplate, tam to zostało lepiej wyjaśnione.

      https://github.com/jquery-boilerplate/jquery-boilerplate/blob/master/src/jquery.boilerplate.js

    • Druga wersja już dostępna :)

      • Comandeer pisze:

        To trochu pomarudzę… ;)

        outline nie jest do końca widoczny (ba, jest widoczny tylko dla tego plusika). no i ten plusik również nie do końca mi pasuje (http://vekit.comandeer.pl/#icons)

        poza tym jest naprawdę dobrze

      • Dzięki.

        Nie wiem o co chodzi z tym linkiem, bo mi tam dropdown tabulatorem nie działa. FF.

      • Comandeer pisze:

        chodzi o sposób wstawiania ikonek.

      • No tak. Tylko jak mam osobny element to mam dla niego osobne zdarzenie. Trudno by było kliknąć ikonę nie wchodząc w link bez osobnego elementu. Jeszcze (z tego co mi wiadomo) nie ma api do rozpoznawania intencji użytkownika :) Poza tym mam złe doświadczenia z ikonami w :before i :after, zawsze więcej z tym roboty niż jest to (zaoszczędzenie kilku bajtów kodu) warte. Przynajmniej w mojej opinii.

      • Comandeer pisze:

        skoro potrzebujesz osobnego elementu do kliku, to Twoje obecne rozwiązanie tym bardziej się pogrąża ;)
        pomyśl: co widzi AT z takiego pustego elementu? nic, dla niego ten element nie istnieje. nie ma treści (bo jest pusty a nawet nie zastosowałeś aria-label), a dodatkowo jest dostępny z klawiatury (w tym momencie po prostu bezsensowny zabieg).
        no i dla AT to menu można o kant tyłka potłuc, bo submenu są ukryte przez display:none a przycisk nie istnieje (bo to pusty element…)
        inna rzecz – potrzebujesz focusowalny, klikalny element, który odpala jakąś akcję… toż to button… po co wymyślać koło od nowa? w HTML są aż dwa klikalne elementy. i o dziwo nikt nie chce użyć button tam, gdzie powinien być użyty… no cóż
        co do :before – radzę Ci zobaczyć jak fontello się przypina do elementu, bo właśnie sobie strzeliłeś w stopę ;) no i przecież pseudoelementy reagują na kliki rodzica.
        moje rozwiązanie? wstaw tam button. po prostu

      • Krytyka dodatkowego elementu do kliku znosi się z zamianą i na button. Dodatkowy element jest konieczny. Wiem jak fontello, jak to określiłeś, „przypina”. Podlinkowane rozwiązanie jest dla mnie nie do przyjęcia ponieważ nie niesie ze sobą możliwości kliknięcia w link, a jedynie rozwinięcia elementu. Jeżeli będę się bawił, jak na podlinkowanym przykładnie, w position absolute to estetyczna animacja odpada, a takiej opcji nie ma. Jedyna sensowna zmiana to zamiana div.show-sub-menu na buttona. Jak już mówisz o ARIA to samo aria-label jest nie wystarczające: aria-haspopup, aria-hidden, role=”menu”, role=”menuitem” itd, itd.

        > pseudoelementy reagują na kliki rodzica
        No to jest oczywiste, dlatego nazywają się pseudoelementami, a nie rodzeństwem.

      • Comandeer pisze:

        >Jak już mówisz o ARIA to samo aria-label jest nie wystarczające: aria-haspopup, aria-hidden, role=”menu”, role=”menuitem” itd, itd.

        owszem, ale w obecnej postaci menu po prostu nie istnieje dla AT. pozbycie się display:none na rzecz innej techniki ukrywania (jest ich tak dużo, że któraś przecież musi pasować) już stanowi duży krok.

        btw [role=menu] i [role=menuitem] AFAIR odnoszą się do interaktywnych menu aplikacyjnych, nie nawigacji per se – stąd niekoniecznie bym widział tu ich użycie. co do aria-haspopup, jak najbardziej

        i dalej nie rozumiem czemu moja metoda miałaby nie odpowiadać: przecież :before złapie klik (będzie częścią buttona). po prostu podmienić button na ikonkę („font-replacement”)

        >Jeżeli będę się bawił, jak na podlinkowanym przykładnie, w position absolute to estetyczna animacja odpada, a takiej opcji nie ma.

        jak dla mnie efektowność zawsze stoi niżej niźli dostępność.

      • U Cibie jest button, a ja potrzebuję linku, button mogę mieć ewentualnie obok linku czyli zamiana z div.show-sub-menu. Nie mogę wsadzić ikony w pseudoklasę linku, bo odpada funkcja „klikania” na urządzeniach z ekranami dotykowymi. A ewentualnie już w samym buttonie zrobić ikonę.

      • Comandeer pisze:

        o tym właśnie myślę: o ukryciu zawartości buttona i podstawieniu ikonki.
        poza tym mnie raczej chodziło o sekcję ikon na stronie a nie samo menu ;) ono jeszcze będzie dopieszczane

      • No ja pomyślę jeszcze nad tym buttonem, dzięki za podsuniecie rozwiazania.

  2. Mam następujące pytanko :
    otóż zauważyłem że menu zarówno pierwsze jak i drugie rozwija się po na jechaniu na element w chromie z kolei w innych przeglądarkach po kliknięciu (chodzi mi oczywiście o przeglądarki komputerowe nie mobilne)..
    Czy mógłby mi to ktoś wytłumaczyć czemu tak się dzieje ?

    • Dziwne, u mnie na wszystkich przeglądarkach działa tak samo. Skrypt dostosowuje działanie zależnie od tego czy ekran jest dotykowy czy nie. Upewnij się, że np. w FF nie masz włączonej symulacji. W pasku adresu wpisz about:config i wyszukaj „touch”. Upewnij się, ze wszystkie ustawienia mają status domyślny.

  3. Ludwik Krwawy pisze:

    „Parafrazując pierwszą polską encyklopedię powszechną, HTML jaki jest, każdy widzi.”
    nie encyklopedię, a Benedykta Chmielowskiego „Nowe Ateny”

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.