//取得 Menu 及 Dropdown 的 DOM var $menu = $(".menu"); var $dropdown = $(".dropdown"); // 偵測 Menu 及 Dropdown 的 mouseenter 和 mouseleave 事件 var $menuMouseLeaveEvent = Rx.Observable.fromEvent($menu, "mouseleave"); var $dropdownMouseLeaveEvent = Rx.Observable.fromEvent($dropdown, "mouseleave"); var $menuMouseEnterEvent = Rx.Observable.fromEvent($menu, "mouseenter"); var $dropdownMouseEnterEvent = Rx.Observable.fromEvent($dropdown, "mouseenter"); //將 Menu 的 mouseenter 和 mouseleave 事件轉成對應的資訊物件 //(這裡儲存了滑鼠有無在 Menu 上面、滑鼠滑進 (或滑出) 了哪個 Menu DOM 的資訊), //並將 mouseenter 和 mouseleave 事件 merge 起來,只要看最後的狀態就好 var isMouseOnMenuEvent = Rx.Observable.merge( $menuMouseEnterEvent.map(function(e) { return { isHovered : true, hoveredDom : e.currentTarget }; }), $menuMouseLeaveEvent.map(function(e) { return { isHovered : false, hoveredDom : e.currentTarget }; })); //將 Dropdown 的 mouseenter 和 mouseleave 事件轉成對應的資訊物件 //(這裡儲存了滑鼠有無在 Dropdown 上面、滑鼠滑進 (或滑出) 了哪個 Dropdown DOM 的資訊) //並將 mouseenter 和 mouseleave 事件 merge 起來,只要看最後的狀態就好 var isMouseOnDropDownEvent = Rx.Observable.merge( $dropdownMouseEnterEvent.map(function() { return { isHovered : true }; }), $dropdownMouseLeaveEvent.map(function() { return { isHovered :false }; })).startWith({ // 因為 RxJS 的 combineLatest() 不能 combine NULL 的事件 (會出錯), // 而一開始 Dropdown 沒有被打開時,不會有 Dropdown 的 mouseenter 和 mouseleave 事件, // 所以在這邊用 startWith 來手動增加一個 event 在最前面 isHovered :false }); // 將 isMouseOnMenuEvent 和 isMouseOnDropDownEvent 兩個事件流 Combine 在一起, // 只看各自的最後一個狀態。 // 因為可能存在在短時間中,事件還沒有被觸發的情況,在這之中的狀態會不準而不對, // 例如: // 滑鼠滑出了 Menu,要準備滑進 Dropdown 但還沒滑進的這一段時間中, // 此時 isOnMenu = false,但 isDropdown 也等於 false,這時用這組狀態判斷 Dropdown 的開關就會不正確, // 所以在這使用了 debounceTime() 來延長事件偵測的時間,例如滑鼠移出 Menu 後,等 100 毫秒看有沒有其他事件再決定送出事件 var mouseHoverStatus = Rx.Observable.combineLatest(isMouseOnMenuEvent, isMouseOnDropDownEvent) .debounceTime(100); // 在註冊事件 (subscribe) 裡判斷狀態,決定是否要開關 Dropdown、和要開關哪個 Dropdown mouseHoverStatus.subscribe(function(e){ if (e[0].isHovered || e[1].isHovered){ var targetDropdown = $("#" + $(e[0].hoveredDom).attr("targetDropdown")); $dropdown.not(targetDropdown).stop(true).animate({ height: 0 }); targetDropdown.stop(true) .animate({ height: 300 }); }else { $dropdown.stop(true).animate({ height: 0 }); } });
<div class="wrapper"> <div class="menu" targetDropdown="dropdown1"> Menu1 <div class="menu_child"> Menu1 Child </div> </div> <div class="menu" targetDropdown="dropdown2"> Menu2 <div class="menu_child"> Menu2 Child </div> </div> <div class="dropdown" id="dropdown1"> Dropdown1 <div class="dropdown_child"> Dropdown1 Child </div> </div> <div class="dropdown" id="dropdown2"> Dropdown2 <div class="dropdown_child"> Dropdown2 Child </div> </div> </div>
.wrapper { width: 500px; margin: 0 auto; } .menu { background-color: yellow; display: inline-block; width: 45%; margin: 0; } .menu_child { background-color: #00ffd0; width: 30%; margin: 0 auto; } .dropdown { background-color: green; width: 80%; height: 0px; overflow: hidden; position: absolute; } .dropdown_child { background-color: #bc0eda; width: 300px; margin: 0 auto; }