Demo: https://codesandbox.io/p/sandbox/navbar-k2szsq (or see code snippet below)
I have a navbar that contains multiple submenus when the user open/close a submenu it opened/closed with a height transition. However only one submenu can be opened at a time that means if the user opened another submenu without closing the first one the previous one gets closed automatically and here’s the problem:
If the user open a submenu that is below to the previous opened submenu and both have a long content height and since both gets open/close with height transition at the same time it causes the scroll to scroll down away from the new submenu that user opened. (please run the below code snippet and do these steps: First open "Submenu 1" (and don’t close it) then open "Submenu 2" and you’ll notice the issue I described)
So is there is a smooth solution to solve this issue I know I can wait until both transitions end then scroll to the opened submenu (but that’s not a good user experience) or maybe first close the previous opened submenu wait until it is closed then open the new submenu (but that’s not what I want) So is there’s a smooth user-friendly solution to close the previous submenu and open the new submenu at the same time (both with height transition) while maintaining the new opened submenu to be on screen? (at least the new opened submenu button (e.g. "Submenu 2") is visible on 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>
3
Answers
calculated the distance of the submenu from the top of the screen before any opening or closing
initialOffset
the opening and closing transitions of the submenus as usualtoggleSubmenu
after the transitions over adjust the scroll position based on the new position of thesubmenu scrollTop
I have added a callback for the ToggleSubMenu to handl the transitionen event. which fired when the csstrasition is completed
I changed your code a little bit,
in your code you directly toggled the submenu without knowing whether it is opening or not so i create a variable isOpening for that purpose.
i also added new function scrollIntoViewIfNeeded which checks if submenu is outside of viewport so it scrolls to that submenu