Capture ul tag under li through JS

<ul class="first">
	<li class="lfirst"><a href="">Menu</a></li>
	<li class="lfirst">
		<a href="">Menu</a>
		<ul class="second">
			<li>Menu 01</li>
			<li>Menu 02</li>
			<li>Menu 03</li>
		</ul>
	</li>
	<li class="lfirst"><a href="">Menu</a></li>
	<li class="lfirst">
		<a href="">Menu</a><a href="">Menu</a></li>
	<li class="lfirst">
		<a href="">Menu</a>
		<ul class="second">
			<li>Menu 01</li>
			<li>Menu 02</li>
			<li>Menu 03</li>
		</ul>
	</li>
	<li class="lfirst"><a href="">Menu</a></li>
</ul>

I am trying to capture ul element within li through JavaScript.

This is the incomplete code I have:

const lFirsts = document.querySelectorAll('.lfirst')
console.log(lFirsts)

lFirsts.forEach(lFirst => {
  lFirst.addEventListener('mouseover', () => {
    // const CaptureLiClassName = lFirst.className
    const getChildUl = 
  })
})

Empty spaces are also treated as children, so to be more accurate I am not attempting those next or sibling concepts of the parent element in DOM.

how should I capture ul under const getChhildUi?

Not sure what you are achieving but you can get all the ul in the lfirst by

document.querySelector(".lfirst .ul)

2 Likes

This works →

Logic breaks when CSS top property is used:

30px is a magic number and should be avoided. A drop down menu usually needs to be at top:100% to clear the main item. Then it doesn’t matter if the menu height is changed.

You don’t need to find the nested ul as you already have found the list parent on mouseover so just add the class straight to the list parent and let css find it. e.g. .active ul {top:100%;}

Rough demo:

Of course no Javascript at all is needed to do the above but I guess you are doing it as a learning exercise :);

2 Likes

Hello @PaulOB

There are a few things that are not clear to me in your demo, but before asking questions I will first analyze them. Thank you so much.

1 Like

Hi, there @PaulOB,

But I think that the current one is dependent on JavaScript, Right? Would it be possible to modify my original code so that I can understand what changes you have implemented?

No, it can all be done with css.

Just remove the js and uncomment the hover css.

The only thing that won’t show is the shadow but that can be put back in the css hover rule

1 Like

Sir,

What is happening here →

/* arrows */
.nav li a:first-child:not(:last-child) {
padding-right: 20px; /* make space for arrows*/
}

That provides the arrow automatically to any list that contains a drop-down.

It styles any anchor that is the first child of a list item but not a last child (I could have used not only child instead).

The reason is that all the anchors inside a list are only child items except when there is a drop-down and then the anchor is a first child only and so you can attach an arrow to it.

This saves having to add a class to the anchor in order to draw the arrow.

1 Like
.first li:hover > a,

.first a:hover {

background: aquamarine;

}

why have taken this →

.first a:hover

If you look at my menu you will see that when you move the cursor down the drop-down menu the top level item stays highlighted even though you are not over the menu item.

The line of code above says that when you hover a list element the direct child anchor should be aquamarine. The child selector is needed otherwise all the anchors in the drop down would be aquamarine.

The a:hover rule is the rule that changes the background of the anchors in the drop-down (it also works on the top level but is superfluous as the li:hover > a rule will effect a change first).

If you don’t use the li:hover > a rule then the top menu item reverts back to its original color as you traverse the drop-down which will look odd.

1 Like

But I could not grasp if this is targeting anything. The same can be verified by eliminating that part. Please correct me in case I have difficulty understanding the objective.

Yes you are right in that it’s not really needed in that context as the li:hover a rule works the same in that structure.

There is always a list around an anchor so the a:hover rule is superfluous in that structure and could be removed. :slight_smile:

1 Like

CSS can be used to create basic drop-down menus, and in may cases, it may be sufficient for the needs.

I commented on the arrow and JS part, and understand your code:

/* arrows */
/* .nav li a:first-child:not(:last-child) { */
  /* make space for arrows*/
  /* padding-right: 20px;  */
/* } */
/* .nav li a:first-child:not(:last-child):after {
  content: "";
  position: absolute;
  right: 3px;
  top: 50%;
  margin-top: -6px;
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 6px solid red;
  transition: 0.5s ease;
}
.nav li:hover a:first-child:not(:last-child):after {
  transform: rotate(180deg);
} */

/* js adds active class so testing here */

/* .first > li.active > ul {
  box-shadow: 0 0 40px 40px rgba(0, 0, 0, 0.5);
} */

Focus can be moved away from the li element by clicking anywhere else on the page. When the focus is moved away from the li element, the styles defined in the CSS rule .first > li:focus-within > ul will no longer be applied to its child ul element. and a drop-down will disappear.

top: 100%
which means the ul element will be positioned below its parent li element, with 100% of the parent’s height being used as the distance from the top of the parent.

But as soon as increase this 200% or increase

margin-top: 10px;

The anticipated results fail to deliver, I think we cant achieve that. The same was an issue in my JS version else it was working.

The point you are missing is that if you move the absolute dropdown to anything greater than 100% then you will no longer be hovering (or mouseover) on the parent list element (which is the trigger) so the hover is lost and the menu disappears (or with js the class is removed and the element disappears).

Moving the dropdown to 200% makes no sense anyway as it will be too far away from the trigger element and would be a nightmare for users to navigate.

There are ways to move the dropdown further away from the trigger element but I think it is a UI mistake. You can add some padding top to the ul so that although it remains attached to the parent list it looks like its moved away.

Another example.

2 Likes
z-index: -1;
pointer-events: none;

Sir, you have set the above two, and then you reset pointer-events: initial;

But z-index is not reset to auto or a +ve number in this →

.first > li:hover > ul, 
.first > li:focus-within > ul

will it not make drop-down transparent in some situations?

I may be wrong but when we show a drop-down z-index should be +ve(or auto) then.

I gave the dropdown a negative z-index so that it slides down behind the nav menu item. If it was a positive number it would slide down in front which looks bad.

e.g. It would look like this at half way.

Screen Shot 2023-02-06 at 18.48.11

I used a negative z-index to pull the dropdown behind the top level anchors so it drops from behind like this:

Screen Shot 2023-02-06 at 18.48.30

The negative z-index will not affect the stacking of the dropdown menu in relation to other items outside the menu because I put a high z-index on the main ul.

.first {
  display: flex;
  position: relative;
  z-index: 99;
}

Note that the trigger item has a position relative but no z-index applied:

.first > li {
  position: relative;
  flex: 1 0 0;
  border-right: 1px solid #000;
}

That means the z-index value will be auto by default.

This allows the nested ul dropdown to fall behind that item. If the code above had a z-index value (any z-index value other than auto) then you could not place any children behind it because once an element has a z-index other than auto then no child can be hidden behind that item (in css terms it is said to become atomic).

The pointer-events:none is required because although you can’t see the menu it is still there on the screen and would block any interaction with elements that happened to be in the way. In your codepen example you can see this in effect if you add some anchors underneath the menu and then try to hover them each in turn. You will see that where a dropdown menu item would be shown you cannot click the anchor.

For example I added these anchors to your code.
Screen Shot 2023-02-06 at 19.04.17

If you do the same in your demo and then run the cursor along the words 'hello" you will see that some words can’t be clicked.

1 Like

Sir,

The actual Page is in real life are very complicated(Below is a different HTML):


In the given CSS code, the .menuholder class has a border-bottom with a z-index value of 0 by default. The .hmenu > li > ul class, which is the dropdown menu, has a z-index value of -1. This means the dropdown menu will be below the border of the .menuholder element in the stacking order.

The h1 tag is likely being displayed above the dropdown menu because it also has a higher z-index value than -1.

Outcome →

Here:

.hmenu > li:hover > ul, 
.hmenu > li:focus-within > ul {
	top:100%;
	margin-top: 1px;
  opacity: 1;
	/* z-index: 1; */
	pointer-events: initial;
}

Unless I reset the z-index to auto or +ve value

.hmenu > li:hover > ul, 
.hmenu > li:focus-within > u

↑ it is low in the food chain of the z-index( Since it is demoted by putting z-index: -1; in .hmenu > li > ul ) as compared to h1 tag and the said border.

You don’t have to live in the mess of complication.

Frequently simplification needs to occur first, so that entanglements preventing further progress are removed.

3 Likes

No that’s not correct.

A child element cannot be under the borders and background of a parent element that has a z-index other than auto. The parent element becomes atomic and its child can’t escape the parents stacking level.

As you can see from this demo the drop-down lies on top of the red border.

No, you still haven’t grasped the concept of z index properly. :). The drop-down menu could have a z index of a million but it’s the parent of that element that controls the stacking order of elements outside that context. If the parent is z index:1 then the drop down menu will not overlap any other elements outside that context that have a z-index of 2 (or 1 if later in the cascade).

For stacking logic you need to go back to the furthest ancestor that has a position and z index defined.

You don’t show all the css for the elements in your screenshot but you would need to check the whole structure of each separate block to determine what goes where.

1 Like