最稳妥方案是用 width + transition,须避开 width:0、display:none 和 flex-basis 动画三类陷阱:width:0 导致内容坍缩、测量失准、动画跳变;flex-basis 参与布局计算,动画易被跳过;JS 立即读写 offsetWidth 会强制同步重排,应改用 requestAnimationFrame 或 CSS 状态驱动。

直接用 width + transition 是最稳妥的方案,但必须避开 width: 0、display: none 和 flex-basis 动画这三类典型陷阱。
为什么不能设 width: 0 再过渡?
浏览器对 width: 0 的处理会导致内部内容坍缩,offsetWidth 读数为 0,JS 测量失准;更关键的是,padding、border、文字渲染全被压扁,transition 插值失去中间状态,动画“啪”一下跳到目标值。
- 收起态推荐
width: 32px(刚好容纳放大镜图标) - 展开态用
width: min(240px, 80vw)兼顾小屏和大屏 - 必须加
overflow: hidden和white-space: nowrap,否则文字换行会打断动画节奏 - 父容器记得设
box-sizing: border-box,避免padding在动画中被二次拉伸
为什么别碰 flex-basis 动画?
flex-basis 不是纯视觉属性,它参与 Flex 布局计算阶段。一旦父容器是 display: flex,且子项没锁死 flex-grow: 0 和 flex-shrink: 0,动画帧就会被浏览器跳过——表现为卡顿、图标被挤出、文字突然换行。
- Chrome 110+ 可能勉强支持,Firefox/Safari 基本不认
transition: flex-basis - 即使写了
transition: flex-basis 0.3s,也大概率只在激活瞬间触发一次重排,后续无动画 - 真正要伸缩,就老实用
width,前提是搜索框不依赖 Flex 自动分配逻辑
JS 控制类名时容易卡顿的真相
点击后立刻 el.classList.add('expanded'),又马上读 el.offsetWidth,会强制同步重排,动画直接中断。
- 正确做法:用
getComputedStyle(el).width读值前,包一层requestAnimationFrame延迟一帧 - 更推荐放弃 JS 测量,改用 CSS 原生状态驱动,比如
:focus-within或:has(input:focus)(Chrome/Firefox/Edge 已稳定支持) - 若需兼容老浏览器,可在 JS 中先读一次
el.offsetWidth强制触发布局,再改 class —— 但仅限一次,禁止连读连写
移动端点击不到输入框?热区没扩够
收起态 width: 32px 通常只够放图标,用户点不到 input 本身,iOS Safari 尤其容易 focus 失败。
- 给
input加padding-left: 32px,再用::before绝对定位图标,左侧空白区域也能触发聚焦 - 绝对不要用
opacity: 0+pointer-events: none隐藏 input —— 这会让键盘在 iOS 上根本打不开 - 按钮图标的
z-index要低于 input,或加pointer-events: none,否则会遮挡焦点热区
真正难的不是写几行 transition,而是让宽度变化时文字不换行、图标不位移、焦点不丢失——这些全靠 white-space、overflow、box-sizing 和触发时机共同约束。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











