如何创建仅在悬停在菜单上后悬停时保持不透明的子菜单,否则则不然?



这个问题的演示在这里:https://jsfiddle.net/cwxLpf7j/62/

我目前正在做一个项目,我想要一个具有特定设计菜单的导航栏。此菜单是这样的,有几个菜单项和一个子菜单。当我将鼠标悬停在任何菜单项上时,此子菜单(绝对定位)将使用 JavaScript 移动到该菜单项下方。我已经完成了代码的移动部分。

但是,有一个小问题。我已经在子菜单的不透明度上使用过渡来隐藏(不透明度:0)并在需要时显示(不透明度:1)它。因此,我将事件侦听器放在菜单项上,效果很好。但是,现在的问题是,当我离开(鼠标离开)菜单项并进入子菜单本身时,子菜单消失了,我无法对子菜单中的按钮/链接执行任何操作。现在,我无法将事件侦听器放在子菜单本身上,因为它存在但透明(当不需要时会触发悬停事件)。我还尝试使用"display:none",但这破坏了不透明度的美丽过渡。

我希望你理解。我创建了一个我在这里所做的事情的小演示:https://jsfiddle.net/cwxLpf7j/62/。

无论如何,这是代码:

const listItems = document.querySelectorAll("li");
const box = document.querySelector(".box");
// This portion adds event listeners to each item in the list
// which hide or show (change opacity) of the box below.
listItems.forEach((item, index) => {
item.addEventListener("mouseenter", () => {
box.style.opacity = 1;
})
item.addEventListener("mouseleave", () => {
box.style.opacity = 0;
})
})
// If I add hover event on the box itself, it will trigger
// without actually having to hover over the list item. Also
// I tried playing with display:none to hide the box fully when
// the list is not being hovered. But that caused to destroy
// the transition on the opacity fully.
// UNCOMMENT THIS PORTION OF CODE TO SEE WHAT I DID
// box.addEventListener("mouseenter", ()=>{
//     box.style.opacity = 1;
// })
// box.addEventListener("mouseleave", ()=>{
//     box.style.opacity = 0;
// })
.box {
width: 300px;
background-color: black;
color: white;
padding: 20px;
opacity: 0;
transition: 0.5s;
}
ul {
list-style: none;
padding: 0;
margin: 4px;
}
li {
display: inline;
padding: 5px 15px;
background-color: lightblue;
cursor: pointer;
}
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<div class="box">
<h1>Hello</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati impedit, ad. Accusamus tenetur vel praesentium quas vero, voluptates ipsam vitae officia, ut culpa dolor porro, officiis incidunt aspernatur nostrum facilis!</p>
</div>

乍一看,这似乎可以通过CSS实现:

.box {
width: 300px;
background-color: black;
color: white;
padding: 20px;
opacity: 0;
transition: 0.5s;
/* to ensure that hovering the sub-menu doesn't trigger the
sub-menu to reveal itself while hidden: */
pointer-events: none;
}
ul {
list-style: none;
padding: 0;
margin: 4px;
}
/* here use the :is() pseudo-class function, along with the
adjacent-sibling combinator ('+') to style the .box element
whenever the ul matches any of the pseudo-classes within
that function; we also couple this selector with the .box
class-selector to match those same states: */
ul:is(:hover, :active, :focus, :focus-within) + .box,
.box:is(:hover, :active, :focus, :focus-within) {
/* we revert the pointer-events to auto, to enable
interaction: */
pointer-events: auto;
/* and, of cousre, update the opacity as before: */
opacity: 1;
}
li {
display: inline;
padding: 5px 15px;
background-color: lightblue;
cursor: pointer;
}
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<div class="box">
<h1>Hello</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati impedit, ad. Accusamus tenetur vel praesentium quas vero, voluptates ipsam vitae officia, ut culpa dolor porro, officiis incidunt aspernatur nostrum facilis!</p>
</div>

JS小提琴演示。

上述方法的警告是,.box元素的显示和交互性是基于与<ul>的交互,而不是其后代<li>元素,因为CSS中(目前)还没有父选择器。

但是,可以使用 JavaScript 修改此方法,以应用或删除父<ul>上的类名或其他属性值,如下所示:

document.querySelectorAll('li').forEach(
(elem) => {
elem.addEventListener('mouseenter', () => {
elem.parentNode.classList.add('isInteractive');
});
elem.addEventListener('mouseleave', () => {
elem.parentNode.classList.remove('isInteractive');
});
});
.box{
width: 300px;
background-color: black;
color: white;
padding: 20px;
opacity: 0;
transition: 0.5s;
pointer-events: none;
}
ul {
list-style: none;
padding: 0;
margin: 4px;
}
/* I added this rule purely to offer a visual demonstration of the
class-name "isInteractive" being added and removed; it's in no
way required for the demo to work: */
ul.isInteractive {
background: linear-gradient(90deg, #fff0, lime);
}
/* modified the selector, below, to style the ".box" on interaction
with the <ul> only once the "isInteractive" class is added to the
<ul>: */
ul.isInteractive + .box,
.box:is(:hover, :active, :focus, :focus-within) {
pointer-events: auto;
opacity: 1;
}
li {
display: inline;
padding: 5px 15px;
background-color: lightblue;
cursor: pointer;
}
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<div class="box">
<h1>Hello</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati impedit, ad. Accusamus tenetur vel praesentium quas vero, voluptates ipsam vitae officia, ut culpa dolor porro, officiis incidunt aspernatur nostrum facilis!</p>
</div>

JS小提琴演示。

带有上述代码的一个警告,专门与此选择器规则集有关:

ul:is(:hover, :active, :focus, :focus-within) + .box,
.box:is(:hover, :active, :focus, :focus-within) { ... }

如果浏览器不理解任何规则,那么它将丢弃谁的规则集;因此,出于兼容性目的,可能值得将其键入到较旧的格式:

ul:hover + .box,
ul:active + .box,
ul:focus + .box,
ul:focus-within + .box,
.box:hover,
.box:active,
.box:focus,
.box:focus-within { ... }

由于上述方法似乎不是特别易于键盘访问,因此我添加了一些补偿,似乎可以更好地支持键盘导航,尽管这导致我将<li>文本包装在<a>标签中以启用选项卡导航。此更新方法如下:

document.querySelectorAll('li').forEach(
(elem) => {
elem.addEventListener('mouseenter', () => {
elem.parentNode.classList.add('isInteractive');
});
elem.addEventListener('mouseleave', () => {
elem.parentNode.classList.remove('isInteractive');
});
});
.box {
width: 300px;
background-color: black;
color: white;
padding: 20px;
opacity: 0;
transition: 0.5s;
pointer-events: none;
}
ul {
list-style: none;
padding: 0;
margin: 4px;
}
ul:is(:hover, :active, :focus, :focus-within)+.box,
.box:is(:hover, :active, :focus, :focus-within) {
pointer-events: auto;
opacity: 1;
}
li {
display: inline;
padding: 5px 15px;
background-color: lightblue;
cursor: pointer;
}
/* this is to allow links to be less-visible within the <li>
elements, if you don't want them to dominate your theme
although it may be considered more accessible if you do
allow your users to easily identify <a> elements: */
li a {
color: inherit;
text-decoration: inherit;
font-weight: inherit;
}
/* here we style all <a> elements (modify to your aesthetic) to have
default presentation in order to allow transitions: */
a {
outline: solid transparent;
outline-offset: 0;
transition: all 0.2s ease-in-out;
}
/* we transition to these styles in order to allow some movement
to occur to draw the eye: */
a:is(:focus, :active, :hover) {
outline: solid #0cc;
outline-offset: 0.2rem;
}
<ul>
<li><a href="#">Item 1</a></li>
<li><a href="#">Item 2</a></li>
<li><a href="#">Item 3</a></li>
</ul>
<div class="box">
<h1>Hello</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati impedit, ad. <a href="#">Accusamus tenetur</a> vel praesentium quas vero, voluptates ipsam vitae officia, ut culpa dolor porro, officiis incidunt aspernatur nostrum facilis!</p>
</div>

JS小提琴演示。

引用:

  • .CSS:
    • :focus-within.
    • :is().
    • :outline.
    • :outline-offset.
  • JavaScript:
    • Arrow Functions.
    • Element.classListAPI。
    • EventTarget.addEventListener().
    • Node.parentNode

我能想到的最简单的解决方案是将菜单和信息框封装到容器中,并侦听该容器上的事件。

const listItems = document.querySelectorAll("li");
const box = document.querySelector(".box");
const menu = document.querySelectorAll(".menu");
for(let i = 0; i < menu.length; i++)
{
menu[i].addEventListener("mouseenter", onMouseEvent, true); //event listener on all children as well
menu[i].addEventListener("mouseleave", onMouseEvent); //to make it simple we only listen on .menu box
}
function onMouseEvent(e)
{
const isMouseEnter = e.type == "mouseenter";
if (isMouseEnter && e.target.tagName != "LI")
return;
box.classList.toggle("show", isMouseEnter); //avoid using inline style
}
.box {
width: 300px;
background-color: black;
color: white;
padding: 20px;
opacity: 0;
transition: 0.5s;
display: inline-block;
}
.box.show
{
opacity: 1;
}
ul {
list-style: none;
padding: 0;
margin: 4px;
}
li {
display: inline;
padding: 5px 15px;
background-color: lightblue;
cursor: pointer;
}
.menu
{
background-color: pink;
display: inline-block;
}
<div class="menu">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<div class="box">
<h1>Hello</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati impedit, ad. Accusamus tenetur vel praesentium quas vero, voluptates ipsam vitae officia, ut culpa dolor porro, officiis incidunt aspernatur nostrum facilis!</p>
</div>

最新更新