Dynamická menu v DHTML – pohyblivé menu

25. února 2002

Dnešním článkem končím seriál zabývající se vytvářením dynamických menu pomocí DHTML a ukáži vám, jak vytvořit opravdové vysouvací menu.

Jako obvykle začneme příkladem.

Budu vycházet z principů, které jsem popsal v druhém a třetím díle. Prvku menuHolder, který obsahuje nabídky i jednotlivá menu, přiřadíte přesnou velikost pomocí CSS vlastností width a height. Zároveň použijete dvě CSS vlastnosti, které říkají, jak se má nakládat s obsahem, který přesahuje hranice prvku (vytyčené právě vlastnostmi width a height). Jsou to tyto dvě vlastnosti:

  • Overflow – určuje, zdá má být prvek vidět celý, bez ohledu na předepsané rozměry (hodnota visible), nebo má být vidět jen část odpovídající předepsaným rozměrům (hodnota hidden), nebo se mají zobrazovat posuvné lišty (hodnoty scroll a auto). V našem případě použijete hodnotu hidden.
  • Clip – určuje, jaká část prvku má být viditelná. Má dvě hodnoty: auto (viditelný je celý prvek) a rect (horní, pravý, dolní, levý). Určuje výřez prvku definovaný pomocí vzdálenosti horního, pravého, dolního a levého okraje viditelné oblasti od levého horního okraje prvku. Přítomnost této vlastnosti je nutná kvůli IE4.

Obě uvedené vlastnosti jsou úzce spjaty s vysouvacím menu. Díky nim budete moci menu skrýt tak, že ho umístíte mimo viditelnou část prvku. Když uživatel přejede myší nad nabídkou, budete menu pomalu posouvat do viditelné části, čímž vznikne dojem skutečného vysunutí. Nyní celý postup vysvětlím podrobně.

HTML kód tvořící menu

HTML kód bude podobný jako u druhého menu seriálu (rozdíly mezi druhým a dnešním menu jsou zvýrazněny):

<div id=“menuHolder“>
<div id=“visibleMenu1″ onmouseover=“showMenu(1);“ onmouseout=“hideMenu(1);“><div class=“menuItem“>Portály<script type=“text/javascript“>function onMouseOver() {showMenu(1);} function onMouseOut() {hideMenu(1);}</script><span class=“cssSupport“><br></span></div></div>
<div id=“hiddenMenu1″ onmouseover=“dontHideMenu(1);“ onmouseout=“hideMenu(1);“><div class=“menuItem“><a href=“http://atlas.cz“ class=“menuLink“>Atlas</a><br><a href=“http://centrum.cz“ class=“menuLink“>Centrum</a><br><a href=“http://seznam.cz“ class=“menuLink“>Seznam</a><script type=“text/javascript“>function onMouseOver() {dontHideMenu(1);} function onMouseOut() {hideMenu(1);}</script><span class=“cssSupport“><br></span></div></div>
<div id=“visibleMenu2″ onmouseover=“showMenu(2);“ onmouseout=“hideMenu(2);“><div class=“menuItem“>Zpravodajství<script type=“text/javascript“>function onMouseOver() {showMenu(2);} function onMouseOut() {hideMenu(2);}</script><span class=“cssSupport“><br></span></div></div>
<div id=“hiddenMenu2″ onmouseover=“dontHideMenu(2);“ onmouseout=“hideMenu(2);“><div class=“menuItem“><a href=“http://cnn.com“ class=“menuLink“>CNN</a><br><a href=“http://ceskenoviny.cz“ class=“menuLink“>České Noviny</a><br><a href=“http://idnes.cz“ class=“menuLink“>iDNES</a><br><a href=“http://www.lidovky.cz“ class=“menuLink“>Lidovky</a><script type=“text/javascript“>function onMouseOver() {dontHideMenu(2);} function onMouseOut() {hideMenu(2);}</script><span class=“cssSupport“><br></span></div></div>
<div id=“visibleMenu3″ onmouseover=“showMenu(3);“ onmouseout=“hideMenu(3);“><div class=“menuItem“>Zábava<script type=“text/javascript“>function onMouseOver() {showMenu(3);} function onMouseOut() {hideMenu(3);}</script><span class=“cssSupport“><br></span></div></div>
<div id=“hiddenMenu3″ onmouseover=“dontHideMenu(3);“ onmouseout=“hideMenu(3);“><div class=“menuItem“><a href=“http://www.techno.cz“ class=“menuLink“>Czech Techno</a><br><a href=“http://flashfun.cz“ class=“menuLink“>FlashFun</a><br><a href=“http://www.kompost.cz“ class=“menuLink“>Kompost</a><br><a href=“http://novinky.cz“ class=“menuLink“>Novinky</a><script type=“text/javascript“>function onMouseOver() {dontHideMenu(3);} function onMouseOut() {hideMenu(3);}</script><span class=“cssSupport“><br></span></div></div>
<div id=“whiteMenu“><script type=“text/javascript“>if (ns4) document.write(‚<img src=“white.gif“ width=227 height=23 border=0 alt=“Nic“>‘);</script><span class=“cssSupport“><br></span></div>
</div>

Jak vidíte, funkci dontHideMenu() předáváte jako parametr číslo příslušné nabídky. Tento krok vysvětlím, až budu probírat zdrojový kód této funkce.

Do obsahu prvku menuHolder jsem přidal další tag DIV. Jediným jeho úkolem je schovat třetí menu. To je totiž větší, než třetí nabídka, a proto, když je zasunuté, je vedle této nabídky viditelné. Pokud se chcete podívat, jak by dnešní menu vypadalo, kdyby tento prvek chyběl, zde je upravený příklad. Všem prohlížečům kromě NS4 stačí, pokud tomuto prvku specifikujete pomocí CSS potřebné rozměry, pozici a barvu pozadí bílou (aby splynul s pozadím stránky – zde musíte použít barvu pozadí vaší stránky). NS4 potřebuje (kvůli špatné interpretaci CSS), aby byl použit jednopixelový bílý obrázek s danými rozměry. A ještě něco – všechny CSS vlastnosti související s tímto prvkem jsem zařadil do skupiny dynamických CSS vlastností (to jsou ty, které jsou definovány uvnitř JavaScriptového zápisu – ještě se k nim dostanu). Je tomu tak proto, že existence celého prvku whiteMenu je spojena pouze s dynamickým menu. V případě, že prohlížeč uživatele patří do skupiny nepodporující DHTML (viz druhý článek), není tohoto prvku vůbec potřeba.

Přiřazení CSS vlastností

U vizuálních CSS vlastností došlo od druhého menu našeho seriálu jen k nepatrným změnám. Všem prvkům hiddenMenu jsem nastavil výšku pomocí vlastnosti height (na 52 px, 68 px a 68 px). Je to logické – pokud chcete s těmito menu posouvat uvnitř prvku menuHolder, potřebujete znát přesně jejich výšku – jinak byste nevěděli, kdy je menu schované apod.

K větším změnám došlo u dynamických CSS vlastností. Zde si je můžete prohlédnout:

if (dhtml) document.write (‚<style type=“text/css“>#scriptSupport {display: none;} #menuHolder {position: ‚ + (ie4 ? ‚absolute‘ : ‚relative‘) + ‚; height: 100px; width: 233px; overflow: hidden; clip: rect(0px,233px,100px,0px); visibility: hidden; z-index: 9;} #visibleMenu1 {position: absolute; left: 0px; top: 0px; z-index: 7;} #visibleMenu2 {position: absolute; left: 51px; top: 0px; z-index: 6;} #visibleMenu3 {position: absolute; left: 135px; top: 0px; z-index: 5;} #whiteMenu {position: absolute; left: 0px; top: 0px; z-index: 4; background-color: #ffffff; height: 22px; width: 227px;} #hiddenMenu1 {position: absolute; left: 0px; top: 0px; z-index: 3;} #hiddenMenu2 {position: absolute; left: 51px; top: 0px; z-index: 2;} #hiddenMenu3 {position: absolute; left: 135px; top: 0px; z-index: 1;}</style>‘);

Prvku menuHolder jsem přiřadil výšku 100 pixelů a šířku 233 pixelů. Zároveň jsem použil vlastnosti overflow a clip (viz výše), které zajístí, že obsah přesahující hranice prvku nebude viditelný.

Všimněte si také, jak na sebe jednotlivé prvky vrstvím pomocí vlastnosti z-index. Nejvyšší z-index musí mít prvek menuHolder, jinak jsou v Operách všechna menu viditelná.

Prvku menuHolder nastavuji v případě, kdy prohlížeč uživatele je IE4, absolutní pozici. Je to kvůli tomu, že IE4 jinak nerespektuje vlastnosti overflow ani clip. Pokud nastavíte absolutní pozici a nenastavíte vlastnosti left, top, bottom nebo right, umístí IE4 prvek na místo na stránce, kde je definován. Dosáhnete tak stejného efektu, jako s relativní pozicí u ostatních prohlížečů.

Identifikaci prohlížečů jsem obohatil ještě o jednu proměnnou:

var opera4 = navigator.userAgent.indexOf(‚Opera 4‘) != -1 ? true : false;

Pomocí tohoto kódu rozeznáte prohlížeč Opera 4. Je to z toho důvodu, že v Opeře 4 není možné tento druh menu zprovoznit (nepodporuje nastavování CSS vlastnosti top pomocí JavaScriptu). Proto upravíte i hodnotu proměnné dhtml:

var dhtml = opera4 ? false : dom ? true : ie4 ? true : ns4 ? true : false;

Tímto jednoduchým postupem jste přeřadili Operu 4 do skupiny prohlížečů nepodporujích DHTML. Zobrazí se v ní jenom obyčejná navigace.

Vytvoření menu

Celé menu vytvoříte opět pomocí funkce menu():

var hiddenMenuTops = new Array(“,‘-33′,‘-49′,‘-49′);
function menu()
{
  if (dhtml)
  {
    for(i = 1; i <= 3; i++) setTop(‚hiddenMenu‘ + i,false,hiddenMenuTops[i]);
    if (opera) for(i = 1; i <= 3; i++) setTop(‚visibleMenu‘ + i,false,1);
    if (ie4) for(i = 1; i <= 3; i++) setTop(‚visibleMenu‘ + i,false,3);
    if (dom) document.getElementById(‚menuHolder‘).style.visibility = ‚visible‘;
    else if (ie4) document.all.menuHolder.style.visibility = ‚visible‘;
    else if (ns4) document.menuHolder.visibility = ‚visible‘;
  }
}

Nejdříve nastavujete vertikální polohu všech hiddenMenu (vlastnost top) pomocí funkce setTop() na hodnoty uvedené v poli hiddenMenuTops. Tyto hodnoty jsou totožné s těmi, které jste přiřazovali těmto prvkům v dynamických stylech. Je to proto, že v dalších krocích budete potřebovat z této vlastnosti číst, a to není možné bez předchozího nastavení. K funkci setTop() se vrátím později. Už zde nepoužívám funkci setVisibility(). Její definice by nebyla výhodná, protože nastavuji pouze viditelnost prvku menuHolder. Proto zviditelňuji prvek menuHolder pomocí posledních třech řádků kódu. Ještě před tím jsem posunul menu pomocí funkce setTop() kvůli špatné interpretaci CSS v Opeře a IE4.

Nyní se podívejte na slíbenou funkci setTop():

function setTop(element,inOrDecrementation,newTop)
{
  if (dhtml)
  {
    if (!inOrDecrementation)
    {
      if (dom) document.getElementById(element).style.top = newTop;
      else if (ie4) document.all[element].style.top = newTop;
      else if (ns4) eval(‚document.menuHolder.document.‘ + element + ‚.top = newTop‘);
    }
    else
    {
      if (dom) document.getElementById(element).style.top = parseInt(document.getElementById(element).style.top) + newTop;
      else if (ie4) document.all[element].style.top = parseInt(document.all[element].style.top) + newTop;
      else if (ns4) eval(‚document.menuHolder.document.‘ + element + ‚.top = parseInt(document.menuHolder.document.‘ + element + ‚.top) + newTop‘);
    }
  }
}

Pokud je parametr InOrDecrementation true, přičítáte k současné vlastnosti top hodnotu třetího parametru, newTop. V opačném případě nastavujete newTop jako novou hodnotu vlastnosti top. Ještě je zajímavé to, že v kódu pro NS4 už počítáte s tím, že vámi volaný prvek se nachází uvnitř prvku menuHolder. Díky tomu nemusíte při každém volání této funkce k příslušnému elementu přidávat kód (ns4 ? ‚menuHolder.document.‘ : “), jako tomu bylo u funkce setVisiblity().

Kromě této funkce budete používat ještě další pomocnou funkci, getTop(), která vrací vertikální pozici elementu:

function getTop(element)
{
  if (dhtml)
  {
    if (dom) return parseInt(document.getElementById(element).style.top);
    if (ie4) return parseInt(document.all[element].style.top);
    if (ns4) return parseInt (eval(‚document.menuHolder.document.‘ + element + ‚.top‘));
  }
}

Rozhýbání menu

Celý problém je dnes trochu složitější, takže se rovnou podíváme na zdrojový kód funkcí pro pohyb menu:

var timer, i, moveUp = false, justMovingDown = false, justMovingUp = false, queue = “;
function showMenu(id)
{
  if (dhtml)
  {
    if (((getTop(‚hiddenMenu1‘) == -33) || (id == 1)) && ((getTop(‚hiddenMenu2‘) == -49) || (id == 2)) && ((getTop(‚hiddenMenu3‘) == -49) || (id == 3)) && (!justMovingDown)) // testuje, zda jsou všechna menu na „startovních“ pozicích a zároveň už se menu nepohybuje
    {
      moveUp = false;
      clearTimeout(timer);
      justMovingUp = false;
      queue = “;
      justMovingDown = true;
      showMenu2(id);
    }
    else queue = id;
  }
}
function showMenu2(id)
{
  if ((!moveUp) && (getTop(‚hiddenMenu‘ + id) != 21)) // testuje, zda není pohyb rušen uživatelem nebo není menu v dolní pozici
  {
    setTop(‚hiddenMenu‘ + id,true,2);
    timer = setTimeout(‚showMenu2(‚ + id + ‚)‘,15);
  }
  else
  {
    moveUp = true;
    justMovingDown = false;
    if ((id == 3) && (getTop(‚hiddenMenu3‘) == 21))
    {
      if (dom) document.getElementById(‚hiddenMenu3‘).style.zIndex = 8;
      else if (ie4) document.all.hiddenMenu3.style.zIndex = 8;
      else if (ns4) document.menuHolder.document.hiddenMenu3.zIndex = 8;
    }
  }
}
function hideMenu(id)
{
  if (dhtml)
  {
    if (!justMovingUp) // testuje, zda už se menu nepohybuje
    {
      moveUp = true;
      clearTimeout(timer);
      showMenu2(id);
      justMovingUp = true;
      timer = setTimeout(‚hideMenu2(‚ + id + ‚)‘,50);
    }
    else if (queue == id) queue = “;
  }
}
function hideMenu2(id)
{
  if ((((getTop(‚hiddenMenu1‘) != -33) && (id == 1)) || ((getTop(‚hiddenMenu2‘) != -49) && (id == 2)) || ((getTop(‚hiddenMenu3‘) != -49) && (id == 3))) && (moveUp)) // testuje, zda pohyb není rušen uživatelem nebo není menu v dolní pozici
  {
    setTop(‚hiddenMenu‘ + id,true,-2);
    if ((id == 3) && (getTop(‚hiddenMenu3‘) != 21))
    {
      if (dom) document.getElementById(‚hiddenMenu3‘).style.zIndex = 1;
      else if (ie4) document.all.hiddenMenu3.style.zIndex = 1;
      else if (ns4) document.menuHolder.document.hiddenMenu3.zIndex = 1;
    }
    timer = setTimeout(‚hideMenu2(‚ + id + ‚)‘,15);
  }
  else
  {
    moveUp = false;
    justMovingUp = false;
    if (queue != “) showMenu(queue);
  }
}
function dontHideMenu(id)
{
  if (dhtml)
  {
    if ((!justMovingDown) && (!justMovingUp)) clearTimeout(timer);
    if (moveUp) showMenu(id);
  }
}

Pohyb menu se pokaždé skládá ze dvou funkcí: showMenu(), showMenu2() a hideMenu(), hideMenu2(). První funkce zkontroluje, zda jsou splněny podmínky pro pohyb menu do příslušného směru. Nastaví proměnné na takové hodnoty, aby mohl pohyb menu začít, a spustí druhou funkci. Ta se cyklicky opakuje až do chvíle, kdy je zastavena (akcí uživatele) nebo se menu dostane do cílové polohy.

Popis proměnných:

  • timer a i používám v časovačích, respektive cyklech a jejich význam je zřejmý;
  • justMovingDown má hodnotu true, jestliže je nějaké menu zrovna v pohybu dolů;
  • justMovingUp je analogická, ale pro pohyb menu nahoru;
  • moveUp slouží jako jakási brzda pohybu; pohyb nahoru totiž může probíhat jen tehdy, má-li hodnotu true, pohyb dolů jen při hodnotě false (pokud je potřeba zabrzdit pohyb menu, stačí změnit hodnotu této proměnné);
  • queue – je-li nějaké menu zrovna v pohybu, nesmí se vysouvat další menu; pokud ale uživatel během tohoto pohybu najede nad jinou nabídku než na tu, která náleží pohybujícímu se menu, automaticky se číslo této nabídky uloží do proměnné queue, až se menu zasune, okamžitě se může začít vysouvat nové; obsah proměnné queue se nuluje, jakmile se příslušné menu vysune, nebo když kurzor myši opustí příslušnou nabídku.

Ve funkci hideMenu() je nutné použít časovač, aby měl uživatel čas přejet kurzorem myši z horní nabídky nad příslušné menu. Také zde několikrát modifikuji vlastnost z-index prvku hiddenMenu3. Je to z toho důvodu, že toto menu je při dosažení dolní pozice potřeba nastavit před prvek whiteMenu (jinak by nebyl vidět jeho horní okraj).

Pro pochopení těchto funkcí je třeba, abyste si představili různé situace, kdy může dojít k jejich spuštění. Pokud i přesto budete mít problémy, jako vždy se můžete zeptat v diskusi pod článkem.

Doufám, že seriál o dynamických menu obohatil alespoň trochu vaše schopnosti tvorby webových stránek a že se nebudete muset bát pustit do tvorby vlastních dynamických menu. Zde se pravděpodobně setkáte s dalšími problémy a budete muset experimentovat. A právě do toho experimentování vám přeji štěstí a hlavně pevné nervy.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *