Poziome menu CSS z min i max-width
22 czerwca 2007
Czasami zdarza się tak, że oddając gotową stronę (zarządzaną zazwyczaj przez panel administracyjny) nie przewidzimy dokładnie ile danych klient będzie chciał w nią wstawić. Chyba najbardziej wrażliwą częścią strony jest nawigacja - złożona z listy nieuporządkowanej, i floatowanych pozycji.
Istnieją dwa podejścia do zaprojektowania takiego menu - pierwsze to określić na stałe szerokość elementu, a drugie to pozwolić aby szerokość była kształtowana przez długość tekstu. Oba mogą zawieść, jeśli potrzeba dłuższej etykietki niż tej w pliku PSD. Dlatego postanowiłem wykorzystać możliwości CSS2 i użyć min & max-width do osiągnięcia rozsądnego kompromisu.
Projekt pozycji nawigacji określonych szerokością minimalną i maksymalną nie jest trudny sam w sobie - tak naprawdę sterują nim dwie właściwości i tyle. Lecz musimy pamiętać, że to tylko początek XXI wieku, a przeglądarki kochają wręcz stroić fochy. Po kolei jednak:
Podstawowe menu
HTML menu.
<ul><li><a href=""><span>…</span></a></li><li><a href=""><span>…</span></a></li>…</ul>
Z pomocą stylów prezentacyjnych (które można dowolnie zmieniać, stąd nie piszę o nich dokładnie) otrzymałem takie menu. Na marginesie - zagnieżdżone spany możemy pominąć, ale wtedy będzie trzeba zmodyfikować hack na IE poniżej.
Następnie wypozycjonowałem je na ekranie, elementy dostały float: left, a linki wyświetlanie blokowe.
ul {margin-left: auto;margin-right: auto;width: 760px;overflow: hidden;}ul li { float: left; }ul a { display: block; }
Efekt użycia tych reguł widać w następnym podglądzie. Ujawnia on problem, o którym pisałem na początku – za długie etykietki powodują brzydkie rozepchnięcie menu.
Założyłem więc, że potrzebujemy na nie więcej miejsca i postanowiłem powiększyć wysokość dodając kolejną linię – choć oczywiście możemy poprzestać na jednej, a wystający tekst skrócić.
ul a {display: block;min-height: 3em;min-width: 80px;max-width: 105px;}
Błędy - Opera, Safari
To by było na tyle – dla przeglądarek, które wspierają użyte w tym selektorze standardy. Jest to Firefox i… Internet Explorer 7. Hehe, też się zaśmiałem. Opera 9, Explorer 6 i Safari 2 oraz 3 mają problemy (każdy program inne), stąd potrzeba pohakowania kodu.

Safari ma nie do końca poprawną implementację max-width. Otóż przeglądarka ta pobiera szerokość tekstu przed zawinięciem i w stosunku do niej aplikuje CSS-owe właściwości. Ostatnia pozycja ma mniej więcej 150px bez zawinięcia, max-width jest 105px, więc ogranicza szerokość, a tekst jest zawijany. To co powinna później zrobić jest dość proste do przewidzenia - sprawdzić jeszcze raz szerokość i zobaczyć, że teraz wynosi jakieś 70px i pozwolić na to. Błąd ten jest bardzo podobny do tego popełnionego przeze mnie podczas pisania expression dla IE6, ale o tym za chwilę.

Opera przesuwa każdą pozycję w lewo o jakąś wartość. Okazuje się, że aby naprawić to dziwaczne zachowanie należy zmienić display linków na inline-block.
Wtedy natomiast Firefox strzeli focha - dla niego dopisałem
display: -moz-inline-block, wystarczające na ten moment.Przez zamianę wyświetlania na liniowo-blokowe Safari 2 musi dostać łatkę na położenie w pionie.
A jako, że zepsułem trochę kod (przez Operę, wiedziałem! ;)), dla IE7 trzeba przywrócić
display: block.
ul a {display: inline-block;display: -moz-inline-block;min-height: 3em;min-width: 80px;max-width: 105px;vertical-align: top;}
Internet Explorer 6
Ok, idziemy dalej. Mamy przed sobą Explorera 6, bez którego eksplorowanie komercyjnych rynków jest niemożliwe – stąd musiałem znaleźć rozwiązanie. Z pomocą przyszły mi expressions i trochę dedykowanego CSS-a.
ul { zoom: 1; }ul a {height: 3em;display: block;float: left;}ul a span {float: left;cursor: pointer;}
Pierwsza linijka włącza layout. Następna reguła to proste poprawki - height zamiast min-height, wspomniane wyświetlanie blokowe oraz dodatkowy float (hasLayout powoduje, że linki miałyby 100% szerokości).
Trzecia reguła wykorzystuje wewnętrzny span, aby później można było określić faktyczną szerokość tekstu pozycji menu. Dalej - kursor często jest źle interpretowany przy zabawach z hasLayout, dlatego też łatka.
Wreszcie - expression (wstawiany do ul a { … }):
hackwidth: expression(this.parsed ? 0 : (boxpl = parseInt(this.currentStyle.paddingLeft),boxpr = parseInt(this.currentStyle.paddingRight),boxbl = (this.currentStyle.borderLeftWidth == 'medium') ? 0 : parseInt(this.currentStyle.borderLeftWidth),boxbr = (this.currentStyle.borderRightWidth == 'medium') ? 0 : parseInt(this.currentStyle.borderRightWidth),this.oldwidth = (this.offsetWidth - (boxpl + boxpr + boxbl + boxbr)),maxwidth = (this.oldwidth > 105) ? (this.style.width = '105px') : 0,minwidth = ((this.oldwidth < 80) || (this.firstChild.offsetWidth < 80)) ? (this.style.width = '80px') : 0,this.parsed = 1));
Bez paniki. ;) Pierwsze pięć wewnętrznych linijek odpowiada za pobranie wszelkich paddingów i obramowań, aby odjąć je potem od szerokości elementu (offsetWidth).
Później zapisuję w wymyślonym atrybucie oldwidth tą szerokość, aby dalej porównać ją z wartościami, ustawionymi jako min/max-width. Dodatkowo, aby nie powielić błędu Safari dodałem warunek sprawdzający szerokość spana (this.firstChild.offsetWidth) po zawinięciu wiersza.
Finał
Końcowy efekt jest prawie idealny - działa w miażdżącej większości przeglądarek i z powodzeniem może być stosowany wszędzie. Mam nadzieję, że się przyda - nie mogłem znaleźć lepszego (czyt: ani jednego) rozwiązania w Internecie. :)




