I want to use Floating UI to prevent dropdown menus from going off the left/right edge of the screen. The example code is as follows:
HTML:
<ul class="menu">
<li class="menu__item">
Item 1
<ul class="submenu">
<li>Item 1.1</li>
<li>Item 1.2</li>
<li>Item 1.3</li>
<li>Item 1.4</li>
<li>Item 1.5</li>
</ul>
</li>
<li class="menu__item">
Item 2
<ul class="submenu">
<li>Item 2.1</li>
<li>Item 2.2</li>
<li>Item 2.3</li>
<li>Item 2.4</li>
<li>Item 2.5</li>
</ul>
</li>
<li class="menu__item">
Item 3
<ul class="submenu">
<li>Item 3.1</li>
<li>Item 3.2</li>
<li>Item 3.3</li>
<li>Item 3.4</li>
<li>Item 3.5</li>
</ul>
</li>
<li class="menu__item">
Item 4
<ul class="submenu">
<li>Item 4.1</li>
<li>Item 4.2</li>
<li>Item 4.3</li>
<li>Item 4.4</li>
<li>Item 4.5</li>
</ul>
</li>
<li class="menu__item">
Item 5
<ul class="submenu">
<li>Item 5.1</li>
<li>Item 5.2</li>
<li>Item 5.3</li>
<li>Item 5.4</li>
<li>Item 5.5</li>
</ul>
</li>
</ul>
CSS:
* {
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
display: flex;
}
a {
color: inherit;
text-decoration: none;
}
.menu {
display: flex;
gap: 5px;
justify-content: space-around;
color: white;
}
.menu > li {
position: relative;
background: blue;
padding: 0 30px;
height: 40px;
}
.submenu {
display: none;
position: absolute;
left: 50%;
top: 40px;
transform: translateX(-50%);
background: red;
}
.menu > li:hover .submenu {
display: flex;
}
.submenu li {
padding: 10px 20px;
}
.submenu a {
white-space: nowrap;
}
JS:
import { autoUpdate, computePosition, shift } from "https://cdn.skypack.dev/#floating-ui/dom#1.2.0";
const menuItems = document.querySelectorAll('.menu__item');
menuItems.forEach((item) => {
const reference = item.querySelector('.menu__link');
const floating = item.querySelector('.submenu');
computePosition(reference, floating, {
placement: "bottom",
middleware: [shift()]
}).then(({ x, y }) => {
Object.assign(floating.style, {
left: `${x}px`
});
});
});
There's a CodePen here.
However, I'm struggling to understand the documentation and the implementation I have doesn't seem to work. The red dropdowns should be center-aligned under the blue items where possible but Floating UI should adjust the positioning horizontally so that the first dropdown should have its left edge aligned to the left of the screen and the last dropdown should have its right edge aligned to the right of the screen.
Could anyone advise how to get this working and ensure that it maintains the correct positioning even after window resizing?
Many thanks.
Related
So, i've seen some other answers for similar stuff, but nothing that really helps or even works, I've tried to follow every step people said in other posts but nothing helped.
I wanna show a sub menu when I hover over it by removing the class hidden that it is defining its display to hiden, but I can´t make it using only css and, even though my js code recognizes that the mouse is hovering through it, it won´t budge.
So, here's my codes
$(".dropdown").hover(function() {
console.log('hover in');
$(".dropdown-content").removeClass("hidden");
console.log('hover out');
$(".dropdown-content").addClass("hidden");
})
.hidden {
display: none;
}
.menu-desktop {
position: absolute;
left: 70%;
list-style-type: none;
}
.menu-desktop ul {
margin: 0;
padding: 0;
list-style: none;
}
.menu-desktop a {
display: inline;
float: left;
margin-left: 5px;
color: #bcbcbc;
text-align: center;
text-transform: uppercase;
text-decoration: none;
background-color: transparent;
border-radius: 0%;
border-color: transparent;
}
.menu-desktop a:hover {
color: #858181
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
}
.dropdown-content .shown {
position: absolute;
background-color: blue;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
padding: 12px 16px;
z-index: 1;
}
.dropdown:hover .dropdown-content {
display: block !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav class="navbar-itens">
<div class="logo">
<img src="assets/9mnb2mazqne71.png" alt="">
</div>
<!-- Navigation menu -->
<ul id="nav" class="menu-desktop">
<li>HOME</li>
<li class="dropdown">EMPRESA</li>
<ul id="submenu" class="dropdown-content hidden">
<li>submenu 1</li>
<li>submenu 1</li>
<li>submenu 1</li>
<li class="dropdown">submenu 1</li>
<ul id="submenu2" class="dropdown-content hidden">
<li>submenu 2</li>
<li>submenu 2</li>
<li>submenu 2</li>
<li>submenu 2</li>
</ul>
</ul>
<li>CLIENTES</li>
<li>CONTATO</li>
</ul>
</nav>
EDIT: Forgot to include my css, so, here it is!
You need to declare function inside hover() to handle hover in & out event:
$( ".dropdown" ).hover(
function() {
$(".dropdown-content").removeClass("hidden");
}, function() {
$(".dropdown-content").addClass("hidden");
}
);
The problems with your code
Main problem
You immediately add the class back after removing it, so you never see the dropdown content.
jQuery's .hover() expects two function arguments (first is run when mouse enters, second when mouse leaves).
direct-child-only problem
Second problem with your code is that it toggles the class on all submenus while the one you want is only the direct, immediate child of the list item being hovered. You can use
$(this).find(".dropdown-content").first()
to only affect the sub-ul you want. Alternatively, instead of .first(), you can also use .eq(0).
Invalid html problem
Please also note that ul can only have li children, so you need to wrap any sub-ul inside an li.
The solution to not use
$(".dropdown").hover(
function() {
$(this).find(".dropdown-content").first().removeClass("hidden");
},
function() {
$(this).find(".dropdown-content").eq(0).addClass("hidden");
}
);
.hidden {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="nav" class="menu-desktop">
<li>HOME</li>
<li class="dropdown">EMPRESA
<ul id="submenu" class="dropdown-content hidden">
<li>submenu 1</li>
<li>submenu 1</li>
<li>submenu 1</li>
<li class="dropdown">submenu 1
<ul id="submenu2" class="dropdown-content hidden">
<li>submenu 2</li>
<li>submenu 2</li>
<li>submenu 2</li>
<li>submenu 2</li>
</ul>
</li>
</ul>
</li>
<li>CLIENTES</li>
<li>CONTATO</li>
</ul>
The solution to use is CSS only
That being said, you don't need any JavaScript for this, and you shouldn't be using JS for a CSS job:
.dropdown-content {
display: none;
}
.dropdown:hover > .dropdown-content {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="nav" class="menu-desktop">
<li>HOME</li>
<li class="dropdown">EMPRESA
<ul id="submenu" class="dropdown-content">
<li>submenu 1</li>
<li>submenu 1</li>
<li>submenu 1</li>
<li class="dropdown">submenu 1
<ul id="submenu2" class="dropdown-content">
<li>submenu 2</li>
<li>submenu 2</li>
<li>submenu 2</li>
<li>submenu 2</li>
</ul>
</li>
</ul>
</li>
<li>CLIENTES</li>
<li>CONTATO</li>
</ul>
I've been learning Javascript and as a bit of practice, thought i'd create myself a dropdown navigation menu, that works on a 'click' rather than hover. I've created the code below (which doesn't work) and i was wondering if someone could explain why so i can see where i've gone wrong.
I can get the dropdown to open, but when i added the code to close the dropdown, the dropdown menu doesn't even open.
I'm hoping someone can point me in the right direction so i can see where i'm going wrong and i can avoid such issues in the future
(function() {
let menuHeader = document.querySelectorAll('.menu-item-has-children');
let subMenu = document.querySelector('.sub-menu');
menuHeader.forEach(function(btn) {
btn.addEventListener('click', function(event) {
event.preventDefault();
subMenu.classList.add('nav-open');
// Close if anywhere on screen aprt form menu is clicked
if (subMenu.classList.contains('nav-open')) {
window.onclick = function(event) {
if (!event.target.classList.contains('sub-menu')) {
subMenu.classList.remove('nav-open');
}
};
};
});
});
})();
<nav class="main-nav">
<ul>
<li>Home</li>
<li class="menu-item-has-children">What We Do
<ul class="sub-menu">
<li>Page 1</li>
<li>Page 2</li>
<li>Page 3</li>
<li>Page 4</li>
<li>Page 5</li>
<li>Page 6</li>
</ul>
</li>
<li class="menu-item-has-children">Our Work
<ul class="sub-menu">
<li>Portfolio 1</li>
<li>Portfolio 2</li>
<li>Portfolio 3</li>
<li>Portfolio 4</li>
<li>Portfolio 5</li>
</ul>
</li>
<li>Contact</li>
</ul>
</nav>
As requested, the CSS is as follows
.main-nav ul > li {
display: inline;
position: relative;
}
.main-nav ul li a {
display: inline-block;
font-family: bebas-neue, sans-serif;
font-style: normal;
font-weight: 400;
text-decoration: none;
color: #333;
font-size: 1.3em;
padding: 0.5em 0.8em 0.8em 0.8em;
transition: background 0.2s linear;
}
.main-nav ul li a:hover {
background: #00a492;
color: #fff;
}
.main-nav ul li a:not(:only-child):after {
content: '\25bc';
font-size: 0.6em;
position: relative;
top: -4px;
left: 2px;
}
/* Second Level of Navigation */
.main-nav ul li ul {
width: 250px;
position: absolute;
top: 160%;
left: 0;
background: #00a492;
}
.main-nav ul li ul li a {
display: block;
}
.sub-menu {
display: none;
}
.nav-open {
display: block;
}
.slicknav_menu {
display:none;
}
.highlight-btn {
background: #00a492 !important;
color: #fff !important;
}
``
Consider using event delegation instead. When anywhere on the page is clicked, run an event listener. If the clicked target is not inside a LI, remove the .nav-open from the existing element with .nav-open, if there is one. Otherwise, if the clicked target was the <a> (a descendant of the outer .menu-item-has-children), navigate to its second child (.children[1]) and add the class to it:
function closeOpenNav() {
const currentOpen = document.querySelector('.nav-open');
if (currentOpen) {
currentOpen.classList.remove('nav-open');
}
}
window.addEventListener('click', (event) => {
const li = event.target.closest('.menu-item-has-children');
if (!li) {
// Close if anywhere on screen aprt form menu is clicked
console.log('closing');
closeOpenNav();
return;
}
if (event.target.parentElement !== li) {
// The clicked element is a descendant of a `.nav-open`
// so, don't do anything
return;
}
event.preventDefault();
closeOpenNav();
const thisSubMenu = li.children[1];
thisSubMenu.classList.add('nav-open');
});
.sub-menu {
display: none;
}
.nav-open {
display: block;
}
<nav class="main-nav">
<ul>
<li>Home</li>
<li class="menu-item-has-children">What We Do
<ul class="sub-menu">
<li>Page 1</li>
<li>Page 2</li>
<li>Page 3</li>
<li>Page 4</li>
<li>Page 5</li>
<li>Page 6</li>
</ul>
</li>
<li class="menu-item-has-children">Our Work
<ul class="sub-menu">
<li>Portfolio 1</li>
<li>Portfolio 2</li>
<li>Portfolio 3</li>
<li>Portfolio 4</li>
<li>Portfolio 5</li>
</ul>
</li>
<li>Contact</li>
</ul>
</nav>
I think it can happen because at first you are adding nav-open class than you are checking is it exist if yes you are adding event to window(window still can catch this event) and deleting this class so nothing should happen. Try to addevent.stopPropagation() under event.preventDefault()
(function() {
let menuHeader = document.querySelectorAll('.menu-item-has-children');
let subMenu = document.querySelector('.sub-menu');
menuHeader.forEach(function(btn) {
btn.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation()
subMenu.classList.add('nav-open');
// Close if anywhere on screen aprt form menu is clicked
if (subMenu.classList.contains('nav-open')) {
window.onclick = function(event) {
if (!event.target.classList.contains('sub-menu')) {
subMenu.classList.remove('nav-open');
}
};
};
});
});
})();```
This is a wroking demo
(function() {
let menuHeader = document.querySelectorAll('.menu-item-has-children');
let subMenus = document.querySelectorAll('.sub-menu');
menuHeader.forEach(function(btn) {
btn.querySelector('a').addEventListener('click', function(event) {
event.preventDefault();
event.stopImmediatePropagation();
closeAllMenus();
btn.querySelector('.sub-menu').classList.add('nav-open');
});
});
document.addEventListener("click", function(){
closeAllMenus();
});
function closeAllMenus(){
subMenus.forEach(function(ele) {
ele.classList.remove('nav-open');
});
}
})();
.sub-menu{
display: none;
}
.sub-menu.nav-open{
display: block;
}
<nav class="main-nav">
<ul>
<li>Home</li>
<li class="menu-item-has-children">
What We Do
<ul class="sub-menu">
<li>Page 1</li>
<li>Page 2</li>
<li>Page 3</li>
<li>Page 4</li>
<li>Page 5</li>
<li>Page 6</li>
</ul>
</li>
<li class="menu-item-has-children">Our Work
<ul class="sub-menu">
<li>Portfolio 1</li>
<li>Portfolio 2</li>
<li>Portfolio 3</li>
<li>Portfolio 4</li>
<li>Portfolio 5</li>
</ul>
</li>
<li>Contact</li>
</ul>
</nav>
The first mistake on your code was declaring this
let subMenu = document.querySelector('.sub-menu');
this always will be the first subMenu, so I just removed it.
If you want to get the subMenu of every element you should do something like this:
btn.querySelector('.sub-menu')
So now It will get the sub menu for every element inside the forEach you already did.
To close the menu on document click I just made an event listener on document click which will remove the class nav-open from any of the sub menus.
document.addEventListener("click", function(){
subMenus.forEach(function(ele) {
ele.classList.remove('nav-open');
});
});
maybe like so, code should be self explanatory
// Dropdown Menu
(function(){
let menuHeader = document.querySelectorAll('.menu-item-has-children');
menuHeader.forEach(function(btn){
btn.addEventListener('click', function(event){
event.preventDefault();
if(this.classList.contains('nav-open')) {
return this.classList.remove('nav-open');
}
var openNavPoints = document.querySelectorAll('.menu-item-has-children.nav-open');
if(openNavPoints.length >= 1) {
[...openNavPoints].forEach(function(openNavPoint) {
openNavPoint.classList.remove('nav-open');
});
}
this.classList.add('nav-open');
});
});
})();
.menu-item-has-children .sub-menu {
display: none;
}
.menu-item-has-children.nav-open .sub-menu {
display: block;
}
<nav class="main-nav">
<ul>
<li>Home</li>
<li class="menu-item-has-children">What We Do
<ul class="sub-menu">
<li>Page 1</li>
<li>Page 2</li>
<li>Page 3</li>
<li>Page 4</li>
<li>Page 5</li>
<li>Page 6</li>
</ul></li>
<li class="menu-item-has-children">Our Work
<ul class="sub-menu">
<li>Portfolio 1</li>
<li>Portfolio 2</li>
<li>Portfolio 3</li>
<li>Portfolio 4</li>
<li>Portfolio 5</li>
</ul>
</li>
<li>Contact</li>
</ul>
</nav>
I have these tabs but is it possible to make the other inactive tabs to be unclickable?
<div class="navbar">
<div class="navbar-inner">
<ul class="nav nav-tabs" style="display:inline-flex">
<li class="active">Step 1</li>
<li>Step 2</li>
<li>Step 3</li>
<li>Step 4</li>
<li>Step 5</li>
<li>Step 6</li>
<li>Step 7</li>
</ul>
</div>
</div>
To get the best result you can:
Add the disabled class to the li element
Remove the data-toggle attribute for the a
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<div class="navbar">
<div class="navbar-inner">
<ul class="nav nav-tabs" style="display:inline-flex">
<li class="active">Step 0</li>
<li>Step 1</li>
<li class="disabled"><a href="#step2" >Step 2</a></li>
<li class="disabled"><a href="#step3" >Step 3</a></li>
<li class="disabled"><a href="#step4" >Step 4</a></li>
</ul>
</div>
</div>
You can use :not() CSS selector with pointer-events: none; to disable click event.
li:not(.active) a{
pointer-events: none;
}
<div class="navbar">
<div class="navbar-inner">
<ul class="nav nav-tabs" style="display:inline-flex">
<li class="active">Step 1</li>
<li>Step 2</li>
<li>Step 3</li>
<li>Step 4</li>
<li>Step 5</li>
<li>Step 6</li>
<li>Step 7</li>
</ul>
</div>
</div>
Simply add this class to the tabs that you want to disable the clicks.
Here is the css.
.avoid-clicks {
pointer-events: none;
}
disable them all and enable the active one
$('li').prop('disabled',true);
$('.active').prop('disabled',false);
To disable the links on the fly using jquery try this:
$(document).ready(function(){
$("a").click(function(event){
event.preventDefault();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
Go to google
You can add the following rules to your non-active items to make them unclickable. This was suggested in the following post:
How to disable all div content
.disabled, li:not(.active) {
pointer-events: none;
opacity: 0.4;
}
ul {
display: inline-flex;
width: 100%;
list-style-type: none;
margin: 0;
padding: 0;
height: 1em;
line-height: 1em;
}
li {
width: 14%;
height: 2em;
margin: 0.1%;
line-height: 2em;
background-color: #FFF;
border: thin solid #777;
text-align: center;
}
li a {
display: block;
width: 100%:
height: 100%;
text-decoration: none;
}
li a, li a:active, li:visited {
color: #48A;
}
li a:hover {
color: #5AC;
text-decoration: underline;
}
.active {
background-color: #FFF;
font-weight: bold;
border: thin solid #DDD;
}
.disabled, li:not(.active) {
background-color: #AAA;
pointer-events: none;
opacity: 0.4;
}
<div class="navbar">
<div class="navbar-inner">
<ul class="nav nav-tabs">
<li class="active">Step 1</li>
<li>Step 2</li>
<li>Step 3</li>
<li>Step 4</li>
<li>Step 5</li>
<li>Step 6</li>
<li>Step 7</li>
</ul>
</div>
</div>
I have an issue while sorting the list items when width if all the <li> is not equal. In the example below I try to place "Item 6" just after "Item 4" to no avail.
$('#sort').sortable({
});
ul {
margin: 0;
padding: 0;
}
li {
display: inline-block;
margin: 5px;
padding: 5px;
background: #0f0;
width: 25%;
}
.ui-sortable-placeholder {
height: 0 !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="sort">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li style="width:100%;">Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
</ul>
Actually you still can put Item 6 after Item 4 (eg. put Item 6 before Item 4 first then move it after Item 4). It's just a bit difficult. Adding grid option makes it easier. Try to run this snippet:
$('#sort').sortable({
grid: [ 10, 10 ]
});
ul {
margin: 0;
padding: 0;
}
li {
display: inline-block;
margin: 5px;
padding: 5px;
background: #0f0;
width: 25%;
}
.ui-sortable-placeholder {
height: 0 !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
<ul id="sort">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li style="width:100%;">Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
</ul>
JSFiddle # http://jsfiddle.net/UqyAq/
html -
<nav>
<ul>
<li>Button 1</li>
<li>Button 2
<ul>
<li>Sub-Button 1</li>
<li>Sub-Button 2</li>
<li>Sub-Button 3</li>
</ul>
</li>
<li>Button 3</li>
<li>Button 4</li>
<li>Button 5</li>
</ul>
</nav>
css -
* {
margin: 0;
padding: 0;
}
nav {
height: 100%; width: 18%;
position: absolute;
}
nav ul {
list-style-type: none;
font-family: 'Universal Accreditation', Serif;
font-size: 18pt;
letter-spacing: 1px;
font-variant: small-caps;
color: black;
}
nav ul ul {
display: none;
}
nav ul li {
display: block;
}
nav ul li:hover ul {
display: block;
}
Basically, you are looking at a simple un-ordered list drop down menu. I am trying to figure out what I need to use to create an easing effect on the drop down of the second tier. Hovering over 'Button 2' will bring up the sub-menu.
Notice how its a one jump movement. How would I slow it down? Maybe look like the sub-menu is sliding out from under 'Button 2'?
Should I use javascript or css?
Use this code..
HTML:
<ul id="menu">
<li>Link 1
<ul class="submenu">
<li>Sub Link 1.1
<ul class="submenu">
<li> Sub Link 1.1.1</li>
<li> Sub Link 1.1.2</li>
</ul>
</li>
<li>Sub Link 1.2</li>
<li>Sub Link 1.3</li>
</ul>
</li>
<li>Link 2
<ul class="submenu">
<li>Sub Link 2.1
<ul class="submenu">
<li> Sub Link 2.1.1</li>
<li> Sub Link 2.1.2</li>
</ul>
</li>
<li>Sub Link 2.2</li>
</ul>
</li>
</ul>
CSS:
.submenu
{
display: none;
}
#menu li ul{
margin-left:15px;
}
JQUERY:
$(document).ready(function () {
$('#menu > li').hover(function () { $(this).find("ul:first").show(); },
function () { $(this).find("ul:first").hide(); }
);
$('#menu li li').hover(function () {
$('ul:first', this).each(function () {
var p = $(this).parent();
$(this).css('top', p.position().top)
.css('left', p.position().left + p.width())
.show();
});},
function () { $('ul:first', this).hide(); }
);
});