真正能用的位移过渡必须绑定在独立滚动容器内、仅当 scrolltop === 0 时生效,并由 js 控制 css 自定义属性驱动;直接对 body 或 html 使用 transform: translatey() 会破坏原生下拉刷新机制,导致 ios safari 悬停不回弹、安卓 chrome 双重重载。

直接用 transform: translateY() 拉内容是错的——它不触发原生下拉刷新,iOS Safari 松手后不回弹,安卓 Chrome 还可能双重重载。真正能用的位移过渡,必须绑定在独立滚动容器内、仅当 scrollTop === 0 时生效,并由 JS 控制 CSS 自定义属性驱动。
为什么不能对 body 或 html 做 translateY
浏览器会把整个视口当做一个“浮动层”,transform: translateY() 会破坏原生滚动惯性与阻尼。iOS Safari 下松手后页面悬停、无法自然回弹;安卓 Chrome 可能触发整页橡皮筋效果,甚至二次刷新。真机上最典型的症状是:手指一抬,内容卡在半空不动。
- 必须作用于一个独立的、带
overflow-y: auto的容器(如<div class="refresh-wrapper">)<li>该容器需显式设 <code>position: relative,为指示器提供定位上下文 - 禁止在
html或body上写transform、will-change或overflow相关样式,否则指示器锚点错乱 - 给容器设
transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.4, 1),只对transform过渡,不加all - 用 CSS 自定义属性记录进度:
--pull-progress: 0,JS 在touchmove中动态更新它 - CSS 里写
transform: translateY(calc(var(--pull-progress) * 60px)),60px 是阈值,数值可调 - 拖拽中移除
transition(防止边拖边动画),松手后再加回来 -
touchend里别立刻设--pull-progress: 0——先判断是否超阈值(比如progress > 0.7),再决定是触发加载还是回弹 - 触发加载后,要同步设
--loading: 1,并在 CSS 里用@keyframes spin配合transform-origin: center转圈,否则旋转中心偏移 - 回弹动画结束时,必须监听
transitionend事件,且校验event.propertyName === 'transform',再重置自定义属性,否则连续操作会状态残留 - 用 DevTools 检查指示器的
offsetParent,如果不是你预期的.refresh-wrapper,说明中间缺了position: relative - 指示器本身别用
top: 0,改用top: calc(-100% + 1px),绕过 iOS 弹性滚动初始定位抖动 - 确保容器有
will-change: transform,否则动画掉帧严重;但它的父级不能有同属性,否则嵌套渲染层冲突
如何让位移过渡既跟手又平滑
核心是「JS 控制变量 + CSS 响应过渡」,而不是靠 JS 直接改 style.transform。这样浏览器能提前优化渲染路径,避免每帧重排。
松手后回弹不同步?检查这三处
常见现象是:松手了,内容还在动,但指示器已缩回,或加载圈转了一半就停了。本质是 JS 和 CSS 状态没对齐。
真机调试时指示器“瞬移”或消失
不是动画写错了,而是 iOS Safari 渲染层被意外剥离。它看到祖先元素有 transform、filter 或 will-change,就会新建 containing block,导致 position: absolute 指示器锚点漂移。
最难的从来不是写出那个 translateY,而是让 JS 的位移计算、CSS 的过渡时机、浏览器的滚动链路三者严丝合缝——少一个条件,真机上就是“看起来差不多,但总差一口气”。











