Sheet动画是需JS驱动的移动端底部弹出菜单交互,仅用transform:translateY()会导致卡顿、无拖拽响应、无弹性回弹;必须结合fixed定位、touch事件监听、raf优化及键盘适配才能实现原生体验。

什么是 Sheet 动画,为什么不能只用 transform: translateY()
移动端底部弹出菜单(Sheet)不是简单地把元素从下往上移进来。真机上用户会明显感知卡顿、手势不跟手、回弹生硬,根本原因是纯 CSS transform 缺乏对触摸拖拽的响应能力,也无法自然衔接系统级的弹性回弹(如 iOS 的 overscroll 效果)。真正可用的 Sheet 必须支持:拖拽中断、松手自动吸附/关闭、惯性滚动、遮罩层透传点击、键盘弹起时自动收起。这些单靠 CSS3 动画做不到。
用 position: fixed + will-change: transform 做基础层定位
Sheet 容器必须脱离文档流,否则可能被其他 position: sticky 或 overflow: hidden 元素截断。固定定位是唯一稳妥选择:
.sheet {
position: fixed;
bottom: 0;
left: 0;
right: 0;
max-height: 80vh;
will-change: transform;
/* 防止 Safari 渲染异常 */
-webkit-overflow-scrolling: touch;
}-
max-height而非height: 100%,避免内容撑开后遮罩层失效 - 必须加
will-change: transform,否则 iOS Safari 下动画掉帧严重 -
-webkit-overflow-scrolling: touch是关键,否则内部滚动卡顿、无法滑动到底部
用 @keyframes + transition 控制入场/退场,但仅限“非交互”阶段
CSS 动画只负责「自动播放」的部分:比如点击按钮后展开、点击遮罩后收起。它不能响应手指拖拽——那得交给 JS。所以动画只定义两个状态:
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
@keyframes sheetEnter {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.sheet.open {
animation: sheetEnter 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}- 贝塞尔曲线选
cubic-bezier(0.34, 1.56, 0.64, 1),模拟 iOS 弹性入场,比ease-out更真实 - 退场不用动画,直接用
transition: transform 0.25s cubic-bezier(0.68, -0.55, 0.27, 1.55),负值 y 可实现“吸下去再弹回”的收起感 - 切勿在拖拽过程中动态改
animation,会导致状态错乱和重排
必须用 JS 监听 touchmove + touchend 实现拖拽逻辑
所有“用户拖着 Sheet 往上/往下拉”的行为,CSS 无能为力。核心是监听 touchmove 计算位移,并用 transform: translateY() 实时更新,同时判断松手后该吸附还是关闭:
立即学习“前端免费学习笔记(深入)”;
let startY = 0;
let startTransform = 0;
<p>sheet.addEventListener('touchstart', e => {
startY = e.touches[0].clientY;
startTransform = parseFloat(getComputedStyle(sheet).transform.split(',')[5]) || 0;
});</p><p>sheet.addEventListener('touchmove', e => {
const dy = e.touches[0].clientY - startY;
const newTransform = startTransform + dy;
// 限制在 [0, 300px] 区间内移动
sheet.style.transform = <code>translateY(${Math.max(0, Math.min(300, newTransform))}px)</code>;
});- 不能依赖
scrollTop或offsetTop,移动端 scroll 行为不可靠 - 必须用
getComputedStyle(...).transform解析当前位移,因为动画和拖拽共存时样式值会混合 - 吸附逻辑(如松手时
dy > 50px就关闭)必须在touchend中计算,不能在touchmove里触发 DOM 更新
实际效果好坏,取决于拖拽过程是否跳过主线程(需用 requestAnimationFrame 批量更新)、是否正确处理多点触控、以及键盘弹出时是否监听 resize 事件重置高度。这些细节漏掉一个,Sheet 就会显得“不像原生”。

















