我有一个可扩展的菜单,其中包含无限深度的嵌套子菜单。
<ul>
<li>One</li>
<li class="contains-submenu">Two
<ul>
<li>Two A</li>
<li class="contains-submenu">Two B
<ul>
<li>Two B I</li>
<li>Two B II</li>
<li>Two B III</li>
</ul>
</li>
<li>Two C</li>
</ul>
</li>
<li>Three</li>
<li class="contains-submenu">Four
<ul>
<li>Four A</li>
<li class="contains-submenu">Four B
<ul>
<li>Four B I</li>
<li>Four B II</li>
<li>Four B III</li>
</ul>
</li>
<li>Four C</li>
</ul>
</li>
</ul>
我想显示/隐藏任何级别的任何子菜单,而不影响任何其他子菜单的显示/隐藏状态,尤其是<的显示/隐藏状态em>子菜单的任何祖先。
我最初发现的是,如果我使用 click
事件监听器来切换类 .show-submenu
来显示或隐藏子菜单,当子菜单向下时(例如 Two B)被关闭,那么它的父菜单,子菜单 Two,也会关闭。
很明显,这种情况正在发生,因为几乎在我点击 Two B 的同时,点击事件也在 Two 上注册(...当然是这样 - Two B 嵌套在 Two 内部Two - 任何对 Two B 的点击也将注册为一次点击在二)。
我尝试使用 .stopPropagation()
修复此问题...但后来我意识到实际上这不是(至少我不认为它是) ) 当事件从 Two B 中通过 Two 向上冒泡时,单个事件在两个不同的元素上注册,但注册了两个不同的事件,一个在 Two 上,几乎紧接着另一个在 Two B 上。
我的解决方案如下:
var activateToggle = true;
function toggleSubmenu() {
if (activateToggle === true) {
this.classList.toggle('show-submenu');
activateToggle = false;
}
setTimeout(function(){activateToggle = true}, 100);
}
这是产生预期效果的合理方法吗?
或者是否有使用 event.target
或类似方法的更传统方法?
完整的工作示例:
var querySelector = 'ul > li > ul';
var selectedElements = [... document.querySelectorAll(querySelector)];
var activateToggle = true;
var initialPadding = 6;
var subsequentPadding = 18;
var level = 0;
while (selectedElements.length > 0) {
for (var j = 0; j < selectedElements.length; j++) {
selectedElements[j].parentNode.classList.add('contains-submenu');
selectedElements[j].style.marginLeft = (0 - (initialPadding + (level * subsequentPadding))) + 'px';
[... selectedElements[j].children].forEach(function(childElement){
childElement.style.paddingLeft = (initialPadding + ((level + 1) * subsequentPadding)) + 'px';
});
}
level++;
querySelector += ' > li > ul';
selectedElements = [... document.querySelectorAll(querySelector)];
}
var containsSubmenus = document.getElementsByClassName('contains-submenu');
function toggleSubmenu() {
if (activateToggle === true) {
this.classList.toggle('show-submenu');
activateToggle = false;
}
setTimeout(function(){activateToggle = true}, 100);
}
for (let i = 0; i < containsSubmenus.length; i++) {
containsSubmenus[i].addEventListener('click', toggleSubmenu, false);
}
ul {
margin-left: 0;
padding-left: 0;
font-family: arial, helvetica, sans-serif;
color: rgb(255, 255, 255);
background-color: rgba(0, 75, 165, 1);
list-style-type: none;
}
li {
padding-left: 6px;
font-size: 16px;
line-height: 32px;
}
ul ul {
display: none;
}
.show-submenu > ul {
display: block;
}
li.show-submenu {
height: auto;
}
.contains-submenu {
font-weight: 700;
cursor: pointer;
}
.contains-submenu ul {
font-weight: 300;
cursor: default;
}
.contains-submenu,
.contains-submenu ul {
background-color: rgba(255, 255, 255, 0.1);
}
<ul>
<li>One</li>
<li class="contains-submenu">Two
<ul>
<li>Two A</li>
<li class="contains-submenu">Two B
<ul>
<li>Two B I</li>
<li>Two B II</li>
<li>Two B III</li>
</ul>
</li>
<li>Two C</li>
</ul>
</li>
<li>Three</li>
<li class="contains-submenu">Four
<ul>
<li>Four A</li>
<li class="contains-submenu">Four B
<ul>
<li>Four B I</li>
<li>Four B II</li>
<li>Four B III</li>
</ul>
</li>
<li>Four C</li>
</ul>
</li>
</ul>
最佳答案
这是一个结合了 event.target
和 event.stopPropagation
的解决方案。
您可以检查 event.target
是否确实包含子菜单,如果是,则切换它并停止事件的传播。
这是预期的行为吗?
var querySelector = 'ul > li > ul';
var selectedElements = [... document.querySelectorAll(querySelector)];
var initialPadding = 6;
var subsequentPadding = 18;
var level = 0;
while (selectedElements.length > 0) {
for (var j = 0; j < selectedElements.length; j++) {
selectedElements[j].parentNode.classList.add('contains-submenu');
selectedElements[j].style.marginLeft = (0 - (initialPadding + (level * subsequentPadding))) + 'px';
[... selectedElements[j].children].forEach(function(childElement){
childElement.style.paddingLeft = (initialPadding + ((level + 1) * subsequentPadding)) + 'px';
});
}
level++;
querySelector += ' > li > ul';
selectedElements = [... document.querySelectorAll(querySelector)];
}
var containsSubmenus = document.getElementsByClassName('contains-submenu');
function toggleSubmenu(event) {
if (event.target.classList.contains('contains-sublist')) {
event.target.classList.toggle('show-sublist');
event.stopPropagation();
} else {
console.log("LEAF");
}
}
for (let i = 0; i < containsSubmenus.length; i++) {
containsSubmenus[i].addEventListener('click', toggleSubmenu, false);
}
ul {
margin-left: 0;
padding-left: 0;
font-family: arial, helvetica, sans-serif;
color: rgb(255, 255, 255);
background-color: rgba(0, 75, 165, 1);
list-style-type: none;
}
li {
padding-left: 6px;
font-size: 16px;
line-height: 32px;
}
ul ul {
display: none;
}
.show-submenu > ul {
display: block;
}
li.show-submenu {
height: auto;
}
.contains-submenu {
font-weight: 700;
cursor: pointer;
}
.contains-submenu ul {
font-weight: 300;
cursor: default;
}
.contains-submenu,
.contains-submenu ul {
background-color: rgba(255, 255, 255, 0.1);
}
<ul>
<li>One</li>
<li class="contains-submenu">Two
<ul>
<li>Two A</li>
<li class="contains-submenu">Two B
<ul>
<li>Two B I</li>
<li>Two B II</li>
<li>Two B III</li>
</ul>
</li>
<li>Two C</li>
</ul>
</li>
<li>Three</li>
<li class="contains-submenu">Four
<ul>
<li>Four A</li>
<li class="contains-submenu">Four B
<ul>
<li>Four B I</li>
<li>Four B II</li>
<li>Four B III</li>
</ul>
</li>
<li>Four C</li>
</ul>
</li>
</ul>
编辑:问题的答案
附加事件监听器的方式有两件事:
- 由于您正在监听包含子菜单的元素的点击,因此子列表中的每个元素将为每个祖先触发 1 个事件。 例如,单击“Two B II”(它有两个“contains-submenu”祖先)将触发两个事件。而且您不想两次切换任何标志。
- 你正在冒泡。因此,一旦您单击“叶子”(不包含子菜单的元素),您将首先处理叶子元素的事件。并且您不想在处理此类元素上的单击事件时执行任何操作。相反,你希望它冒泡。
出于这些原因,我在切换子列表并停止传播之前检查 classList。这样:
- 我们只想在事件的目标是“contains-submenu”元素时停止事件的传播,以防止冒泡到另一个“contains-submenu”祖先。
- 当目标是“叶子”元素时,我们不想阻止冒泡。
- 如果事件目标不是“contains-submenu”元素,我们不想切换任何子列表。
关于javascript - 当 Event.stopPropagation 不起作用时,如何防止在祖先元素上注册相同的事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46913283/