実現したいこと
幅広画面では横並びのドロップダウンメニューが、
幅狭画面では同じ幅で 1 つずつ縦に並ぶ。(折り返さない)
前提
以下の処理を追加しようとしています。(他解決策でも)
- 各要素の幅を取得する
- 最長文字列の要素幅を特定する
- 画面幅内に全要素を横並びできない場合は、最長文字列の要素幅を全兄弟要素に設定する
発生している問題
JavaScript で生成している Html 要素の幅を取得できない。
該当のソースコード
Html
1<div id="suiteBarDelta"> 2</div>
JavaScript
1let myMockListData = [ 2 { 3 URL: "/siteA.aspx", 4 URLNAME: "Home", 5 dropDown: "no", 6 id: "home" 7 }, 8 { URL: "/siteF.aspx", URLNAME: "Site F", dropDown: "yes", id: "Training" }, 9 { URL: "/siteF.aspx", URLNAME: "Site F", dropDown: "MegaMenu", id: "Training" }, 10 { 11 URL: "/siteA.aspx", 12 URLNAME: "Site A", 13 dropDown: "yes", 14 id: "SiteA" 15 } 16]; 17 18var subList = [ 19 { subURL: "/site/a", id: "SiteA", URLNAME: "Site A" }, 20 { subURL: "/site/a", id: "SiteA", URLNAME: "Site A" }, 21 { subURL: "/site/a", id: "SiteA", URLNAME: "Site A" } 22]; 23 24var megaMenuCategory = [ 25 { id: "Training", category: "DT", url: "www.gmail.com" }, 26 { id: "Training", category: "画面幅が小さいと縦並び、各アイテムの幅が揃わない", url: "www.gmail.com" }, 27 { id: "Training", category: "IT", url: "www.gmail.com" } 28]; 29 30var categoryMenu = [ 31 { category: "DT", menuItem: "DT-BT", menuUrl: "www.gmail.com" }, 32 { category: "DT", menuItem: "DT-SE", menuUrl: "www.gmail.com" }, 33 { category: "DT", menuItem: "DT-PI", menuUrl: "www.gmail.com" }, 34 { category: "画面幅が小さいと縦並び、各アイテムの幅が揃わない", menuItem: "Human Resources", menuUrl: "www.gmail.com" }, 35 { category: "IT", menuItem: "IT-IO", menuUrl: "www.gmail.com" }, 36 { category: "IT", menuItem: "IT-CS", menuUrl: "www.gmail.com" } 37]; 38 39createNavigation(myMockListData); 40 41function myFunction() { 42 var x = document.getElementById("myTopnav"); 43 if (x.className === "topnav") { 44 x.className += " responsive"; 45 } else { 46 x.className = "topnav"; 47 } 48} 49 50function createNavigation(navData) { 51 var headerElement = document.getElementById("suiteBarDelta"); 52 headerElement.insertAdjacentHTML('afterend', '<div class="topnav" id="myTopnav"><a href="javascript:void(0);" style="font-size:15px;" class="icon" onclick="myFunction()">☰</a></div>'); 53 var topNav = document.getElementById("myTopnav"); 54 for (var x = 0; x < navData.length; x++) { 55 if (navData[x].dropDown === "no") { 56 var aLink = _createEl("a"); 57 aLink.href = navData[x].URL; 58 aLink.appendChild(document.createTextNode(navData[x].URLNAME)); 59 topNav.appendChild(aLink); 60 } else if (navData[x].dropDown === "yes") { 61 var buildSubNavigation; 62 buildSubNavigation = buildSubNavBar(navData[x].id); 63 topNav.appendChild(buildSubNavigation); 64 } else { 65 //build megamenu 66 var buildSubNavigation; 67 buildDirectorateNav = buildDirectorateMegaMenu(navData[x].id); 68 } 69 } 70} 71 72function buildDirectorateMegaMenu(navDataID) { 73 var buildNav; 74 var topNav = document.getElementById("myTopnav"); 75 buildNav = buildSubNavBar(navDataID, "megaMenu"); //Build mega menu and attach in buildSubNavBar() 76 topNav.appendChild(buildNav); 77} 78 79function buildSubNavBar(subNavID, isDirectorate) { 80 //create div and add dropdown class 81 var ddDiv = _createEl("div"); 82 if(isDirectorate === "megaMenu"){ 83 ddDiv.classList.add("Mdropdown"); 84 }else{ 85 ddDiv.classList.add("dropdown"); 86 } 87 //create button and add text 88 var btn = _createEl("button"); 89 if(isDirectorate === "megaMenu"){ 90 btn.classList.add("Mdropbtn"); 91 }else{ 92 btn.classList.add("dropbtn"); 93 } 94 //append the text to the button 95 btn.appendChild(document.createTextNode(subNavID)); 96 97 //create i tag and add "fa fa-caret-down" classes 98 var itag = _createEl("i"); 99 itag.classList.add("fa"); 100 itag.classList.add("fa-caret-down"); 101 itag.classList.add("fa-fw"); 102 btn.appendChild(itag); 103 ddDiv.appendChild(btn); 104 105 var ddContent = _createEl("div"); 106 ddContent.classList.add("dropdown-content"); 107 for (var i = 0; i < subList.length; i++) { 108 if (subList[i].id === subNavID && subList[i].id !== "Training") { 109 var a = _createEl("a"); 110 a.href = subList[i].subURL; 111 a.appendChild(document.createTextNode(subList[i].URLNAME)); 112 ddContent.appendChild(a); 113 } 114 } 115 116 if(isDirectorate !== "megaMenu"){ 117 ddContent = createContent(subNavID, ddContent, false, ddDiv); 118 119 } else { 120 121 //create div and add dropdown class 122 var megaDivDropDown = _createEl("div"); 123 megaDivDropDown.classList.add("dropdown"); 124 125 var megaBtn = _createEl("button"); 126 megaBtn.classList.add("dropbtn"); 127 128 megaDivDropDown.appendChild(megaBtn); 129 var megaI = _createEl("i"); 130 megaI.classList.add("fa"); 131 megaI.classList.add("fa-caret-down"); 132 133 megaBtn.appendChild(megaI); 134 135 var megaDDivContent = _createEl("div"); 136 megaDDivContent.classList.add("Mdropdown-content"); 137 138 ddDiv = createContent(subNavID, megaDDivContent, true, ddDiv); 139 } 140 ddDiv.appendChild(ddContent); 141 return ddDiv; 142} 143function _createEl(el) { 144 return document.createElement(el); 145} 146 147function createContent(subNavID, content, bMegaMenu, ddDiv) { 148 149 var iWidthMax = 0; 150 var iWidthAll = 0; 151 //Loop through categories & sub-categories items 152 for (var i = 0; i < megaMenuCategory.length; i++) { 153 if (megaMenuCategory[i].id === subNavID) { 154 155 var headerDiv = _createEl("div"); 156 headerDiv.classList.add("header"); 157 158 var h2 = _createEl("h2"); 159 h2.appendChild(document.createTextNode(megaMenuCategory[i].category)); 160 headerDiv.appendChild(h2); 161 162 // 幅取得箇所案 1 : h2 追加直後 163 iWidthAll = iWidthAll + headerDiv.style.width; 164 if(iWidthMax < headerDiv.clientWidth) { 165 iWidthMax = headerDiv.clientWidth; 166 } 167 168 var divCol = _createEl("div"); 169 divCol.classList.add("column"); 170 var colHr = _createEl("h3"); 171 colHr.appendChild(document.createTextNode("")); 172 divCol.appendChild(colHr); 173 for (var x = 0; x < categoryMenu.length; x++) { 174 if (megaMenuCategory[i].category === categoryMenu[x].category) { 175 var colAnchor = _createEl("a"); 176 colAnchor.href = categoryMenu[x].menuUrl; 177 colAnchor.appendChild(document.createTextNode(categoryMenu[x].menuItem)); 178 179 divCol.appendChild(colAnchor); 180 headerDiv.appendChild(divCol); 181 content.appendChild(headerDiv); 182 if(bMegaMenu) { 183 ddDiv.appendChild(content); 184 } 185 } 186 } 187 188 // 幅取得箇所案 2 : 全行追加後に最後の h2 要素を取得する 189 var nodes = document.querySelectorAll('.header>h2'); 190 var last = nodes[nodes.length- 1]; 191 } 192 } 193/* 194 // 画面幅が全要素の和より小さい場合、最大要素幅にする 195 if (window.innerWidth < iWidthAll) { 196 var childElems = content.children; 197 for (var i = 0; i < childElems.length; i++) { 198 childElems[i].style.width = iWidthMax; 199 } 200 } 201*/ 202 203 if(!bMegaMenu) { 204 return content; 205 } else { 206 return ddDiv; 207 } 208} 209
CSS
1body {margin:0;} 2 3.topnav { 4 display: flex; 5 overflow: hidden; 6 background-color: #5A7FA2; 7} 8 9.topnav a { 10 display: block; 11 color: #f2f2f2; 12 text-align: center; 13 padding: 14px 16px; 14 text-decoration: none; 15 font-size: 17px; 16} 17 18.active { 19 background-color: #4CAF50; 20 color: white; 21} 22 23.topnav .icon { 24 display: none; 25} 26 27.dropdown { 28 overflow: hidden; 29} 30 31.dropdown .dropbtn { 32 font-size: 17px; 33 border: none; 34 outline: none; 35 color: white; 36 padding: 14px 16px; 37 background-color: inherit; 38 font-family: inherit; 39 margin: 0; 40} 41 42.dropdown-content { 43 display: none; 44 position: absolute; 45 background-color: #f9f9f9; 46 min-width: 160px; 47 left: auto; 48 box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 49 z-index: 1; 50} 51 52.dropdown-content a { 53 float: none; 54 color: black; 55 padding: 12px 16px; 56 text-decoration: none; 57 display: block; 58 text-align: left; 59} 60 61.topnav a:hover, .dropdown:hover .dropbtn { 62 background-color: #555; 63} 64 65.dropdown-content a:hover { 66 background-color: #ddd; 67 color: black; 68} 69 70.dropdown:hover .dropdown-content { 71 display: flex; 72 flex-wrap: wrap; 73} 74 75.dropdown:last-child .dropdown-content { 76 flex-direction: column; 77} 78 79/*MEGA-MENU*/ 80* { 81 box-sizing: border-box; 82} 83 84.Mnavbar { 85 overflow: hidden; 86 background-color: #333; 87 font-family: Arial, Helvetica, sans-serif; 88} 89 90.Mnavbar a { 91 font-size: 16px; 92 color: white; 93 text-align: center; 94 padding: 14px 16px; 95 text-decoration: none; 96} 97 98.Mdropdown { 99 overflow: hidden; 100} 101 102.Mdropdown .Mdropbtn { 103 font-size: 16px; 104 border: none; 105 outline: none; 106 color: white; 107 padding: 14px 16px; 108 background-color: inherit; 109 font: inherit; 110 margin: 0; 111} 112 113.Mnavbar a:hover, .Mdropdown:hover .Mdropbtn { 114 background-color: #555; 115} 116 117.Mdropdown-content { 118 display: none; 119 position: absolute; 120 background-color: #f9f9f9; 121 left: 0; 122 box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 123 z-index: 1; 124} 125 126.header { 127 float: left; 128 background: #555; 129 padding: 16px; 130 color: white; 131 overflow: hidden; 132 flex-grow: 1; 133} 134 135.header h2 { 136 padding: 16px; 137 color: white; 138} 139 140.Mdropdown:hover .Mdropdown-content { 141 display: flex; 142 flex-wrap: wrap; 143} 144 145.column { 146 margin: 0 -16px -16px; 147 padding: 10px; 148 background-color: #ccc; 149 height: 250px; 150} 151 152.column a { 153 float: none; 154 color: black; 155 padding: 16px; 156 text-decoration: none; 157 display: block; 158 text-align: left; 159} 160 161.column a:hover { 162 background-color: #ddd; 163} 164 165.header:hover h2 { 166 color: orange; 167}
試したこと
- style.width
- clientWidth
「同じ質問」かどうかにつきまして
■ 最初の質問から派生し、解決したい全 3 件の質問があり、
1. 幅狭画面で縦並びするメニュー幅を揃える (必須技術は CSS)
2. JavaScript 関数化 (必須技術は JavaScript)
3. Html 要素幅を取得する (必須技術は JavaScript)
違いは以下になります。
1 件目は実現したいこと全容であり、幅広い技術が必要となり、解決したら、
今後ドロップダウン/メガメニューを作成される方に知見となる内容、
2 件目、3 件目は、1 件目を模索する中で、
1 つの仮定に基づいて進めるにあたって発生してきた小さな質問であり、
必要な技術領域も少なく、1 件目とは回答できる人も異なるのではないかと考え、
最終的に 1 件目の解決につながるように、分割しています。
したがって、投稿しているソースコードも「似て非なり」であり、
注目してもらいたい部分は、
1 件目は全ソースコード、
2 件目は JavaScipt 関数化部分、
3 件目は JavaScipt Html 要素取得部分
というように、異なってきます。
--------------------------------------------------------------------------------
■ 3 件目に、1 件目と同じ 2 枚の画像
- 実現したいこと
- 発生している問題
を付加した理由は、
JavaScript コードを読み解くにあたって、
画面要素の構成があると、可読性が高まると考え、
この 3 件目の質問には背景として、画面の画像や他のコードも付加しています。
また、1 件目の回答者からも、
「再投稿するほうが解決に近づきやすい」と
助言いただいたことにより、分割投稿に至りました。
すみません。
質問文に書いてある仕様が複雑でかなり理解しづらいです。
確認❶
とりあえずメニュー階層だけ解読したのですが、これで合っているでしょうか?
・親メニューは4つ(Home, Traning, Traning, Site A)ある。
・Training の下に子メニュー、更にその下に孫メニューがある
・Training が2つ存在するが、どちらもメニュー内容は同じ
------------------------------------------
Home
Training
│
├ DT
│ ├ DT-BT
│ ├ DT-SE
│ └ DT-PI
│
├ 画面幅が...揃わない
│ └ Human Resources
│
└ IT
├ IT-IO
└ IT-CS
Training
( 内容は上と同じ。こちらはメガメニューで表示 )
Site A
├ Site A
├ Site A
└ Site A
------------------------------------------
確認❷
・1個目の「Traning」は通常のドロップダウン
・2個目の「Traning」はメガメニュー
と認識しています。
添付画像だと1個目の「Traning」が展開されていますが、メガメニューではなく通常のドロップダウンとして表示した場合に上手くいかないということでしょうか?
> 実現したいこと
> 幅広画面では横並びのドロップダウンメニューが、
> 幅狭画面では同じ幅で 1 つずつ縦に並ぶ。(折り返さない)
とありますが、通常のドロップダウンでも、幅広画面のときメニューを横並びにするのですか?
確認❸
画像が3枚添付されていますが、すべて「幅狭画面」のときの画像でしょうか?
確認❹
「画面幅が...揃わない」の文字色がオレンジになっていますが、これは :hover の影響ですか?
「Human Resources」の背景色が明るいのも、同じく :hover の影響ですか?
これら2つが何か特別なメニューというわけでは無いと考えて良いですか?
試してみましたが、clientWidth で問題なく取得できるようでした。
shinoharat さま、ご検討ありがとうございます。
確認❶
はい、ご認識のとおりになります。
実現したいこと全体の仕様は以下の前提に記述しています。
https://teratail.com/questions/v2xhan6ck1jc82
確認❷
メガメニュー、通常のドロップダウン、どちらも
画面幅によっては、縦横混在の並び、バラバラの幅になります。
はい、現状の詳細設計では、通常のドロップダウンでも、
幅広画面での横並びを準備したいと考えています。
これら機能につきましても、以下の前提に記述しています。
https://teratail.com/questions/v2xhan6ck1jc82
確認❸
はい、3 枚の画像は、すべて「幅狭画面」の時の画像になります。
確認❹
はい、すべて hover の影響で、
特別なメニューのわけではありません。
なにとぞどうぞよろしくお願いいたします。
Lhankor_Mhy さま、ご確認どうもありがとうございます。
質問に載せました、JavaScript コード行数 164 の直前で
headerDiv.clientWidth をコンソール出力すると、ゼロになります。
どこで、どの要素に対して、どのように記述したら、
clientWidth を問題なく取得できたか、教示いただけませんでしょうか?
なにとぞどうそよろしくお願いいたします。
display: none の状態だと0になると思います。
https://cly7796.net/blog/javascript/can-not-get-size-when-elements-of-the-display-none/
Lhankor_Mhy さま、コメントありがとうございます。紹介いただきましたように、
質問に載せました、JavaScript コード行数 188 の直前で
以下の記述で明示的に display: block を指定してみても、
コンソール出力で clientWidth はゼロになります。
if(bMegaMenu) {
var listHeader = document.querySelectorAll('.header h2');
var eleHeader = listHeader[1];
eleHeader.style.display='block';
console.log("document.querySelectorAll('.header h2').textContent : " + eleHeader.textContent);
console.log("document.querySelectorAll('.header h2').clientWidth : " + eleHeader.clientWidth);
eleHeader.style.display='none';
}
textContent は取得できていますが、要素の取得に問題があるのでしょうか?
なにか、お気づきの点はありますでしょうか?
祖先要素の .dropdown-content が display:none になっているのではないでしょうか。
display:none は子孫要素ごと非表示にします。
Lhankor_Mhy さま、ご返信ありがとうございます。
祖先要素は .Mdropdown-content で display:none になっているため、
要素取得できた .header と同様の記述で
.Mdropdown-content を取得すると、配列要素数がゼロであり、
配列要素を参照すると undefined になってしまいます。
// var listContent = document.querySelectorAll('.Mdropdown-content');
var listContent = document.getElementsByClassName("Mdropdown-content");
var eleContent = listContent[0];
console.log("document.querySelectorAll('.Mdropdown-content').length : " + listContent.length);
console.log("document.querySelectorAll('.Mdropdown-content')[0] : " + eleContent);
どのようにしたら、
.Mdropdown-content や .dropdown-content を取得できるものでしょうか?
引数 content の中に入ってそうな気がしますけど。
といいますか、この段階では、document に挿入されてなくないですか?
topNav.appendChild(buildSubNavigation) が実行されてから取得してみてはどうでしょうか?
Lhankor_Mhy さま、何度も検討いただきましてどうもありがとうございます。
応答遅れまして申し訳ございません。
たしかに、topNav.appendChild(
以降である必要があるため、
createContent 関数内では、
最後の要素「.Mdropdown-content」では undefined になり、
その前の要素「.dropdown-content」については幅を取得できることを確認できました。
質問に載せました、JavaScript コード行数 76、
buildDirectorateMegaMenu 関数内の topNav.appendChild(buildNav);
の直後に、以下の記述により、一時的に display: block へ変更し、要素幅を取得後、 display: none へ戻すと、
var iWidthMax = 0;
var iWidthAll = 0;
listContent = document.getElementsByClassName("Mdropdown-content");
eleContentLast = listContent[listContent.length - 1];
eleContentLast.style.display = 'block';
var listHeader = document.querySelectorAll('.Mdropdown-content .header h2');
var eleHeader;
var eleColumnLast;
for (var iHeader = 0; iHeader < listHeader.length; iHeader++) {
eleHeader = listHeader[iHeader];
iWidthAll = iWidthAll + eleHeader.clientWidth;
if(iWidthMax < eleHeader.clientWidth) {
iWidthMax = eleHeader.clientWidth;
}
}
eleContentLast.style.display = 'none';
// 画面幅が全要素の和より小さい場合、最大要素幅にする
if (window.innerWidth < iWidthAll) {
var childElems = eleContentLast.children;
for (var i = 0; i < childElems.length; i++) {
childElems[i].style.width = iWidthMax;
}
}
hover でメニュー表示されなくなってしまいます。
JavaScript で以下のようにホバーイベントを追加すると、
var targetsMdropdown = document.getElementsByClassName("Mdropdown");
var targetMdropdown = targetsMdropdown[targetsMdropdown.length - 1];
var popupMcontents = document.getElementsByClassName("Mdropdown-content");
var popupMcontent = popupMcontents[popupMcontents.length - 1];
// ドロップダウンに hover した時
targetMdropdown.addEventListener('mouseover', () => {
popupMcontent.style.display = 'flex';
}, false);
// ドロップダウンから離れた時
targetMdropdown.addEventListener('mouseleave', () => {
popupMcontent.style.display = 'none';
}, false);
狙い通り、Mdropdown-content については幅を揃えることができました。
ただし、複数要素が存在する dropdown-content について、
ループ処理してみると、最後の要素が常に表示されてしまったり、
その他の要素が表示されない状態になってしまいます。
この質問でこれらの問題解明を継続することは分かりにくいと思いますので、新たな質問として分割投稿し、
この質問については Html 要素幅取得の解決のみで close しようと思います。
Lhankor_Mhy さまに、どこで、どの要素に対して、とコメントいただいたことで
要素幅を取得することができました。「助かりました」を示すために、
いただいたコメントで回答いただければ、ベストアンサーに設定したいと思います。
よろしくお願いいたします。
進展あったようで何よりです。
自己解決の処理をしていただけますと助かります。
https://teratail.com/help#resolve-myself
Lhankor_Mhy さま、
私を思いやる、優しいお言葉、ありがとうございます。
では、教示いただたきました自己解決にいたします。
周りに相談できる開発者がいない環境の中で、自分ではどうしようもなく、
半ば諦めていた中での、ダメ元での投稿でしたので、たいへん助かりました。
私の問題解決のために貴重なお時間とお気持ちを、
助けていただきましてどうもありがとうございました。

回答1件
あなたの回答
tips
プレビュー