uni-app中无法用animation API实现精准抛物线动画,应使用requestAnimationFrame手动计算抛物线轨迹(如y=ax²+bx+c或sin扰动)并实时更新transform位置,配合uni.createSelectorQuery获取节点坐标,避免transition不稳定性。

uni-app里用 animation 实现抛物线动画不现实
直接用 uni-app 自带的 animation API 做淘宝购物车那种抛物线飞入结算栏的效果,基本做不到。因为 animation 只支持线性/缓动路径(translateX/translateY 分开控制),没法精确描述二次函数轨迹,强行拆成两段动画会断层、卡顿、不同步。
真实项目中靠谱的做法是:用 requestAnimationFrame 手动算点 + transform: translate(x, y) 实时更新位置。
- 抛物线公式推荐用
y = a * x² + b * x + c,其中 x 是横向进度(0→1),y 是纵向偏移量 - 起始点和终点固定后,a、b、c 可解出;更简单做法是用贝塞尔曲线近似,比如
cubic-bezier(0.3, 1.2, 0.7, 0.8)(注意:这个值超出标准范围,但浏览器允许,能做出“先扬后抑”的抛物感) - uni-app 的
view必须设position: absolute,否则transform无效或定位错乱
怎么让小球从商品列表飞进右上角购物车图标
核心是拿到两个 DOM 节点的屏幕坐标,再做相对位移计算。uni-app 里不能直接用 getBoundingClientRect()(某些平台不支持),得用 uni.createSelectorQuery()。
- 起点:点击商品时,用
uni.createSelectorQuery().select('#item-123').boundingClientRect()拿到左上角坐标 - 终点:购物车图标一般固定在
tabBar或自定义导航栏上,建议给它加 id(如id="cart-icon"),同样查位置 - 别忘了减去页面滚动偏移:
pageScrollOffset = uni.getSystemInfoSync().windowHeight - uni.getSystemInfoSync().screenHeight + 页面 scrollTop(实际用uni.pageScrollTo监听更稳) - 动画过程中,每帧更新
style.transform = translate(${x}px, ${y}px),x/y 按时间插值算,不是按 DOM 位置插值
为什么用 transition + transform 会飘、偏、延迟
因为 uni-app 的 view 在某些平台(尤其是微信小程序)会被编译成原生组件,transition 对 transform 的支持不稳定,且无法控制贝塞尔曲线中间态。你看到的“飘”,其实是系统对 transform 做了硬件加速降级或插值补偿。
- 安卓微信里,
transition动画经常跳帧,尤其在低端机上;iOS 微信则可能因视图层级问题导致 z-index 错乱,小球被遮挡 - 不要用
left/top做动画——触发重排,卡顿明显;只用transform和opacity - 如果必须用声明式动画,可以尝试
animation: flyIn 0.6s cubic-bezier(0.3, 1.2, 0.7, 0.8),但要加animation-fill-mode: forwards,否则结束后元素会闪回原位
uni-app 抛物线动画的最小可用代码结构
下面这段是真正在 H5 和微信小程序都跑得通的精简骨架,去掉所有业务逻辑,只留轨迹计算和渲染:
// 假设 this.ballEl 是要飞的小球 view 元素
const start = { x: 100, y: 300 }; // 起点(屏幕坐标)
const end = { x: 320, y: 80 }; // 终点(屏幕坐标)
const duration = 600; // 毫秒
const startTime = Date.now();
<p>const animate = () => {
const elapsed = Date.now() - startTime;
const t = Math.min(elapsed / duration, 1); // 进度 0→1
const x = start.x + (end.x - start.x) <em> t;
const y = start.y + (end.y - start.y) </em> t - 80 <em> Math.sin(t </em> Math.PI); // 简单抛物扰动</p><p>this.ballEl.style.transform = <code>translate(${x}px, ${y}px)</code>;
if (t < 1) requestAnimationFrame(animate);
};
animate();
注意:这里的 Math.sin(t * Math.PI) 是轻量替代二次函数的方式,视觉上比纯线性更“抛”,也不用解方程。真正上线时建议封装成可复用的 parabolaFly(el, from, to, opts) 函数,但别塞太多配置项——多数场景只需要调一次,写太重反而难调试。
最常被忽略的是:飞行动画结束后的清理。记得在 t === 1 时把小球 display: none 或移除节点,否则它还占着绝对定位空间,可能挡住后续点击。


















