uni-app中需用requestAnimationFrame手动计算抛物线轨迹并逐帧更新位置,通过uni.createSelectorQuery()获取实时节点坐标,避免CSS动画失真与多端兼容问题。

uni-app 里怎么用 requestAnimationFrame 做购物车抛物线动画
直接用 CSS 动画做不到真实抛物线——它只能线性或贝塞尔曲线,而「从商品列表飞进购物车」需要带重力感的弧线。核心是手动算点 + requestAnimationFrame 逐帧更新位置。
常见错误是直接套用 Web 端的 getBoundingClientRect() 坐标逻辑,但在 uni-app 的多端环境里,iOS、Android、小程序对节点尺寸和坐标系的返回值差异很大,尤其在页面滚动后没重新计算导致飞偏。
- 必须用
uni.createSelectorQuery()获取真实节点位置,且要在动画触发前调用.exec(),不能缓存旧坐标 - 抛物线公式推荐用「时间参数化」形式:
y = y0 + vy * t + 0.5 * g * t²,其中g初始设为1.2左右(纯视觉调节,不是物理模拟) - 总动画时长控制在
400ms–600ms,太短像瞬移,太长用户会感知卡顿 - 安卓真机上
requestAnimationFrame可能掉帧,建议加兜底:超时600ms强制结束并跳到终点
为什么不能只靠 transition 或 animation
CSS 的 transition 只支持起点→终点的插值,无法动态注入中间 Y 轴偏移;@keyframes 写死的贝塞尔曲线(如 cubic-bezier(0.2, 0.6, 0.4, 1))在不同屏幕宽高比下弧度失真严重,尤其小屏手机上容易飞出视口。
更关键的是:购物车图标位置常随 TabBar 或自定义导航栏浮动,CSS 动画无法响应运行时布局变化。而 JS 动画每帧都能重新读取目标节点最新 boundingClientRect。
- 小程序平台不支持
transform: translate3d()在某些低端机型上触发硬件加速,纯 CSS 动画反而更卡 - uni-app 的
uni.$on('tabBarChange')等事件不会触发 CSS 重绘,但 JS 动画可监听并重置目标坐标 - 如果购物车按钮用了
position: fixed,CSS 动画在 iOS 微信里会出现定位错乱,JS 方案可主动转为视口坐标系计算
uni.createSelectorQuery() 获取节点坐标的坑
最常踩的坑是:查了源节点却忘了查目标节点,或者用了 select('.cart-icon') 但实际 class 是 cart__icon —— 小程序端严格区分大小写,H5 端却忽略,一跑就白屏无报错。
另一个隐蔽问题是:在 onPullDownRefresh 或页面刚进入时立即查节点,此时 DOM 还没渲染完成,返回 boundingClientRect 全是 0。
- 务必在
$nextTick后再执行查询,Vue 3 的nextTick在 uni-app 里等价于uni.nextTick - 查询多个节点时,不要链式写
.select().boundingClientRect().select().boundingClientRect(),要分开调用,否则第二个.exec()会覆盖第一个结果 - 获取到的
top/left是相对于 viewport 的,但transform: translate()是相对自身定位,需统一用px单位,别混用rpx
性能敏感点:动画中别碰 this.data 和 uni.setStorageSync
每帧都触发 setData(或 Vue 响应式赋值)会导致频繁 diff,安卓低端机直接掉到 15fps;更糟的是在动画循环里调 uni.setStorageSync,会阻塞主线程,动画瞬间冻结。
正确做法是只操作 DOM 样式属性,用 element.style.transform = translate3d(x, y, 0),完全绕过框架层更新。
- 用
document.getElementById或ref直接拿原生节点,别通过this.$refs.xxx再取$el,多一层代理就多一次性能损耗 - 动画开始前把所有计算量做完(起始/终点坐标、时间步长、重力系数),循环体里只做加减乘除
- 动画结束后立刻调
element.remove()清理临时元素,避免内存泄漏,尤其列表页反复进出时
动画最难调的其实是「手感」:抛物线顶点不能太高(否则像扔东西),也不能太平(否则像滑动)。建议先固定一个设备录屏,逐帧看轨迹,再微调 g 和初速度 vy。不同机型的屏幕刷新率差异会让同一套参数表现不一,真机调试不可跳过。


















