.container1 { height: 30px; }.container1 div { float: left; }.container1 div, .container1_2 div { width: 100px; background: none repeat scroll 0% 0% rgb(250, 252, 253); border: 1px solid rgb(92, 156, 192); padding: 10px; }div.on1 { font-weight: bold; background: none repeat scroll 0% 0% rgb(238, 243, 247); }div.on1_2 { font-weight: bold; background: none repeat scroll 0% 0% rgb(255, 255, 247); border: 1px solid rgb(255, 204, 0); } 菜單使用演示:
效果預覽

自定義樣式

下拉菜單

任意定位

相對容器

相對菜單

-------------test-------------


位置: 第四個第三個第二個第一個1秒0.5秒0.2秒不延時

仿京東商城商品分類菜單:


.container2, .container2 dd, .container2_2 dl, .container2_2 dd { margin: 0pt; }.container2 { font-size: 14px; width: 190px; border: 1px solid rgb(207, 32, 32); background: none repeat scroll 0% 0% rgb(255, 255, 245); padding: 5px 8px; line-height: 30px; color: rgb(51, 51, 51); }.container2 dt { font-weight: bold; color: rgb(207, 32, 32); }.container2 dd { background: url("") no-repeat scroll 180px 10px transparent; }.container2_2 { background-color: rgb(190, 190, 195); display: none; }.container2_2 dl { font-size: 14px; width: 200px; border: 1px solid rgb(150, 150, 150); background: none repeat scroll 0% 0% rgb(255, 255, 255); position: relative; left: -3px; top: -3px; }.container2_2 dd div { padding: 5px 20px; background: url("") no-repeat scroll 6px 7px transparent; }.container2_2 dt, .shadow { padding: 0pt 5px; position: absolute; background: none repeat scroll 0% 0% rgb(255, 255, 255); border-width: 1px 0pt 1px 1px; border-style: solid none solid solid; border-color: rgb(150, 150, 150) -moz-use-text-color rgb(150, 150, 150) rgb(150, 150, 150); width: 169px; left: -180px; top: -1px; height: 24px; line-height: 24px; }.shadow { background-color: rgb(190, 190, 195); border-color: rgb(190, 190, 195); top: 0pt; }.container2_2 a { display: block; }.container2_2 a:link, .container2_2 a:visited, .container2_2 a:active { color: rgb(51, 51, 51); text-decoration: none; }.container2_2 a:hover { color: rgb(255, 96, 38); text-decoration: underline; } 
 
    

仿window xp右鍵菜單:

.container3 { font-size: 12px; border: 1px solid rgb(157, 157, 161); padding: 3px; line-height: 18px; background: none repeat scroll 0% 0% rgb(255, 255, 255); cursor: default; -moz-user-select: none; }.container3 div { padding: 0pt 20px; }.menu3_1 { color: rgb(172, 168, 153); }.menu3_2 { background: url("") no-repeat scroll 133px 0pt transparent; }.menu3_2_on { background-position: 133px -18px; }.menu3_3 { background: url("") no-repeat scroll left -36px transparent; }.menu3_3_on { background-position: left -54px; }.menu3_4 { background: url("") no-repeat scroll left -72px transparent; }.menu3_4_on { background-position: left -90px; }.line3 { border-bottom: 1px solid rgb(172, 168, 153); margin: 4px 0pt; }.on3 { background-color: rgb(49, 106, 197); color: rgb(255, 255, 255); }.area3 { width: 500px; height: 200px; border: 1px solid rgb(172, 168, 153); }.pos3 { position: absolute; display: none; width: 150px; } 
 
 
 
 
 
V
 
  
I
 
  
E
 
  
F
 
  
P
 
  
S
 
  
P
 
  
S

仿淘寶拼音索引菜單:


.container4 li, .container4_2 li { list-style: none outside none; }.container4 ul, .container4_2 { margin: 0pt; }.container4 { width: 350px; padding: 7px 10px; font: 12px/15px Verdana; border: 1px solid rgb(204, 204, 204); background: none repeat scroll 0% 0% rgb(255, 254, 237); height: 15px; }.container4 li { float: left; padding: 0pt 10px; border-right: 1px solid rgb(204, 204, 204); }.container4 div { float: left; color: rgb(0, 0, 0); padding-right: 10px; }li.menu4 { position: relative; margin-left: -1px; top: -1px; z-index: 9999; border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(133, 204, 255) rgb(133, 204, 255) -moz-use-text-color; padding-bottom: 8px; color: rgb(255, 96, 38); background: none repeat scroll 0% 0% rgb(219, 243, 255); }.container4_2 { width: 350px; padding: 10px; border: 1px solid rgb(133, 204, 255); background: none repeat scroll 0% 0% rgb(219, 243, 255); line-height: 25px; font-size: 14px; font-weight: bold; display: none; }.container4_2 a { display: block; }.container4_2 a:link, .container4_2 a:visited, .container4_2 a:active { color: rgb(86, 85, 83); text-decoration: none; }.container4_2 a:hover { color: rgb(255, 85, 0); text-decoration: underline; }.container4 a:link, .container4 a:visited, .container4 a:hover, .container4 a:active { color: rgb(86, 85, 83); text-decoration: none; }.menu4 a:link, .menu4 a:visited, .menu4 a:active { color: rgb(255, 96, 38); }.menu4 a:hover { color: rgb(255, 96, 38); text-decoration: underline; }

程序原理

程序最關鍵的地方是多級聯動,先大概説明一下:
首先第一級的菜單元素整理好後,從他們開始,當某個菜單元素觸發顯示下級菜單時,
準備好下一級的容器元素,並把下一級的菜單元素放進去,再定位並顯示容器元素。
裏面的菜單元素又可以觸發顯示下級菜單,然後按上面的步驟執行下去。
這樣一級一級的遞推下去,形成多級的聯動菜單。

程序説明

【容器對象】

在多級聯動中,每一級都需要一個容器元素來存放菜單元素。
程序中每個容器元素都對應一個容器對象,用來記錄該容器的相關信息。
容器對象的集合記錄在程序的_containers屬性中。

容器參數containers是程序實例化時的必要參數,它的結構如下:

[
     容器元素(id),
     { id: 容器元素(id), menu: 插入菜單元素(id) },
     

 ]

首先如果containers不是數組的話,程序會自動轉成單元素數組。
如果菜單插入的元素就是容器元素本身,可以直接用容器元素(id)作為數組元素。
否則應該使用一個對象結構,它包括一個id屬性表示是容器元素(id)和一個menu屬性表示菜單插入的元素(id)。

containers會在程序初始化時這樣處理:


Code   
$$A.forEach($$A.isArray(containers)  ?  containers : [containers],  function (o, i){
      var  pos, menu;
      if  ( o.id ) {
         pos  =  o.id; menu  =  o.menu  ?  o.menu : pos;
     }  else  {
         pos  =  menu  =  o;
     };
     pos  =  $$(pos); menu  =  $$(menu);
     pos  &&  menu  &&   this ._iniContainer( i, {  " pos " : pos,  " menu " : menu } );
 },  this );

主要是生成一個容器對象,其中pos屬性是容器元素,menu屬性是插入菜單的元素。
然後傳遞索引和容器對象給_iniContainer函數,對容器對象做初始化。

在_iniContainer中,首先用_resetContainer重置容器對象可能在程序中設置過的屬性。
再給容器元素添加事件:


Code   
$$E.addEvent(oContainer,  " mouseover " , $$F.bind( function (){ clearTimeout( this ._timerContainer); },  this ));
 $$E.addEvent(oContainer,  " mouseout " , $$F.bindAsEventListener( function (e){
      // 先判斷是否移出到所有容器之外 
      var  elem  =  e.relatedTarget,
         isOut  =  $$A.every( this ._containers,  function (o){  return   o.pos  ==  elem  ||   ! ($$D.contains(o.pos, elem)); });
      if  ( isOut ) {
          // 清除定時器並隱藏 
         clearTimeout( this ._timerContainer); clearTimeout( this ._timerMenu);
          this ._timerContainer  =  setTimeout( $$F.bind(  this .hide,  this  ),  this .delay );
     };
 },  this ));

在mouseout時,先判斷是否容器內部或容器之間觸發,不是的話再用定時器執行hide隱藏函數。
在hide裏面,主要是隱藏容器:


this   ._forEachContainer( 
  function 
  (o, i){
        if 
   ( i  
  === 
    
  0 
   ) {
            this 
  ._resetCss(o);
     }    else 
   {
            this 
  ._hideContainer(o);
     };
 });


由於第一級容器一般是不自動隱藏的,只需要用_resetCss來重置樣式。
其他容器會用_hideContainer函數來處理隱藏:


$$D.setStyle( container.pos, { left:    " 
  -9999px 
  " 
  , top:  
  " 
  -9999px 
  " 
  , visibility:  
  " 
  hidden 
  " 
   } );
   this 
  ._containers[container._index  
  - 
    
  1 
  ]._active  
  = 
    
  null 
  ;


其中_active屬性是保存該容器觸發下級菜單的菜單對象,在隱藏容器同時重置上一級容器的_active。

在mouseover時清除容器定時器,其實就是取消hide執行。

之後是設置樣式:


if    ( index ) {
     $$D.setStyle(container.pos, {
         position:    " 
  absolute 
  " 
  , display:  
  " 
  block 
  " 
  , margin:  
  0 
  ,
         zIndex:    this 
  ._containers[index  
  - 
    
  1 
  ].pos.style.zIndex  
  + 
    
  1 
  
     });
 };


除了第一級容器外,都設置浮動需要的樣式。

最後用_index屬性記錄索引,方便調用,並把容器對象插入到容器集合中: 

container._index    = 
   index;
   this 
  ._containers[index]  
  = 
   container;


這個索引很重要,它決定了容器是用在第幾級菜單。

【菜單對象】

容器元素插入了菜單元素才算一個菜單。
程序中每個菜單元素都對應一個菜單對象,用來記錄該菜單的相關信息。

程序初始化前,應該先創建好自定義菜單集合,它的結構是這樣的: 

[
     { id:    1 
  , parent:  
  0 
  , html: 元素內容 },
     { id:    2 
  , parent:  
  1 
  , html: 元素內容 },
     

 ]

其中id是菜單的唯一標識,parent是父級菜單的id。
除了這兩個關鍵屬性外,還可以包括以下屬性:
rank:排序屬性
elem:自定義元素
tag:生成標籤
css:默認樣式
hover:觸發菜單樣式
active: 顯示下級菜單時顯示樣式
html:菜單內容
relContainer:是否相對容器定位(否則相對菜單)
relative:定位對象
attribute:自定義Attribute屬性
property:自定義Property屬性

其中relContainer和relative是用於下級容器定位的。

自定義菜單集合會保存在_custommenu屬性中。
在程序初始化時會執行_buildMenu程序,根據這個_custommenu生成程序需要的_menus菜單對象集合。
_buildMenu是比較關鍵的程序,菜單的層級結構就是在這裏確定,它由以下幾步組成:

第一步,清除舊菜單對象集合的dom元素。
這一步後面“內存泄漏”會詳細説明。

第二步,生成菜單對象集合。 為了能更有效率地獲取指定id的菜單對象,_menus是以id作為字典關鍵字的對象。
首先創建帶根菜單(id為“0”)對象的_menus: 


this   ._menus    = 
   {  
  " 
  0 
  " 
  : {  
  " 
  _children 
  " 
  : [] } };


然後整理_custommenu並插入到_menus中:


Code   
$$A.forEach( this ._custommenu,  function (o) {
      var  menu  =  $$.deepextend( $$.deepextend( {}, options ), o  ||  {} );
      if  (  !! this ._menus[ menu.id ] ) {  return ; };
     menu._children  =  []; menu._index  =   - 1 ;
      this ._menus[menu.id]  =  menu;
 },  this );

其中菜單對象中包含對象屬性,要用deepextend深度擴展來複制屬性。
為確保id是唯一標識,會排除相同id的菜單,間接排除了id為“0”的菜單。
在重置_children(子菜單集合)和_index(聯級級數)之後,就可以插入到_menus中了。

第三步,建立樹形結構。
菜單之間的關係是一個樹形結構,程序通過id和parent來建立這個關係的(寫過數據庫分級結構的話應該很熟悉)。
而第一版是把子類直接菜單寫在菜單元素的menu屬性中,形成類似多維數組的結構。
比較這兩個方法,第一版的優勢在於定義菜單時就直接確立了關係,而新版還必須根據id和parent來判斷增加代碼複雜度。
新版的優勢是使用維護方便,靈活,級數越多就越體現出來,而第一版剛好相反。

能不能結合這兩個方法的優勢呢?
這裏採用了一個折中的方法,在寫自定義菜單對象時用的是新版的方法,然後程序初始化時把它轉換成類多維數組結構。
轉換過程是這樣的:
首先根據parent找到父菜單對象:


var    parent    = 
    
  this 
  ._menus[o.parent];


如果找不到父菜單對象或父菜單對象就是菜單對象本身的,當成一級菜單處理:


if    (    ! 
  parent  
  || 
   parent  
  === 
   o ) { parent  
  = 
   menus[o.parent  
  = 
    
  " 
  0 
  " 
  ]; };

最後把當前菜單對象放到父菜單對象的_children集合中: 


parent._children.push(o);


這就把_menus變成了類多維數組結構,而且這個結構不會發生死循環。

第四步,整理菜單對象集合。 這步主要是整理_menus裏面的菜單對象。
首先,把自定義菜單元素放到碎片文檔中:


!!   o.elem    && 
   ( o.elem  
  = 
   $$(o.elem) )  
  && 
    
  this 
  ._frag.appendChild(o.elem);


菜單元素是需要顯示時才會處理的,這樣可以防止在容器上出現未處理的菜單元素。

然後是修正樣式(詳細看樣式設置部分)。

最後,對菜單對象的_children集合進行排序:

o._children.sort(   function   ( x, y ) {  
  return 
   x.rank  
  - 
   y.rank  
  || 
   x.id  
  - 
   y.id; });

先按rank再按id排序,跟菜單對象定義的順序是無關的。

執行完BuildMenu程序之後,_menus菜單對象集合就建立好了。
麻煩的是在每次修改_custommenu之後,都必須執行一次_buildMenu程序。

【多級聯動】

容器對象和菜單對象都準備好了,下面就是如何利用它們來做程序的核心——多級聯動效果了。

多級聯動包括以下步驟:

第一步,準備一級容器。
一級容器一般是顯示狀態的(也可以自己定義它的顯示隱藏,像仿右鍵菜單那樣)。

第二步,向容器插入菜單。
通過_insertMenu程序,可以向指定容器插入指定菜單,其中第一個參數是索引,第二個參數是父菜單id。

在_insertMenu程序裏面,先判斷是否同一個父級菜單,是的話就返回不用重複操作了: 


var    container    = 
    
  this 
  ._containers[index];
   if    ( container._parent  
  === 
   parent ) {  
  return 
  ; };
 container._parent    =    parent;  
 接着把原有容器內菜單移到碎片對象中:
  
$$A.forEach( container._menus,    function   (o) { o._elem  
  && 
    
  this 
  ._frag.appendChild(o._elem); },  
  this 
   );
  
 在第一版,菜單每次使用都會重新創建,新版改進後會把舊菜單元素保存到碎片對象中,要使用時再拿出來。
然後根據parent獲取父菜單對象,並把父菜單的_children子菜單集合的插入到容器中:
  
$$A.forEach(   this   ._menus[parent]._children,  
  function 
  ( menu, i ){
        this   ._checkMenu( menu, index );
     container._menus.push(menu);
     container.menu.appendChild(menu._elem);
 },    this   );


 
這樣整個菜單就準備好了。

第三步,添加觸發下級菜單事件。 上面在把菜單插入到容器之前,會先用_checkMenu程序檢查菜單對象。

_checkMenu程序主要是檢測和處理菜單元素。
首先判斷沒有自定義元素,沒有的話就創建一個:

var    elem    = 
   menu.elem;
   if    (  
  ! 
  elem ) { elem  
  = 
   document.createElement(menu.tag); elem.innerHTML  
  = 
   menu.html; };  
 第一版並不能自定義元素,但考慮到seo、漸進增強等,在新版加入了這個功能。
 但每次BuildMenu之後會把所有菜單元素包括自定義元素都清除,這個必須留意。然後分別設置property、attribute和className屬性:
  
$$.extend( elem, menu.property );
   var    attribute  
  = 
   menu.attribute;
   for    ( 
  var 
   att  
  in 
   attribute) { elem.setAttribute( att, attribute[att] ); };
 elem.className    =    menu.css;  
 ps:關於property和attribute的區別請看這裏的attribute/property部分 。
然後是關鍵的一步,添加HoverMenu觸發事件程序:
  
menu._event    =    $$F.bindAsEventListener(  
  this 
  ._hoverMenu,  
  this 
  , menu );
 $$E.addEvent( elem,    "   mouseover 
  " 
  , menu._event );

 
處理後的元素會保存在菜單對象的_elem屬性中。

第四步,觸發顯示下級菜單事件。
當觸發了顯示下級菜單事件,就會執行_hoverMenu程序。
在_hoverMenu程序裏面,主要是做一些樣式設置,詳細參考後面的樣式設置部分。
然後是用定時器準備執行_showMenu顯示菜單程序。

第五步,整理菜單容器。 在_showMenu程序中,首先是隱藏不需要的容器:


this   ._forEachContainer(    function 
  (o, i) { i  
  > 
   index  
  && 
    
  this 
  ._hideContainer(o); } );


然後判斷當前菜單是否有子菜單,當有子菜單時,先用_checkContainer程序檢查下級菜單容器。
_checkContainer程序主要是檢查容器是否存在,不存在的話就自動添加一個:


var    pre    = 
    
  this 
  ._containers[index  
  - 
    
  1 
  ].Pos
     ,container    =    pre.parentNode.insertBefore( pre.cloneNode( 
  false 
  ), pre );
 container.id    =     
  "" 
  ;


其實就是用cloneNode複製前一個容器,注意要重置id防止衝突。
雖然程序能自動創建菜單,但也要求至少自定義一個容器。

第六步,顯示菜單容器。 在顯示之前,先按第二步向容器插入菜單,最後就是執行_showContainer程序來定位和顯示容器了。

當下一個容器內的菜單觸發顯示下級菜單事件時,會顯示下下級的菜單容器。
程序就是這樣一級一級遞推下去,形成多級聯級效果。

【樣式設置】

樣式設置也是一個重要的部分,不是説要弄出多炫的界面,而是如何使程序能最大限度地靈活地實現那些界面。

菜單對象有三個樣式相關的屬性,分別是:
css:默認樣式
hover:鼠標進入菜單時使用樣式
active:顯示下級菜單時使用樣式

在_buildMenu程序中,會對這些樣式屬性進行整理:


element ui Container 左右 聯動_IEelement ui Container 左右 聯動_Chrome_02Code

if  (  !! o.elem  &&  o.elem.className ) {
     o.css  =  o.elem.className;
 }  else   if  ( o.css  ===  undefined ) { o.css  =   "" ; };
 if  ( o.hover  ===  undefined ) { o.hover  =  o.css; };
 if  ( o.active  ===  undefined ) { o.active  =  o.hover; };

可以看到,程序會優先使用自定義元素的class,避免被程序設置的默認樣式覆蓋。
空字符串也可能被用來清空樣式,所以要用undefined來判斷是否自定義了樣式。

程序中主要在兩個地方設置樣式:在鼠標移到菜單元素上時(_hoverMenu)和顯示下級菜單時(_showMenu)。

在_hoverMenu程序中,先對每個顯示的容器設置一次樣式:

this   ._forEachContainer(   function 
  (o, i){
        if    ( o.pos.visibility  
  === 
    
  " 
  hidden 
  " 
   ) {  
  return 
  ; };
        this   ._resetCss(o);
        var    menu  
  = 
   o._active;
        if    ( menu ) { menu._elem.className  
  = 
   menu.active; };
 });

由於鼠標可能是在多個容器間移動,所以所有顯示的容器都需要設置。
用_resetCss重置容器樣式後再設置有下級菜單的菜單的樣式為active。
為了方便獲取,容器對象用一個_active屬性來保存當前容器觸發了下級菜單的菜單對象。

然後是設置鼠標所在菜單的樣式:


if    (    this 
  ._containers[menu._index]._active  
  !== 
   menu ) { elem.className  
  = 
   menu.hover; };


為了優先設置active樣式,在當前菜單不是容器的_active時才設置hover樣式。

在_showMenu程序中,首先把顯示下級菜單的菜單對象保存到容器的_active屬性。
再用_resetCss重置當前容器樣式,這個在同級菜單中移動時會有用。
然後再根據當前菜單是否有下級菜單來設置樣式為active或hover。

【內存泄漏】

上面“菜單對象”中説到清除舊菜單對象的dom元素,這個主要是為了防止內存泄漏。
關於內存泄漏也有很多文章,這裏推薦看看Douglas Crockford的“JScript Memory Leaks ”和winter的“瀏覽器中的內存泄露 ”。

下面説説我解決本程序內存泄漏的經過:
首先,通過調用程序的Add和Delete數千次來測試是否有內存泄漏。
怎麼看出來呢?可以找些相關的工具來檢測,或者直接看任務管理器的頁面文件(pf)使用記錄。
結果發現,雖然每個元素都用removeChild移出了dom,但隨着循環的次數增多,pf還是穩步上升。
於是按照Memory Leaks中説的“we must null out all of its event handlers to break the cycles”去掉事件:


removeEvent( elem,    "   mouseover 
  " 
  , o._event );


效果是有了,但不太理想,然後再逐一排除,發現原來是_elem屬性還關聯着元素,結果經過一些操作後,又把元素append到dom上,還重新創建了一個元素。

於是在移除元素後,立即重置_elem和elem屬性:


o._elem  =  o.elem  =   null ;


內存泄漏就沒有了,其實這裏也不算是內存泄露了,而是程序設計有問題了。
所以清除dom元素時必須注意:
1,按照Douglas Crockford的建議,移除所有dom元素相關的事件函數;
2,刪除/重置所有關聯dom元素的js對象/屬性。

【cloneNode的bug】

在上面多級聯動中説到,會用cloneNode複製容器,但cloneNode在ie中有一個bug:
在ie用attachEvent給dom元素綁定事件,在cloneNode之後會把事件也複製過去。
而用addEventListener添加的事件就不會,可以在ie和ff測試下面的代碼: 


Code   
<! DOCTYPE html > 
 < html > 
 < body > 
 < div id = " t " > div < / div> 
 < script > 
 var  o  =  document.getElementById( " t " );
 if (o.attachEvent){
  o.attachEvent( " onclick " ,  function (){alert( 2 )});
 } else {
  o.addEventListener( " click " ,  function (){alert( 2 )},  false );
 }
 document.body.appendChild(o.cloneNode( true ));
 < / script> 
 < / body> 
 < / html>  
 在ie和ff點擊第一個div都會觸發alert,關鍵是第二個div,在ff不會觸發,而ie就會。
 當然這個是不是bug還不清楚,或許attachEvent本來就是這樣設計的也説不定。
 但第一版就是由於這個bug,而沒有用cloneNode。在找解決方法之前,再擴展這個問題,看看直接添加onclick事件會不會有同樣的bug。
 首先測試在元素裏面添加onclick:   
<!   DOCTYPE html   > 
  
   <   html 
  > 
  
   <   body 
  > 
  
   <   div id 
  = 
  " 
  t 
  " 
   onclick 
  = 
  " 
  alert(1) 
  " 
  > 
  div 
  < 
  / 
  div> 
  
   <   script 
  > 
  
   var    o  
  = 
   document.getElementById( 
  " 
  t 
  " 
  );
 document.body.appendChild(o.cloneNode(   true   ));
   <   / 
  script> 
  
   <   / 
  body> 
  
   <   / 
  html>  
 結果在ie和ff都會複製事件。
再測試在js添加onclick: 
  
   Code   
<! DOCTYPE html > 
 < html > 
 < body > 
 < div id = " t " > div < / div> 
 < script > 
 var  o  =  document.getElementById( " t " );
 o.onclick  =   function (){alert( 1 )}
 document.body.appendChild(o.cloneNode( true ));
 < / script> 
 < / body> 
 < / html>


結果在ie和ff都不會複製事件,看來只有attachEvent會引起這個bug。

下面是解決方法:
用John Resig在《精通JavaScript》推薦的Dean Edwards寫的addEvent和removeEvent方法 來添加/移除事件。
它的好處就不用説了,而且它能在ie解決上面説到的cloneNode的bug。
因為它的實現原理是在ie用onclick來綁定事件,而上面的測試也證明用onclick綁定的事件是不會被cloneNode複製的。

ps:我對原版的方法做了些修改,方便調用。

【浮動定位】

容器的浮動定位用的是浮動定位提示效果 中的定位方法。
在該文章中已經詳細説明了如何獲取指定的浮動定位座標,這裏做一些補充。

一般來説用getBoundingClientRect配合scrollLeft/scrollTop就能獲得對象相對文檔的位置座標。
測試下面代碼:

Code   
<! DOCTYPE html > 
 < html > 
 < body style = " padding:1000px 0; " > 
 < div id = " t1 "  style = " border:1px solid; width:100px; height:100px; " >< / div> 
 < div id = " t2 " >< / div> 
 < script > 
 var  $$  =   function  (id) {
      return   " string "   ==   typeof  id  ?  document.getElementById(id) : id;
 };
 var  b  =   0 ;
 window.onscroll = function (){
   var  t  =  $$( " t1 " ).getBoundingClientRect().top  +  document.documentElement.scrollTop;
   if ( t  !=  b ){ b  =  t; $$( " t2 " ).innerHTML  +=  t  +   " <br> " ; }
 }
 < / script> 
 < / body> 
 < / html>

在除ie8外的瀏覽器,t會保持在一個固定值,但在ie8卻會在1008和1009之間變換(用鼠標一格一格滾會比較明顯)。
雖然多數時候還是標準的1008,但原來的效果可能就會被這1px的差距破壞(例如仿京東和仿淘寶的菜單)。
ps:chrome和safari要把documentElement換成body。

為了解決這個問題,只好在ie8的時候用回傳統的offset來取值了(詳細參考代碼)。
至於造成這個問題的原因還沒弄清楚,各位有什麼相關資料的記得告訴我哦。

使用技巧

在仿京東商城商品分類菜單中,實現了一個陰影效果。
原理是這樣的:
底部是一個灰色背景層(陰影),裏面放內容層,然後設置內容層相對定位(position:relative),並做適當的偏移(left:-3px;top:-3px;)。
由於相對定位會保留佔位空間,這樣就能巧妙地做出了一個可自適應大小的背景層(陰影)。
ps:博客園首頁也做了類似的效果,但貌似錯位有些嚴重哦。

仿右鍵菜單效果並不支持opera,因為opera並沒有類似oncontextmenu這樣的事件,要實現的話會很麻煩。
ps:如果想兼容opera的話,可以看看這篇文章“Opera下自定義右鍵菜單的研究 ”。
注意,在oncontextmenu事件中要用阻止默認事件(preventDefault)來取消默認菜單的顯示。
這個效果還做了一個不能選擇的處理,就是拖動它的內容時不會被選擇。
在ff中把樣式-moz-user-select設為none就可以了,而ie、chrome和safari通過在onselectstart返回false來實現相同的效果。
ps:css3有user-select樣式,但貌似還沒有瀏覽器支持。
當然,還有很多不完善的地方,這裏只是做個參考例子,就不深究了。

仿淘寶拼音索引菜單主要體現了a