[HTMLパーツ] メニュー項目サンプル #2「多階層対応」

2019年2月13日

HTML テクノロジー プログラミング

HTMLのそれぞれのパーツのサンプル集を掲載してくれているサイトは非常に便利ですが、それをコピペで使っているエンジニアは、僕は個人的に仕事をするとぬるすぎてイライラしてしまいます。 まあ、100歩譲って、コピペして効率アップをしてもいいとしても、自分でコーディングするというクリエータースタイルを持ったエンジニアの方が一緒に仕事をしていて質の良いモノを作り出せた経験があるので、ライブラリやコピペに頼るエンジニアはどうしてもいただけません。 そんなわけで、前回作ったホームページで使えるパーツのヘッダメニューを多階層対応版にバージョンアップしたので、デモとともにソースコードを載せておきます。 コピペするなり、コードをいじくって改良するなりしてお楽しみください。

デモ

See the Pen Menu-2 Multilayer by YugetaKoji (@geta1972) on CodePen.

ソースコード

<html lang="en"> <head> <meta charset="utf-8"> <title>HTML-parts Header</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="menu2.css"> <script src="menu2.js"></script> </head> <body> <div class="menu-bar"> <ul> <li> <a href="javascript:void(0)">Menu-1</a> <ul> <li><a href="#menu-1-1">Menu-1-1</a></li> <li><a href="#menu-1-2">Menu-1-2</a></li> </ul> </li> <li> <a href="#menu-2">Menu-2</a> </li> </ul> <ul> <li> <a href="javascript:void(0)">Menu-3</a> <ul> <li><a href="#menu-3-1">Menu-3-1</a></li> <li> <a href="#menu-3-2">Menu-3-2</a> <ul> <li><a href="#menu-3-2-1">Menu-3-2-1</a></li> <li><a href="#menu-3-2-1">Menu-3-2-2</a></li> <li> <a href="#menu-3-2-3">Menu-3-2-3</a> <ul> <li><a href="#menu-3-2-3-1">Menu-3-2-3-1</a></li> <li><a href="#menu-3-2-3-2">Menu-3-2-3-2</a></li> <li><a href="#menu-3-2-3-3">Menu-3-2-3-3</a></li> </ul> </li> </ul> </li> </ul> </li> </ul> </div> </body> </html> html,body{ margin : 0; padding : 0; border : 0; width : 100%; height : 100%; } *, *:before, *:after { -webkit-box-sizing : border-box; -moz-box-sizing : border-box; -o-box-sizing : border-box; -ms-box-sizing : border-box; box-sizing : border-box; } .menu-bar{ height : 60px; border-bottom : 1px solid #AAA; line-height : 60px; display : -webkit-flex; display : flex; -webkit-justify-content : center; justify-content : center; -webkit-align-items : center; align-items : center; } /* basic */ .menu-bar ul{ margin : 0; padding : 0; border : 0; } .menu-bar ul li{ margin : 0; padding : 0; border : 0; list-style : none; } .menu-bar ul li a{ display : inline-block; height : 100%; padding : 0 8px; cursor : pointer; text-decoration : none; color : #666; white-space : nowrap; } .menu-bar ul li a:hover{ /* background-color : #eee; */ color : #C66; } /* top-menu */ .menu-bar > ul{ display : flex; height : 100%; min-width : 100px; } .menu-bar > ul > li{ position : relative; } .menu-bar > ul > li > a{ min-width : 100px; text-align:center; } /* .menu-bar > ul > li > a:hover{ background-color:#eee; } */ .menu-bar ul > li[data-view="1"] > a{ background-color:#eee; } .menu-bar ul > li[data-view="0"] > a{ animation: menu-noactive 0.3s linear forwards; } .menu-bar ul > li.dropdown > a:after, .menu-bar ul > li[data-dropdown="1"] > a:after{ content: ""; display: inline-block; width: 0; height: 0; vertical-align: middle; border-top: 6px dashed; border-right: 4px solid transparent; border-left: 4px solid transparent; box-sizing: border-box; margin-left:4px; } /* second-menu */ .menu-bar > ul > li ul{ position : absolute; /* left : 0; */ line-height : 30px; border:1px solid #aaa; border-width:1px 1px 0; /* box-shadow : 2px 2px 8px rgba(0,0,0,0.3); */ opacity : 0.0; visibility : hidden; transform : scale(0); transform-origin:top; } .menu-bar > ul > li > ul{ /* position : absolute; */ left : 0; /* line-height : 30px; border:1px solid #aaa; border-width:1px 1px 0; box-shadow : 2px 2px 8px rgba(0,0,0,0.4); opacity : 0.0; visibility : hidden; transform : scale(0); transform-origin:top; */ } .menu-bar ul > li[data-view="1"] > ul{ visibility : visible; opacity : 1.0; } .menu-bar ul > li[data-view="0"] > ul{ animation: menu-close 0.3s linear forwards; } .menu-bar ul > li[data-view="1"] > ul{ animation: menu-list-open 0.1s linear forwards; } .menu-bar ul > li[data-view="0"] > ul{ animation: menu-list-close 0.1s linear forwards; } .menu-bar > ul > li > ul li{ position:relative; border-bottom : 1px solid #aaa; } .menu-bar > ul > li > ul li > a{ min-width : 120px; text-align:center; } /* third-menu */ .menu-bar > ul > li > ul > li ul{ position:absolute; top:0; left:100%; } .menu-bar > ul > li > ul > li ul > li{ position:relative; min-width:120px; } @keyframes menu-noactive{ 0%{ background-color:#eee; } 100%{ background-color:rgba(255,255,255,0.0); } } @keyframes menu-close{ 0%{ opacity:1.0; visibility:visible; } 100%{ opacity:0.0; visibility:hidden; } } @keyframes menu-list-open{ 0%{ transform : scaleY(0); } 100%{ transform : scaleY(1); } } @keyframes menu-list-close{ 0%{ transform : scaleY(1); } 100%{ transform : scaleY(0); } } ;(function(){ // ページ内にjsライブラリの読み込み var $$addScript = function(file){ var s = document.createElement("script"); s.src = file; document.body.appendChild(s); } // イベントライブラリ var $$event = function(target, mode, func){ //other Browser if (typeof target.addEventListener !== "undefined"){ target.addEventListener(mode, func, false); } else if(typeof target.attachEvent !== "undefined"){ target.attachEvent('on' + mode, function(){func.call(target , window.event)}); } }; var $$ = function(){ var state = document.readyState; if(state === "complete"){ this.start(); } else if(state === "interactive"){ $$event(window , "DOMContentLoaded" , (function(e){this.start(e)}).bind(this)); } else{ $$event(window , "load" , (function(e){this.start(e)}).bind(this)); } }; $$.prototype.start = function(){ // dropdown-set this.set_dropdown(); // dropdown-click this.set_dropdown_click(); // top-menu // var links = document.querySelectorAll(".menu-bar ul > li[data-dropdown='1'] > a"); var links = document.querySelectorAll(".menu-bar ul > li > a"); for(var i=0; i<links.length; i++){ $$event(links[i] , "click" , (function(e){this.set_top_menu_toggle(e)}).bind(this)); } // other-click $$event(window , "click" , (function(e){this.checkClick(e)}).bind(this)); }; $$.prototype.set_top_menu_toggle = function(e){ var currentTarget = e.currentTarget; if(!currentTarget || currentTarget.parentNode.getAttribute("data-dropdown") !== "1"){ this.set_top_menu_hidden(); } var top_menus = document.querySelectorAll(".menu-bar ul > li"); for(var i=0; i<top_menus.length; i++){ // 対象リンククリック if(top_menus[i] === currentTarget.parentNode){ if(top_menus[i].getAttribute("data-view") !== "1"){ this.set_top_menu_view(top_menus[i] , true); } else{ this.set_top_menu_view(top_menus[i] , false); } } // 対象の親階層は処理しない else if(this.checkHierarchy(currentTarget , top_menus[i])){ continue; } // 対象外リンククリック else{ this.set_top_menu_view(top_menus[i] , false); } } }; $$.prototype.checkHierarchy = function(currentTarget , menuElement){ while(!currentTarget.matches(".menu-bar")){ if(currentTarget === menuElement){ return true; } currentTarget = currentTarget.parentNode; } return false; }; // bool @ [true:active , false:unactive] $$.prototype.set_top_menu_view = function(linkElm , bool){ if(!linkElm){return;} if(bool === true){ linkElm.setAttribute("data-view" , "1"); } else{ linkElm.setAttribute("data-view" , "0"); } }; // all-link-hidden $$.prototype.set_top_menu_hidden = function(){ var listElm = document.querySelectorAll(".menu-bar ul > li"); for(var i=0; i<listElm.length; i++){ if(listElm[i].getAttribute("data-dropdown") !== "1"){continue;} listElm[i].setAttribute("data-view" , "0"); } }; // bool @ [true:view , false:hidden] $$.prototype.set_top_menu_active = function(dropdowns , currentTarget){ if(!dropdowns || !dropdowns.length){return;} for(var i=0; i<dropdowns.length; i++){ if(!currentTarget || dropdowns[i].getAttribute("data-active") === "1"){ dropdowns[i].setAttribute("data-active" , "0"); } else{ dropdowns[i].setAttribute("data-active" , "1"); } } }; // window-event $$.prototype.checkClick = function(e){ var target = e.target; if(target && target.matches(".menu-bar ul > li *")){ return; } if(!target || !target.matches(".menu-bar ul > li > a")){ this.set_top_menu_hidden(); } }; $$.prototype.set_dropdown = function(){ var top_menus = document.querySelectorAll(".menu-bar ul > li"); for(var i=0; i<top_menus.length; i++){ var listsElm = top_menus[i].querySelectorAll(":scope > ul"); if(!listsElm.length){continue;} top_menus[i].setAttribute("data-dropdown" , "1"); } }; $$.prototype.set_dropdown_click = function(){ var dropdowns = document.querySelectorAll(".menu-bar ul li[data-dropdown='1']"); for(var i=0; i<dropdowns.length; i++){ var a = dropdowns[i].querySelector(":scope > a"); a.href = "javascript:void(0)"; } }; new $$; })();

ちょこっと解説

前回のヘッダメニューは1階層のみでしたが、今回は多階層という事で、MacOSの画面上部メニューを意識して作ってみました。 今どきのホームページ用にサイズを大きめにしていますが、サイズと色を合わせるだけで全く同じ表現ができると思いますよ。 そして今回の改修ポイントは、上部階層もネストが深くなった階層でも、同じ関数で実行されるという点と、リスト階層の仕組みをどの階層でも同じにして「シンプル」を意識して行いました。 dropdownのクリックイベント埋め込みを ".menu-bar > ul ..."となっていたのを".menu-bar ul ..."と変更して、下層全てに対応させるだけなのですが、全ての階層を同じ構造にするからこうした事が可能になるんですね。 そして、ページ読み込み直後に、小階層があるリンクポイントに"data-dropdown='1'"という属性値を埋め込んでいるんですが、ここも仕組み化さえできてしまえば非常に効率的に行えてcss連動でページ自体も動作が軽くなります。 最も注意した点は、第1階層は、常に表示されているメニューで第2階層は、下に表示されるのですが、それ以降の階層はメニューの右側に表示されるように、MacOSを意識して作っています。 最後にバグではないのですが、実現できていない機能として、画面のはみ出し対応は何も処理をしていません。 ページのスクロールバーが表示されてしまいます。 MacOSではどうなっているかというと、画面右端いっぱいになった場合は、右サイド表示が左サイド表示に切り替わります。 あと、小階層のメニュー数が多すぎた場合は、メニューの位置が画面サイズいっぱいに入るポジションに移動して、内部がスクロール対応するようになりますが、これも全く対応させていません。 どうしても必要になったら、またバージョンアップ版を作るとしましょう。 今回はここまで!

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。