何はともあれその全体像は以下の図の通り。

階層メニュー部分のHTML文は以下のようになっている。それはUL-LI-UL-LI-・・・の階層構造であり、入れ子になったリスト構造である。この構造によって入れ子の部分をまとめて管理できるので、親−子−孫−構造こそが階層メニューの真骨頂ではないだろうか。
なお、メニューの各リスト項目をわざわざ<p>タグで囲ったのは、この部分をブロック扱いにするためである。(それによって文字部分ではない余白にマウスが重なっても当該部分をハイライトすることが出来る。)<p>タグを用いずにcssで各メニュー項目をブロック扱いする方法もあるが、この方が筋が通っていると考えた。
【ポイント】
■階層メニュー HTML文■
<!--▼階層メニュー開始-->
<ul id="gmenu">
<li onmouseover="showMenu(this,childwidth)" onmouseout="hideMenu(this)">
<p>MainList1</p>
<ul class="submenu1">
<li><p>SubList1-1</p></li>
<li><p>SubList1-2</p></li>
<li><p>・・・・・</p></li>
<li><p>SubList1-n</p></li>
<li><p>・・・・・</p></li>
</ul>
</li>
<li onmouseover="showMenu(this,childwidth)" onmouseout="hideMenu(this)">
<p>MainList2</p>
<ul class="submenu1">
<li onmouseover="showMenu2(this,childwidth)"><p>SubList2-1</p>
<ul class="submenu2">
<li><p>SubList2-1-1</p></li>
<li><p>SubList2-1-2</p></li>
<li><p>・・・・・</p></li>
<li><p>SubList2-1-n</p></li>
<li><p>・・・・・</p></li>
</ul>
</li>
<li onmouseover="showMenu2(this,childwidth)">><p>SubList2-2</p>
<ul class="submenu2">
<li><p>SubList2-2-1</p></li>
<li><p>SubList2-2-2</p></li>
<li><p>・・・・・</p></li>
<li><p>SubList2-2-n</p></li>
<li><p>・・・・・</p></li>
</ul>
</li>
<li><p>・・・・・</p></li>
<li><p>SubList2-n</p></li>
<li><p>・・・・・</p></li>
</ul>
</li>
<li onmouseover="showMenu(this,childwidth)" onmouseout="hideMenu(this)">
・・・(以下略)
</ul>
<!--▲階層メニュー終了-->
■階層メニュー HTMLブロックイメージ■

関連するCSS文の全てを掲載すると膨大になるし、その必要もないと思われるので要点となるCSS文をピックアップする。
#gmenu {
font-size:100%; line-height: 1.2;
margin: 0;
padding-left:0;
padding-bottom:0.7em;
list-style-type:none;
}
.submenu1 {
position:absolute; z-index:9;
display:none; /*初期は隠蔽*/
margin-top:-1.35em; /*menuタイトルにmenu項目をほぼ並べるため*/
margin-left:160px; /*menuタイトルの160px右にmenu項目を配置*/
color: #def; background-color: #333;
text-align: left;
border: 1px solid #def;
list-style-type:none;
}
.submenu2 {
position:absolute;z-index:10;
display:none;/*初期は隠蔽*/
margin-top:-1.35em;/*margin-leftはJavascriptにて定義する*/
color: #def; background-color: #333;
text-align: left;
border: 1px solid #def;
list-style-type:disc;
}
【ポイントあるいは弁明】
いかにしてちらつきを解消したのか、それが最大のポイントであり最も難しかった。onmouseoverであるブロックを表示することは簡単に出来るしサンプルも沢山ある。しかし、それを隠蔽する手法となると、領域外でクリックさせるものは多々あるものの、onmouseoutで隠蔽するスクリプトについては、少なくとも多段階層メニューのサンプルという点では余り見かけなかった。
そのため半年前からつい先日まで、我流のonmouseout → style.display="none"だけで階層メニューを隠蔽していたのだが、この方法には致命的欠陥があった。それがマウスカーソルをメニュー項目間で移動する度に頻繁に発生するちらつきである。
入れ子構造になっているulブロック内にはliタグもpタグもaタグもある。つまり多くのHTML要素が混在している。言い換えればDOMでいうnodeが沢山ある。問題は、最外側にあるnode内においてonmouseoutを設定した場合において、その内側にある任意のnodeからマウスが離れた時にも、onmouseoutイベントが発生したとbrowserが解釈してしまうことにあったのだ! そしてそのことが半年の間解明できずに時が流れ、あるサイトに遭遇してやっと解明できたのだ。
ちらつきを解消するにはどうすればよいのか? 最外側に設定されたonmouseoutイベントが入れ子になったその内部にあるタグには影響しないようにすれば良いのだろうが、それはどうするのか?
onmouseoutを最内部のタグに設定すれば、その子nodeはないのだからちらつかないかも知れないが、そうなるとメニュー項目の数だけの沢山のタグに全てonmouseoutを設定しなければならず、とても現実的ではない。
いっそのこと、階層メニュー内部の任意の位置、あるいはその最外側よりも外側の任意の箇所でマウスをクリックすれば隠蔽されるようにしてしまおうか? それは実に簡単に可能なことなので、誘惑に屈してしまおうかと思ったことも一度ならずあった。易きに流れることはたやすいことだから・・・。
しかし、しかしである。半年も事実上放置してその解決がクリックを要求する手法では余りに情けない! スマートに消えると共に、ちらつかないようなさっぱりした階層メニューこそが欲しているものであり、また実現できればそれに越したことはないのだから、もう一踏ん張りするしかない───と思っているその時に、あるサイトに遭遇したのだ。
それはある個人Webマスターの喜怒哀楽/entryである。このサイトから重大なヒントを得ることが出来たのだ。
それは「タイマー起動によって隠蔽動作を遅延させ、かつonmouseoverになった瞬間にそのタイマーをキャンセルしてしまう」──これである。この手法がこのサイトに的確に示されていたのである。
かくして完成したスクリプトは以下の通りである。まだまだ不勉強で改善すべき点は多々あると思うが、とりあえずスマートに動作する階層メニューとして一応の完成を自画自賛する次第である。
【課 題】
多段階層メニューJavascript文
関連するJavascript文は長くなるが、以下に全部抜粋しておく。
※ コメント文は当該行以下、次のコメントの前行までのスクリプトの説明。
05/06/08バグ取り修正版
/*++++++++++++++++++++++++++++++++++++++++++ 第2階層メニューの表示 ++++++++++++++++++++++++++++++++++++++++++*/
//Global variable var target=""; //前もってtimer1をセットしておく。 timer1=setTimeout("",500); /*++++第2階層表示関数++++*/ function showGmenu(which,childwidth) { //次行でタイマーを解除する。 clearTimeout(timer1); //ulの全ての子Nodeを捕捉(liもブロック変数も含まれる) var tempTarget = which.parentNode.childNodes; //子ノードを順番に辿る。 for (i=0; i < tempTarget.length; i++){ //第二子Nodeがない場合戻る。目的以外のタグを排除 if (tempTarget[i].childNodes[1] == null) continue; //ul-li-[1stchild] var menuitem = tempTarget[i].firstChild; //submenu=ul-li-1stchild=[2ndchild] var submenu = tempTarget[i].childNodes[1]; //該当Menu表示 if (tempTarget[i] == which) { with (submenu.style){ display = "block"; width = childwidth + "px" ; } //該当MenuItemの色替え with (menuitem.style) { backgroundColor = "#dd0"; color = "black"; } } else { //非該当menu隠蔽 submenu.style.display = "none"; //非該当MenuItemの色戻し with (menuitem.style) { backgroundColor = "#333"; color = "#dd0"; } } } }//END function/*++++++++++++++++++++++++++++++++++++++++++ 階層メニューの隠蔽 ++++++++++++++++++++++++++++++++++++++++++*/
/*++++(1)タイマー起動++++*/ function hideGmenu(which) { target = which; timer1 = setTimeout("hide()",500); } /*++++(2)タイマーにより0.5秒後に隠蔽++++*/ function hide() { //当該MainMenuItemの色戻し with (target.firstChild) { style.backgroundColor = "#333"; style.color = "#dd0"; } //当該MainMenulistの隠蔽 target.childNodes[1].style.display = "none"; }//END function/*++++++++++++++++++++++++++++++++++++++++++ 第3階層メニューの表示 ++++++++++++++++++++++++++++++++++++++++++*/
function showGmenu2(which,childwidth) { for (var i=0; which.parentNode.childNodes[i]; i++){ //ul-li-ul-[li] menulist = which.parentNode.childNodes[i]; if (!menulist.hasChildNodes()) continue; //ul-li-ul-li-[p] menuitem = menulist.firstChild; //ul-li-ul-li-p=[ul] submenu = menulist.childNodes[1]; (document.all)? listwidth = 180: listwidth = 180 - 20 if (menulist == which) { //該当menulist with (menuitem) { style.backgroundColor = "#dd0"; style.color = "black"; } with (submenu) { //該当menuを表示 style.display = "block"; //該当menuの左マージンを設定 if (document.all) style.marginLeft = listwidth -10 + "px"; else style.marginLeft = listwidth +10 + "px"; //展開する第3階層のmenu幅 style.width = childwidth + "px" ; } } else { //非該当menu隠蔽 submenu.style.display = "none"; with (menuitem) { //他のmenulistの色戻し style.backgroundColor = "#333"; style.color = "#dd0"; } } } }//END function