
本文介绍一种纯 css + 少量 javascript 的优雅方案,实现自适应宽度侧边栏的流畅展开/收起动画——自动测量内容自然宽度、避免文本重排、兼顾性能与可维护性。
本文介绍一种纯 css + 少量 javascript 的优雅方案,实现自适应宽度侧边栏的流畅展开/收起动画——自动测量内容自然宽度、避免文本重排、兼顾性能与可维护性。
在构建响应式管理后台或仪表盘时,一个可折叠的侧边栏是常见需求。但单纯用 display: none 或 width: 0 配合固定值过渡,往往带来三大痛点:需手动设定初始宽度、内容区域文字随宽度变化而频繁重排、隐藏状态仍参与布局计算影响渲染效率。
理想的解决方案应满足三点:
✅ 宽度自适应:展开时完全贴合内容真实宽度(含内边距、字体、换行等);
✅ 动画平滑:使用 transition 实现 width 渐变,而非突兀切换;
✅ 视觉稳定:收起过程中菜单内容不重排、不闪烁,文字保持原始布局形态。
核心思路是:首次加载时动态测量菜单内容的自然宽度,并将其作为 width 的基准值写入内联样式;后续通过 classList.toggle() 控制 collapsed 状态,配合 CSS 过渡完成动画。
以下是完整实现:
<div class="container">
<div class="menu">
<div class="toggle">✕</div>
<div class="menu-content">
<div class="content">首页<br>用户管理<br>订单统计<br>系统设置</div>
</div>
</div>
<div class="main-content">主内容区:此处放置页面主体内容...</div>
</div>
.container {
display: flex;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
}
.menu {
background: #ffeb3b;
flex: none;
overflow: hidden;
}
.toggle {
cursor: pointer;
user-select: none;
padding: 12px;
font-weight: bold;
}
.menu-content {
transition: width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
overflow: hidden;
white-space: nowrap; /* 关键:防止收起时文字换行重排 */
}
.menu-content .content {
padding: 16px;
opacity: 1;
transition: opacity 0.4s;
/* 强制单行显示 + 溢出隐藏,确保视觉一致性 */
text-overflow: ellipsis;
overflow: hidden;
}
.container.collapsed .menu-content {
width: 0 !important; /* 覆盖 JS 设置的 width */
}
.container.collapsed .menu-content .content {
opacity: 0;
}
// 初始化:为每个菜单测量并设置自然宽度
function initMenuWidth() {
document.querySelectorAll('.menu-content').forEach(menuContent => {
const container = menuContent.closest('.container');
if (container.classList.contains('collapsed')) return;
const contentEl = menuContent.querySelector('.content');
// 获取 content 元素在展开状态下的自然宽度(含 padding)
const rect = contentEl.getBoundingClientRect();
const computedStyle = getComputedStyle(contentEl);
const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0;
const paddingRight = parseFloat(computedStyle.paddingRight) || 0;
const width = rect.width + paddingLeft + paddingRight;
// 写入 menu-content 的 width(非 content),使其成为过渡锚点
menuContent.style.width = `${Math.ceil(width)}px`;
});
}
// 绑定折叠按钮事件
document.querySelectorAll('.toggle').forEach(toggle => {
toggle.addEventListener('click', () => {
const container = toggle.closest('.container');
const menuContent = container.querySelector('.menu-content');
if (container.classList.contains('collapsed')) {
// 展开:恢复自然宽度
initMenuWidth();
} else {
// 收起:记录当前宽度后设为 0(触发 transition)
const currentWidth = menuContent.offsetWidth;
menuContent.style.width = `${currentWidth}px`;
// 强制重排,确保 transition 生效
void menuContent.offsetWidth;
menuContent.style.width = '0';
}
container.classList.toggle('collapsed');
});
});
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', initMenuWidth);
// 可选:监听窗口 resize(如需响应式支持)
window.addEventListener('resize', () => {
if (!document.querySelector('.container.collapsed')) {
initMenuWidth();
}
});
? 关键技巧说明:
- 使用 white-space: nowrap + text-overflow: ellipsis 防止收起过程中文字换行,消除“抖动感”;
- cubic-bezier(0.34, 1.56, 0.64, 1) 提供轻微弹性效果,比线性过渡更自然;
- JS 中先写入当前宽度再设为 0,并借助 void offsetWidth 强制浏览器同步重排,确保 transition 正确触发;
- .collapsed 类仅控制 width: 0 和 opacity: 0,不依赖 display: none,既保留语义结构,又避免 DOM 重绘开销。
该方案平衡了纯 CSS 的简洁性与 JavaScript 的灵活性,在不牺牲可访问性(如屏幕阅读器仍可读取隐藏内容)的前提下,实现了真正“智能”的侧边栏动画体验。










