最轻量兼容的递进展示方案是用 display 切换配合 max-height + opacity 过渡,结合 aria-hidden/tabindex 保障可访问性,并用 IntersectionObserver 实现滚动触发;IE9-需降级为纯显隐。

用 display 切换 + CSS 过渡实现逐级展开
最轻量、兼容性最好的递进展示方式,适合纯前端控制的静态内容。核心是把每级内容包裹在独立容器中,初始设为 display: none,再通过 JS 控制其 display 值,并配合 transition 实现淡入/高度变化效果。
注意:CSS transition 对 display 无效,所以必须搭配 opacity 或 max-height 模拟过渡。推荐用 max-height 配合 overflow: hidden:
.step-content {
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.3s ease-out, opacity 0.2s ease-out;
}
.step-content.active {
max-height: 500px; /* 需预估最大高度,或用 JS 动态赋值 */
opacity: 1;
}- 不要设
max-height: 100%—— 百分比在max-height中不触发过渡 - 若内容高度差异大,用 JS 获取
scrollHeight后再设max-height更稳妥 - IE10+ 支持
max-height过渡,IE9 及以下需降级为纯显示/隐藏
用 aria-hidden 和 tabindex 保障可访问性
视觉上递进展示时,屏幕阅读器用户容易迷失当前在哪一级。不能只靠 CSS 隐藏,必须同步更新语义属性。
每次展开某一级,应确保:
立即学习“前端免费学习笔记(深入)”;
- 前序所有未激活内容的容器设
aria-hidden="true" - 当前激活内容设
aria-hidden="false",且包含tabindex="0"以便键盘聚焦 - 为每级标题添加
aria-controls指向对应内容 ID,例如:第二步
否则视障用户可能听到全部内容被一次性读出,或焦点卡死在不可见区域。
用 IntersectionObserver 触发滚动递进
当“递进”依赖用户滚动行为(如长页面分段浮现),IntersectionObserver 比监听 scroll 事件更高效、无卡顿。
典型用法是给每级内容加类名 js-step,初始化观察器后动态添加激活类:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('active');
// 可选:停止观察已激活项
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.js-step').forEach(el => observer.observe(el));
-
threshold: 0.1表示内容 10% 进入视口即触发,避免用户还没看到就激活 - 务必调用
unobserve(),否则重复进入/退出会反复触发,尤其在快速滚动时 - 不支持 IE,需用
scroll+getBoundingClientRect()降级
避免用 visibility: hidden 替代 display: none
visibility: hidden 保留元素占位,会导致后续内容布局错乱、点击穿透、焦点仍可抵达 —— 这些都不是“递进展示”该有的行为。
真实场景中常见问题:
- 用户点了“下一步”,但下一级内容在视觉上出现,实际 DOM 位置被上一级空盒子撑开,造成空白断层
- 下一级按钮虽不可见,但键盘
Tab仍能聚焦到它,违反操作流逻辑 - 移动端触摸区域重叠,误触隐藏按钮
只要不是为了做“隐藏但保持布局”的动画中间帧,一律优先用 display: none 控制显隐。递进的本质是内容阶段性呈现,不是视觉暂存。











