CSS 如何选择父容器中嵌套层级最深的 ul 元素?
结论
在给出的 HTML 中,可以用固定层级的 CSS 选择器选中包含 Section 1.2.1.1 的 <ul>。如果列表深度不固定,需要比较所有分支并选出整个容器中最深的 <ul>,CSS 没有通用选择器能直接完成这种比较,可以用 JavaScript 计算深度并添加 class。
当前结构可以直接指定第四层列表
以 .listCan 下的顶层 <ul> 为第一层,目标 <ul> 位于第四层:
.listCan > ul > li > ul > li > ul > li > ul {
color: red;
}
在题目的结构中,这条规则会选中包含 Section 1.2.1.1 的 <ul>。它依赖列表按 ul > li > ul 的方式嵌套,且目标位于第四层。
如果以后出现第五层列表,这条规则仍然匹配第四层,不会自动改为选择第五层。由于 color 会继承,第五层文字也可能变红,但这不表示选择器匹配了第五层。
为什么排除子列表不能筛出全局最深层
在支持 :has() 的浏览器中,可以这样选择所有不含后代 <ul> 的列表:
.listCan ul:not(:has(ul)) {
color: red;
}
它选中的是每个分支末端的 <ul>。在给出的结构中,匹配结果包括包含下列条目的列表:
- Section 1.1.1
- Section 1.2.1.1
- Section 2.1.1
- Section 2.2.1 和 Section 2.2.2
这些列表都没有更深的子列表,但嵌套深度并不相同。
题目中的子列表位于 <li> 内部,因此检查 <ul> 的直接子元素是否为 <ul>,不能正确判断是否还有嵌套列表。:has(ul) 可以检查任意后代,:has(> li > ul) 则可以检查紧接着的下一层列表。
原选择器中的 li > ul > li > ul 检查的是一段固定长度的后代结构,无法据此判断某个列表是否比其他分支中的所有列表都深。
深度变化时,用 JavaScript 标记最深的列表
下面的代码按 <ul> 的嵌套层数计算深度,给达到最大深度的列表添加 deepest-ul:
function markDeepestLists(container) {
if (!container) return;
const lists = [...container.querySelectorAll('ul')];
let maxDepth = 0;
const entries = lists.map((ul) => {
let depth = 1;
for (
let parent = ul.parentElement;
parent && parent !== container;
parent = parent.parentElement
) {
if (parent.tagName === 'UL') {
depth += 1;
}
}
maxDepth = Math.max(maxDepth, depth);
return { ul, depth };
});
for (const { ul, depth } of entries) {
ul.classList.toggle('deepest-ul', depth === maxDepth);
}
}
markDeepestLists(document.querySelector('.listCan'));
配套样式:
.listCan ul.deepest-ul {
color: red;
}
在题目的结构中,只有包含 Section 1.2.1.1 的 <ul> 会获得这个 class。如果多个分支具有相同的最大深度,它们都会被标记。
使用时需要注意
在列表 DOM 创建完成后调用函数,例如将脚本放在相关 HTML 后面。动态增加、删除或移动列表后,需要再次调用函数更新标记。
原 HTML 片段末尾还缺少容器的 </div>,实际使用时应补齐。如果页面中有多个 .listCan,可以逐个处理:
document.querySelectorAll('.listCan').forEach(markDeepestLists);
备注:内容仅供参考。
