admin管理员组文章数量:1122847
I'm trying to calculate if a horizontal menu has space for all menu items, and if not, wrap overflowing items inside a dropdown.
Here is what I have. It works - almost - because sometimes is doesn't calculate correctly. Try resizing the window, you'll see something like this: (on this figure the elements doesn't stay in there original order)
const nav = document.querySelector(".navbar");
const contentBar = nav.querySelector(".navbar-nav");
const navItems = document.querySelectorAll(".navbar-nav > li:not(.grouped)");
const allItems = contentBar.querySelectorAll('li');
const dropdown = nav.querySelector(".grouped-content");
const more = nav.querySelector(".grouped");
const update = () => {
// Show all items for exact calculation
allItems.forEach((item) => {
item.classList.remove('d-none');
});
let avaliableWidth = nav.offsetWidth;
let stopWidth = more.offsetWidth;
let subitems = [];
dropdown.innerHTML = "";
navItems.forEach((item, i) => {
// Calculate if menu items are wider than navbar
if (avaliableWidth > stopWidth + item.offsetWidth) {
stopWidth += item.offsetWidth;
} else {
// Not enough space
let li = document.createElement("li");
li.innerHTML = item.innerHTML;
// Append overflowing menu items to dropdown
dropdown.appendChild(li);
// Hide items that won't fit in menu
item.classList.add('d-none');
// Count items in dropdown
subitems.push(i);
}
})
// Hide "More" item if it has no children
if (!subitems.length) {
more.classList.add('d-none');
}
}
update();
window.addEventListener("resize", update);
.nav-link {
white-space: nowrap;
max-width: 100px;
text-overflow: ellipsis;
overflow: hidden;
}
<link href="/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<div class="container">
<nav class="navbar navbar-expand-sm">
<ul class="navbar-nav">
<li class="nav-item"><a href="#" class="nav-link">Item 1</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 22</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 333</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 4444</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 5555555</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 66</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 7777777777777</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 8</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 999999</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 10101010</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 1111</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 12121212</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 131313131313</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 1414</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 15</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 16161616</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 1717</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 18181818181818</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 191919</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 20</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 212121</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 2222</a></li>
<li class="nav-item dropdown grouped">
<a href="#" class="nav-link dropdown-toggle" aria-haspopup="true" aria-expanded="false" role="button" data-bs-toggle="dropdown" data-bs-display="static" data-bs-auto-close="outside">More</a>
<ul class="dropdown-menu grouped-content"></ul>
</li>
</ul>
</nav>
</div>
<script src="/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
I'm trying to calculate if a horizontal menu has space for all menu items, and if not, wrap overflowing items inside a dropdown.
Here is what I have. It works - almost - because sometimes is doesn't calculate correctly. Try resizing the window, you'll see something like this: (on this figure the elements doesn't stay in there original order)
const nav = document.querySelector(".navbar");
const contentBar = nav.querySelector(".navbar-nav");
const navItems = document.querySelectorAll(".navbar-nav > li:not(.grouped)");
const allItems = contentBar.querySelectorAll('li');
const dropdown = nav.querySelector(".grouped-content");
const more = nav.querySelector(".grouped");
const update = () => {
// Show all items for exact calculation
allItems.forEach((item) => {
item.classList.remove('d-none');
});
let avaliableWidth = nav.offsetWidth;
let stopWidth = more.offsetWidth;
let subitems = [];
dropdown.innerHTML = "";
navItems.forEach((item, i) => {
// Calculate if menu items are wider than navbar
if (avaliableWidth > stopWidth + item.offsetWidth) {
stopWidth += item.offsetWidth;
} else {
// Not enough space
let li = document.createElement("li");
li.innerHTML = item.innerHTML;
// Append overflowing menu items to dropdown
dropdown.appendChild(li);
// Hide items that won't fit in menu
item.classList.add('d-none');
// Count items in dropdown
subitems.push(i);
}
})
// Hide "More" item if it has no children
if (!subitems.length) {
more.classList.add('d-none');
}
}
update();
window.addEventListener("resize", update);
.nav-link {
white-space: nowrap;
max-width: 100px;
text-overflow: ellipsis;
overflow: hidden;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<div class="container">
<nav class="navbar navbar-expand-sm">
<ul class="navbar-nav">
<li class="nav-item"><a href="#" class="nav-link">Item 1</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 22</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 333</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 4444</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 5555555</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 66</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 7777777777777</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 8</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 999999</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 10101010</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 1111</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 12121212</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 131313131313</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 1414</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 15</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 16161616</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 1717</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 18181818181818</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 191919</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 20</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 212121</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 2222</a></li>
<li class="nav-item dropdown grouped">
<a href="#" class="nav-link dropdown-toggle" aria-haspopup="true" aria-expanded="false" role="button" data-bs-toggle="dropdown" data-bs-display="static" data-bs-auto-close="outside">More</a>
<ul class="dropdown-menu grouped-content"></ul>
</li>
</ul>
</nav>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
What am I missing?
Share Improve this question edited Dec 11, 2024 at 11:39 Mark Rotteveel 109k225 gold badges155 silver badges218 bronze badges asked Nov 30, 2024 at 14:34 MeekMeek 3,3549 gold badges42 silver badges71 bronze badges 2- Check the image in the top of the post - the order of the items is not correct. I marked the wrong item with a red box. You might have to resize your window. – Meek Commented Nov 30, 2024 at 15:23
- 1 you could have simply written that your elements are out of order, and used clearer labels in your LIs – Mister Jojo Commented Nov 30, 2024 at 15:54
1 Answer
Reset to default 2your error :
if (avaliableWidth > stopWidth + item.offsetWidth) {
change to (look at parenthesis)
if (avaliableWidth > (stopWidth + item.offsetWidth)) {
This all about Operator precedence
A cleanest way to do this...
(_this will also work with bootstrap media queries_)First JS Code version:
const
nav = document.querySelector('.navbar') // nav
, contentBar = nav.querySelector('.navbar-nav') // nav > ul
, moreGroup = nav.querySelector('.navbar-nav > li.grouped')
, navItems = [...nav.querySelectorAll('.navbar-nav > li:not(.grouped)')]
, moreList = moreGroup.querySelector('ul.grouped-content')
;
sizingNav();
window.addEventListener('resize', sizingNav);
function sizingNav()
{
let avaliable_width = nav.offsetWidth;
while (moreList.lastElementChild) // clear the "More" List
moreList.removeChild(moreList.lastElementChild);
moreGroup.classList.add('d-none');
navItems.forEach( item => item.classList.remove('d-none') );
for (let i = navItems.length; i-- > 0;) // right to left movin to set
{ // elements into the 'More' list
if ( contentBar.offsetWidth > avaliable_width )
{
let li = document.createElement('li');
li.innerHTML = navItems[i].innerHTML;
moreList.prepend(li);
navItems[i].classList.add('d-none');
moreGroup.classList.remove('d-none');
}
else break; // no needs to go further...
}
}
const
nav = document.querySelector('.navbar') // nav
, contentBar = nav.querySelector('.navbar-nav') // nav > ul
, moreGroup = nav.querySelector('.navbar-nav > li.grouped')
, navItems = [...nav.querySelectorAll('.navbar-nav > li:not(.grouped)')]
, moreList = moreGroup.querySelector('ul.grouped-content')
;
sizingNav();
window.addEventListener('resize', sizingNav);
function sizingNav()
{
let avaliable_width = nav.offsetWidth;
while (moreList.lastElementChild) // clear the "More" List
moreList.removeChild(moreList.lastElementChild);
moreGroup.classList.add('d-none');
navItems.forEach( item => item.classList.remove('d-none') );
for (let i = navItems.length; i-- > 0;) // right to left movin to set
{ // elements into the 'More' list
if ( contentBar.offsetWidth > avaliable_width )
{
let li = document.createElement('li');
li.innerHTML = navItems[i].innerHTML;
moreList.prepend(li);
navItems[i].classList.add('d-none');
moreGroup.classList.remove('d-none');
}
else break; // no needs to go further...
}
}
.nav-link {
white-space : nowrap;
max-width : 100px;
text-overflow : ellipsis;
overflow : hidden;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<div class="container">
<nav class="navbar navbar-expand-sm">
<ul class="navbar-nav">
<li class="nav-item"><a href="#" class="nav-link">Item 1</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 2-2</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 3-33</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 4-444</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 5-555555</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 6-6</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 7-777777777777</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 8</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 9-99999</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 10+101010</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 11+11</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 12+121212</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 13+1313131313</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 14+14</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 15+</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 16+161616</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 17+17</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 18+181818181818</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 19+1919</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 20+</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 21+2121</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 22+22</a></li>
<li class="nav-item dropdown grouped">
<a href="#" class="nav-link dropdown-toggle" aria-haspopup="true" aria-expanded="false" role="button" data-bs-toggle="dropdown" data-bs-display="static" data-bs-auto-close="outside">More</a>
<ul class="grouped-content"></ul>
</li>
</ul>
</nav>
</div>
[EDIT]
I'm a little surprised that this post can please, while this question is down-voted.My first answer here was just made to show the OP a better approach than his calculation by successive additions.
But as my answer visibly arouses interest, I prefer to leave "to prosperity" this new answer, which avoids create/delete
LI+insertHTML
, when it is enough just to readdress them directly in the right place.
Please, give a positive score to the OP. This question was really badly asked at the beginning, but it has been corrected; and this one may have interest on SO.
PS: As for the use of the d-none
class, it is completely useless since the arrival of the 2 CSS pseudo-classes: :has and :empty...
see css addition
LI.grouped:has( UL.grouped-content:empty ) {
display : none;
}
New version:
const
nav = document.querySelector('.navbar') // nav
, contentBar = nav.querySelector('.navbar-nav') // nav > ul
, moreGroup = nav.querySelector('.navbar-nav > li.grouped')
, navItems = [...nav.querySelectorAll('.navbar-nav > li:not(.grouped)')]
, moreList = moreGroup.querySelector('ul.grouped-content')
;
sizingNav();
window.addEventListener('resize', sizingNav);
function sizingNav()
{
let avaliable_width = nav.offsetWidth
;
while (moreList.firstElementChild) // replace all `LI.nav-item`
{ // back from moreList to navbar-nav
moreList.firstElementChild.classList.add('nav-item');
moreGroup.before(moreList.firstElementChild); // so, it is a move just before
}
for (let i = navItems.length; i-- > 0;) // right to left movin to set
{ // elements into the 'More' list
if ( contentBar.offsetWidth > avaliable_width )
{
navItems[i].classList.remove('nav-item');
moreList.prepend(navItems[i]);
}
else break; // no needs to go further...
}
}
const
nav = document.querySelector('.navbar') // nav
, contentBar = nav.querySelector('.navbar-nav') // nav > ul
, moreGroup = nav.querySelector('.navbar-nav > li.grouped')
, navItems = [...nav.querySelectorAll('.navbar-nav > li:not(.grouped)')]
, moreList = moreGroup.querySelector('ul.grouped-content')
;
sizingNav();
window.addEventListener('resize', sizingNav);
function sizingNav()
{
let avaliable_width = nav.offsetWidth
;
while (moreList.firstElementChild) // replace all `LI.nav-item`
{ // back from moreList to navbar-nav
moreList.firstElementChild.classList.add('nav-item');
moreGroup.before(moreList.firstElementChild); // so, it is a move just before
}
for (let i = navItems.length; i-- > 0;) // right to left movin to set
{ // elements into the 'More' list
if ( contentBar.offsetWidth > avaliable_width )
{
navItems[i].classList.remove('nav-item');
moreList.prepend(navItems[i]);
}
else break; // no needs to go further...
}
}
.nav-link {
white-space : nowrap;
max-width : 100px;
text-overflow : ellipsis;
overflow : hidden;
}
LI.grouped:has( UL.grouped-content:empty ) {
display : none;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<div class="container">
<nav class="navbar navbar-expand-sm">
<ul class="navbar-nav">
<li class="nav-item"><a href="#" class="nav-link">Item 1</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 2-2</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 3-33</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 4-444</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 5-555555</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 6-6</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 7-777777777777</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 8</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 9-99999</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 10+101010</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 11+11</a></li>
<li class="nav-item"><a href="#" class="nav-link">Item 12+121212</a></li>
<li class="nav-item dropdown grouped">
<a href="#" class="nav-link dropdown-toggle" aria-haspopup="true" aria-expanded="false" role="button" data-bs-toggle="dropdown" data-bs-display="static" data-bs-auto-close="outside">More</a>
<ul class="grouped-content"></ul>
</li>
</ul>
</nav>
</div>
本文标签: javascriptDetecting overflowing menu items doesn39t always calculate correctlyStack Overflow
版权声明:本文标题:javascript - Detecting overflowing menu items doesn't always calculate correctly - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736490891a1944343.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论