Sample: https://codesandbox.io/p/sandbox/navbar-k2szsq (or refer to the code snippet below)
In my navbar, I have implemented multiple submenus with a height transition when the user opens or closes a submenu. However, only one submenu can be open at a time. This means that if a user tries to open another submenu without closing the first one, the previous one automatically closes. Here lies the challenge:
When the user opens a submenu located below the previously opened submenu, both having considerable content height and undergoing height transitions simultaneously, it causes the page to scroll down away from the newly opened submenu (please run the code snippet below and follow these steps: First open "Submenu 1" and leave it open, then open "Submenu 2" to observe the issue mentioned).
Is there an elegant solution to address this problem? Waiting for both transitions to end before scrolling to the new submenu may not provide a good user experience. Similarly, closing the previous submenu first and waiting for it to close before opening the new one is not desirable either. So, is there a smooth and user-friendly solution to simultaneously close the previous submenu and open the new one with a height transition, while ensuring that the new submenu remains on screen (at least making its button visible at the top edge of the navbar)?
generateNavbar(".navbar");
$(".submenu-title").on("click", function() {
const $submenuTitle = $(this);
const $submenu = $submenuTitle.closest(".submenu");
$submenu.siblings(".submenu.opened").each(function() {
toggleSubmenu($(this), false);
});
toggleSubmenu($submenu, !$submenu.hasClass("opened"));
});
$(".submenu-content").on("transitionend", function() {
const $submenuContent = $(this);
if ($submenuContent.hasClass("opened")) {
$submenuContent.css("height", "");
}
});
function toggleSubmenu($submenu, isOpen) {
const $submenuContent = $submenu.find(".submenu-content");
if (isOpen) {
$submenuContent.css("height", $submenuContent.get(0).scrollHeight);
} else {
$submenuContent.css("height", $submenuContent.get(0).scrollHeight);
// reflow
$submenuContent.get(0).offsetHeight;
$submenuContent.css("height", 0);
}
$submenu.toggleClass("opened");
}
function generateNavbar(selector) {
// data
const submenusItemsCount = [
36, 27, 14, 1, 6, 3, 23, 50, 21, 4, 5, 50, 3, 12, 4, 4, 6, 9, 20, 2,
];
const submenus = submenusItemsCount.map(
(submenuItemsCount, submenuIndex) => ({
title: `Submenu ${submenuIndex + 1}`,
items: [...new Array(submenuItemsCount)].map(
(_, itemIndex) =>
`Submenu ${submenuIndex + 1} - Item ${itemIndex + 1}`
),
})
);
// dom
const $navbar = $(selector);
const $submenus = submenus.map((submenu) => {
const $submenu = $('<div class="submenu" data-submenu />');
const $submenuTitle = $(
`<div class="submenu-title" data-submenu-title>${submenu.title}</div>`
);
const $submenuContent = $(
`<div class="submenu-content" style="height: 0" data-submenu-content />`
);
submenu.items.map((submenuItem) => {
$submenuContent.append(
`<div class="submenu-item" data-submenu-item>${submenuItem}</div>`
);
});
$submenu.append($submenuTitle);
$submenu.append($submenuContent);
return $submenu;
});
$navbar.html($submenus);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.backdrop {
position: fixed;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
top: 0;
left: 0;
}
.navbar {
position: fixed;
width: 100%;
height: 80%;
z-index: 100;
background-color: #fff;
bottom: 0;
left: 0;
overflow-y: scroll;
}
.submenu-title {
display: flex;
align-items: center;
padding: 16px 8px;
width: 100%;
height: 50%;
border-bottom: 1px solid #9e9e9e;
cursor: pointer;
}
.submenu-content {
background-color: #f0f0f0;
transition: height 325ms ease;
overflow: hidden;
}
.submenu-item {
padding: 16px 8px;
height: 50px;
border-bottom: 1px solid #9e9e9e;
cursor: pointer;
}
<div class="backdrop"></div>
<div class="navbar">Loading...</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>