原生 details+summary 实现多级分类折叠,无需 JS、语义清晰、支持键盘导航;分类数据应为扁平结构(含 id/parent_id),通过 filter 动态渲染子集;避免 DOM 层级依赖与 class/id 拼接筛选。

用 details + summary 实现原生多级分类折叠,别手写 JS 控制展开状态
原生 details 支持嵌套、无需 JS、语义清晰、自带焦点与键盘导航(Space/Enter 展开),比自己用 div + click + classList.toggle 更轻量也更可靠。Firefox 和 Chrome 都已稳定支持多年,Safari 16+ 也无问题。
常见错误是把 button 塞进 summary:这会破坏原生行为(比如点击后不收起、键盘无法触发)。summary 本身已是可聚焦、可交互元素,加 button 反而导致事件冒泡混乱或无障碍读屏重复播报。
- 移动端点击区域小?给
summary加padding: 12px 16px并设cursor: pointer - 想换箭头图标?必须用
details[open] > summary::after,不能只靠::marker(Firefox 不支持) - 需要动态加载子类?
details元素的toggle事件可监听,但异步内容建议先占位再替换 innerHTML,避免 DOM 重排抖动
三级联动下拉选择器怎么绑定数据依赖?关键在 data-parent-id 而非 DOM 层级
很多项目把二级分类塞进一级 select 的 change 回调里硬编码加载,结果三级选不了、回退时状态错乱。根本问题是把 UI 层级当成了数据关系——实际应靠扁平数组 + parent_id 字段建模。
后端返回的分类列表应该是类似这样的扁平结构:
[{"id":1,"name":"手机","parent_id":0},{"id":101,"name":"iPhone","parent_id":1},{"id":102,"name":"安卓","parent_id":1},{"id":1011,"name":"iPhone 15","parent_id":101}]前端用 Array.filter(item => item.parent_id === currentId) 拿子集,而不是靠 levelTwo.options 的 DOM 索引去查。
立即学习“前端免费学习笔记(深入)”;
-
levelOne初始化后,保存当前选中id(比如selectedLevelOne = 1) -
levelTwo的change事件里,用selectedLevelOne查出所有parent_id === selectedLevelOne的项并渲染 -
levelThree同理,依赖levelTwo.value,而非监听levelTwo的 DOM 变化 - 用户切换一级时,二级、三级要清空并重置
disabled状态,避免残留旧值提交
Tab 页内双列滚动区域为什么不能直接给 .tab-pane 加 d-flex?
Bootstrap 4.6 的 .tab-pane 默认靠 display: none / block 切换显隐,而 d-flex 工具类带 !important,会覆盖这个逻辑,导致非激活 Tab 内容仍占据空间、宽度异常、甚至滚动条错位。
正确做法是:在 .tab-pane 内部嵌一层 <div class="h-100">,再用 <div class="row h-100"> 包住两列,每列加 d-flex flex-column + overflow-auto。这样高度继承链完整,滚动互不干扰。
- 第一列放分类树(
details列表),第二列放商品列表(section+article) - 两列都设
flex-grow-1,让主体区域撑满剩余高度 - 避免给
.tab-pane设固定高度——它应由父容器控制,否则在响应式断点下容易溢出或留白
商品列表筛选和分类联动时,data-category 必须唯一且不可拼接
当用户从分类树点击「手机 → iPhone → iPhone 15」,最终筛选条件不是字符串拼接 "phone-iphone-iphone15",而是取叶子节点的 id 或唯一标识(如 data-id="1011"),然后用它查商品的 data-category-id="1011"。
用 class 或 id 存分类路径是典型反模式:class="cat-phone cat-iphone" 会导致多选时匹配爆炸;id="prod-1011" 则无法复用(id 必须唯一,不能多个商品共用同一 id)。
- 商品 DOM 上只存一个
data-category-id="1011",JS 过滤时用element.dataset.categoryId === targetId - 若需支持“显示该分类及其所有子类”,得提前构建好父子映射表(比如
{1011: [1011, 101]}),而不是运行时递归遍历 DOM - 点击分类树某项后,不要手动
scrollIntoView商品区——用锚点href="#laptop"更自然,且支持浏览器前进后退
id/parent_id 的数组好操作。



















