You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, I've created JS to work with shrink and expand end (last) menu items depend on container element or screen width. I'll share here in case you may interest to implement into your current work.
Main files
/** * Responsive navbar * * @license MIT rundiz.com */classRdResponsiveNavbar{/** * @type {boolean} Mark that previously was stopped working or not, just to prevent multiple working in loops. */
#isPreviousStoppedWorking =null;/** * @type {boolean} Mark this class to stop working. (Stop detect resize, element size change and make responsive navbar work.) */
#isStopWorking =false;/** * @type {number} Minimum break point (screen width) that this will be stop working and restore original navbar with wrapper. */
#minBreakPointStopWorking =0;/** * @type {string} Navbar class name. Default is 'rd-navbar-nav'. */
#navbarClass ='rd-navbar-nav';/** * @type {string} Class name of responsive navbar item. (Usually it is `li` of main navbar.) * This item will show up when area is narrower than navbar. * Default is 'rd-nav-item-responsive'. */
#navItemResponsiveClass ='rd-nav-item-responsive';/** * @type {string} Class name of responsive navbar item that is currently active. Default is 'rd-nav-item-responsive-active'. */
#navItemResponsiveActiveClass ='rd-nav-item-responsive-active';/** * @type {string} Class name of responsive navbar item link. */
#navItemResponsiveLinkClass ='rd-nav-link-responsive';/** * @type {string} Text or HTML inside responsive navbar item link. */
#navItemResponsiveLinkText ='...';/** * @type {string} Class name of navbar submenu (ul) of responsive navbar item. Default is 'rd-nav-submenu-responsive'. */
#navSubMenuResponsiveClass ='rd-nav-submenu-responsive';/** * @type {number} Number of offset when screen is wider so that navbar can expand/show up and did not cause horizontal scroll bar. */
#offsetWider =50;/** * @type {Element} Original HTML of navbar (including wrapper). */
#originalHTML;/** * @type {string} The selector of HTML that wrap navbar. */
#selector ='.rd-responsive-navbar';/** * Responsive navbar. This should work per single navbar. * * @param {object} options The options * @param {string} options.selector The selector of HTML that wrap navbar. Default is `.rd-responsive-navbar`. * @param {number} options.offsetWider Number of offset when screen is wider so that navbar can expand/show up and did not cause horizontal scroll bar. * Default is 50 OR a navigation bar item that have maximum width. * Value type must be integer. * @param {number} options.minBreakPointStopWorking Minimum break point (screen width) that this will be stop working and restore original navbar with wrapper. * Default is 0. * Value must be integer and value less than or equal to 0 will be always working. * This is to let something else to be working instead in case this is on top of other navbar framework. * @param {string} options.navItemResponsiveActiveClass Class name of responsive navbar item that is currently active. * Default is 'rd-nav-item-responsive-active'. * @param {string} options.navItemResponsiveLinkText Text or HTML inside responsive navbar item link. Default is '...'. * @param {string} options.navSubMenuResponsiveClass Class name of navbar submenu (ul) of responsive navbar item. * Default is 'rd-nav-submenu-responsive'. */constructor(options={}){if(typeof(options?.selector)==='string'){this.#selector =options.selector;}if(typeof(options?.offsetWider)==='number'){this.#offsetWider =parseInt(options.offsetWider);}else{constmaxNavWidth=this.#getMaxNavItemWidth();if(!isNaN(maxNavWidth)){this.#offsetWider =maxNavWidth;}}if(typeof(options?.minBreakPointStopWorking)==='number'){this.#minBreakPointStopWorking =parseInt(options.minBreakPointStopWorking);}if(typeof(options?.navItemResponsiveLinkText)==='string'){this.#navItemResponsiveLinkText =options.navItemResponsiveLinkText;}if(typeof(options?.navItemResponsiveActiveClass)==='string'){this.#navItemResponsiveActiveClass =options.navItemResponsiveActiveClass;}if(typeof(options?.navSubMenuResponsiveClass)==='string'){this.#navSubMenuResponsiveClass =options.navSubMenuResponsiveClass;}letnavbarWrapper=document.querySelector(this.#selector);if(!navbarWrapper){thrownewError('Navbar wrapper with selector "'+this.#selector +'" could not be found.');}if(!navbarWrapper.querySelector('.'+this.#navbarClass)){thrownewError('Navbar class `'+this.#navbarClass +'` could not be found.');}navbarWrapper=undefined;this.#keepOriginalHTML();this.#checkMinBreakPoint();this.#setupResponsiveLastMenuItem();this.#listenClickShowHideResponsiveNavbarItem();this.#listenResize();}// constructor/** * Check minimum break point and current screen width that must be equal or wider than break point to work. * * If minimum break point option was not set then this will skip working. * * This method was called from `constructor()`. */
#checkMinBreakPoint(){if(this.#minBreakPointStopWorking >0){constnavbarWrapper=document.querySelector(this.#selector);if(window.screen.width<this.#minBreakPointStopWorking){this.#isStopWorking =true;if(true!==this.#isPreviousStoppedWorking){this.#isPreviousStoppedWorking =true;do{constsubMenuResponsiveItem=document.querySelector(this.#selector +' .'+this.#navItemResponsiveClass +' > .'+this.#navSubMenuResponsiveClass +' > li');if(!subMenuResponsiveItem){break;}this.#moveFirstResponsiveMenuItemToLastMenuItem();}while(true);}}else{this.#isStopWorking =false;if(false!==this.#isPreviousStoppedWorking){this.#isPreviousStoppedWorking =false;}}}}// #checkMinBreakPoint/** * Get maximum navigation items width. * * This method was called from `constructor()`. * * @returns {number} Return max width. */
#getMaxNavItemWidth(){constnavbarWrapper=document.querySelector(this.#selector);constnavItemsWidth=[];navbarWrapper.querySelectorAll('.'+this.#navbarClass +' > li')?.forEach((item)=>{if(item.classList.contains(this.#navItemResponsiveClass)){// if this item is navbar item responsive. // do not work here.return;}constitemRect=item.getBoundingClientRect();navItemsWidth.push(parseFloat(itemRect.width));});returnMath.max(...navItemsWidth);}// #getMaxNavItemWidth/** * Keep original HTML of navbar (including wrapper). * * This method was called from `constructor()`. */
#keepOriginalHTML(){constnavbarWrapper=document.querySelector(this.#selector);if(typeof(this.#originalHTML)==='undefined'){consttemplate=document.createElement('div');template.setAttribute('id','rd-responsive-navbar-original');template.setAttribute('style','display:none;');template.innerHTML=navbarWrapper.outerHTML;document.body.appendChild(template);// use just `cloneNode(true)` can still be altered by its original HTML.this.#originalHTML =template;}}// #keepOriginalHTML/** * Listen click to show/hide responsive sub menu. * * This method was called from `constructor()`. */
#listenClickShowHideResponsiveNavbarItem(){document.addEventListener('click',(event)=>{if(event.target.closest('.'+this.#navItemResponsiveClass)){// if click inside responsive navbar item.constthisTarget=event.currentTarget.activeElement;if(thisTarget.classList?.contains(this.#navItemResponsiveLinkClass)){// if clicking on responsive link inside responsive navbar item.event.preventDefault();this.toggleSubMenuResponsive();}}else{// if click outside responsive navbar item.// hide responsive sub menu.this.toggleSubMenuResponsive('hide');}});}// #listenClickShowHideResponsiveNavbarItem/** * Listen resize screen, or navbar wrapper and start to functional. * * This method was called from `constructor()`. */
#listenResize(){window.addEventListener('resize',(event)=>{this.#checkMinBreakPoint();});constnavbarWrapper=document.querySelector(this.#selector);if(!navbarWrapper.dataset.rdResponsiveNavbarObserved){constresizeObserver=newResizeObserver((entries)=>{if(false===this.#isStopWorking){this.#navbarSizeMayChanged();}});resizeObserver.observe(navbarWrapper);navbarWrapper.dataset.rdResponsiveNavbarObserved='true';}}// #listenResize/** * Move first responsive menu item to last menu item (but before navbar item responsive). * Or this method is restoration of `#moveLastMenuItemToResponsiveMenuItem()` but per item. * * This method was called from `#navbarSizeMayChanged()`. * * @returns */
#moveFirstResponsiveMenuItemToLastMenuItem(){constnavbarNav=document.querySelector(this.#selector +' > .'+this.#navbarClass);constresponsiveMenuItem=navbarNav?.querySelector('.'+this.#navItemResponsiveClass);constfirstResponsiveMenuItem=responsiveMenuItem?.querySelector('ul > li:first-child');if(typeof(firstResponsiveMenuItem)!=='object'||firstResponsiveMenuItem===null){returnnull;}letlastMenuItem=navbarNav.lastElementChild;lastMenuItem.insertAdjacentElement('beforebegin',firstResponsiveMenuItem);// this is already moved, no need to `remove()` source.if(!responsiveMenuItem?.querySelector('ul > li:first-child')){responsiveMenuItem.style.display='none';}letcustomEvent=newCustomEvent('rundiz.responsivenavbar.moveResponsiveItemToLastItem',{'bubbles': true,'detail': {'firstResponsiveMenuItem': firstResponsiveMenuItem,}});document.dispatchEvent(customEvent);customEvent=newCustomEvent('rundiz.responsivenavbar.menuItemUpdated',{'bubbles': true,});document.dispatchEvent(customEvent);}// #moveFirstResponsiveMenuItemToLastMenuItem/** * Move last menu item to responsive menu item. * * This method was called from `#navbarSizeMayChanged()`. */
#moveLastMenuItemToResponsiveMenuItem(){constnavbarNav=document.querySelector(this.#selector +' > .'+this.#navbarClass);constnavbarChildren=navbarNav.children;letlastMenuItem;if(navbarChildren.length>=2){lastMenuItem=navbarChildren[navbarChildren.length-2];}else{returnnull;}if(lastMenuItem){constresponsiveMenuItem=navbarNav?.querySelector('.'+this.#navItemResponsiveClass);constresponsiveMenuItemUL=responsiveMenuItem?.querySelector('ul');responsiveMenuItem.style.display='';responsiveMenuItemUL.insertAdjacentElement('afterbegin',lastMenuItem);// this is already moved, no need to `remove()` source.letcustomEvent=newCustomEvent('rundiz.responsivenavbar.moveLastItemToResponsiveItem',{'bubbles': true,'detail': {'lastMenuItem': lastMenuItem,}});document.dispatchEvent(customEvent);customEvent=newCustomEvent('rundiz.responsivenavbar.menuItemUpdated',{'bubbles': true,});document.dispatchEvent(customEvent);}}// #moveLastMenuItemToResponsiveMenuItem/** * Check that navbar size may changed then start functional. * * This method was called from `#listenResize()`. */
#navbarSizeMayChanged(){if(true===this.#isStopWorking){return;}constnavbarWrapper=document.querySelector(this.#selector);constnavbarWrapperRect=navbarWrapper.getBoundingClientRect();// calculate navbar wrapper's content width. // can't use query `navbarWrapper > *` and just `getBoundingClientRect()` because some times, the navbar's content is `inline-block` and will cause incorrect size on expand which result in can't expand.// use this method in calculation. (nav wrapper's width - anything that consume space before its content (`ul`) such as padding, border.)constnavbarWrapperComputed=window.getComputedStyle(navbarWrapper);letnavbarWrapperContentWidth=navbarWrapperRect.width-(parseFloat(navbarWrapperComputed.paddingLeft)+parseFloat(navbarWrapperComputed.paddingRight));navbarWrapperContentWidth=navbarWrapperContentWidth-(parseFloat(navbarWrapperComputed.borderLeftWidth)+parseFloat(navbarWrapperComputed.borderRightWidth));if(isNaN(navbarWrapperContentWidth)){console.info('[rd responsive navbar]: Navbar wrapper content has `NaN` width. Using navbar wrapper\'s width instead.',navbarWrapperRect.width,navbarWrapperComputed)navbarWrapperContentWidth=navbarWrapperRect.width;}letallNavItemsWidth=this.#sumAllNavItemsWidth();if(navbarWrapperContentWidth<allNavItemsWidth){// if navbar wrapper width is narrower that its content or scroll bar appeared.do{letallNavItemsWidth=this.#sumAllNavItemsWidth();if(navbarWrapperContentWidth>=allNavItemsWidth){break;}constreturnResult=this.#moveLastMenuItemToResponsiveMenuItem();if(typeof(returnResult)!=='undefined'&&null===returnResult){break;}}while(true);}else{// else. here, after resize window to larger it can be equal (navbar wrapper width vs navbar scroll width).// check the actual navbar items width.do{letallNavItemsWidth=this.#sumAllNavItemsWidth();if((allNavItemsWidth+this.#offsetWider)<=navbarWrapperContentWidth){constreturnResult=this.#moveFirstResponsiveMenuItemToLastMenuItem();if(typeof(returnResult)!=='undefined'&&null===returnResult){break;}}else{break;}}while(true);}}// #navbarSizeMayChanged/** * Setup last menu item as responsive menu. * * This method was called from `constructor()`. */
#setupResponsiveLastMenuItem(){constnavbarNav=document.querySelector(this.#selector +' > .'+this.#navbarClass);if(navbarNav&&!navbarNav?.querySelector('.'+this.#navItemResponsiveClass)){consthtml='<li class="'+this.#navItemResponsiveClass +'" style="display: none;">'+"\n"+'<a class="'+this.#navItemResponsiveLinkClass +'" href="#">'+this.#navItemResponsiveLinkText +'</a>'+"\n"+'<ul class="'+this.#navSubMenuResponsiveClass +'"></ul>'+"\n"+'</li>';navbarNav.insertAdjacentHTML('beforeend',html);constcustomEvent=newCustomEvent('rundiz.responsivenavbar.setupsuccess',{'bubbles': true,});document.dispatchEvent(customEvent);}}// #setupResponsiveLastMenuItem/** * Summary all nav items width. * * This method was called from `#navbarSizeMayChanged()`. * * @returns {number} Return total nav items width. */
#sumAllNavItemsWidth(){constnavbarWrapper=document.querySelector(this.#selector);lettotalNavItemWidth=0;navbarWrapper.querySelectorAll('.'+this.#navbarClass +' > li')?.forEach((item)=>{if(item.classList.contains(this.#navItemResponsiveClass)){// if this item is navbar item responsive. // do not calculate here.return;}constitemRect=item.getBoundingClientRect();totalNavItemWidth=Math.ceil(parseFloat(totalNavItemWidth)+parseFloat(itemRect.width));});// calculate including width of navbar item responsive (last one) that is maybe hiding.constnavItemResponsive=navbarWrapper.querySelector('.'+this.#navItemResponsiveClass);if(navItemResponsive){constoriginalDisplay=navItemResponsive.style.display;constoriginalVisibility=navItemResponsive.style.visibility;navItemResponsive.style.display='';navItemResponsive.style.visibility='hidden';constnavItemResponsiveWidth=navItemResponsive.offsetWidth;navItemResponsive.style.display=originalDisplay;navItemResponsive.style.visibility=originalVisibility;totalNavItemWidth=Math.ceil(parseFloat(totalNavItemWidth)+parseFloat(navItemResponsiveWidth));}// endif;returntotalNavItemWidth;}// #sumAllNavItemsWidth/** * Toggle sub menu responsive (show/hide). * * @param {string|null} doing What action is doing. Accepted: 'hide', 'show', `null`. Default is `null` to auto detect. */toggleSubMenuResponsive(doing=null){constnavItemResponsive=document.querySelector('.'+this.#navItemResponsiveClass);constsubmenu=navItemResponsive?.querySelector('.'+this.#navSubMenuResponsiveClass);constsubmenuDisplayingClass='rd-display-submenu-responsive-item';if(null===doing){if(submenu?.classList?.contains(submenuDisplayingClass)){submenu?.classList?.remove(submenuDisplayingClass);navItemResponsive?.classList?.remove(this.#navItemResponsiveActiveClass);}else{submenu?.classList?.add(submenuDisplayingClass);navItemResponsive?.classList?.add(this.#navItemResponsiveActiveClass);}}elseif('show'===doing){submenu?.classList?.add(submenuDisplayingClass);navItemResponsive?.classList?.add(this.#navItemResponsiveActiveClass);}elseif('hide'===doing){submenu?.classList?.remove(submenuDisplayingClass);navItemResponsive?.classList?.remove(this.#navItemResponsiveActiveClass);}}// toggleSubMenuResponsive}// RdResponsiveNavbar
How it works?
Here is the full width of web page or large screen size. The menu items will be show all.
This screenshot is for narrower screen width. The end menu items will be shrink into responsive menu item as sub menu (click on caret icon).
This screenshot is based on Bootstrap 5 md break point (768px). All menu items will be appears but in Bootstrap responsive navbar style.
With this JS, it can help long navbar menu items display better on more device (instead of over the screen or fall down to multiple lines). It can change minimum break point to stop working (and let the framework such as Bootstrap to work).
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, I've created JS to work with shrink and expand end (last) menu items depend on container element or screen width. I'll share here in case you may interest to implement into your current work.
Main files
rd-responsive-navbar.js
navbar.css
navbar-bootstrap.css
Usage
simple-demo.html
Bootstrap 3 with collapsible navbar.
Bootstrap 5 with collapsible navbar.
Screenshots
How it works?

Here is the full width of web page or large screen size. The menu items will be show all.
This screenshot is for narrower screen width. The end menu items will be shrink into responsive menu item as sub menu (click on caret icon).
This screenshot is based on Bootstrap 5
mdbreak point (768px). All menu items will be appears but in Bootstrap responsive navbar style.With this JS, it can help long navbar menu items display better on more device (instead of over the screen or fall down to multiple lines). It can change minimum break point to stop working (and let the framework such as Bootstrap to work).
I hope it will be useful.
Beta Was this translation helpful? Give feedback.
All reactions