CSS 实现加入购物车抛物线动画

问题背景

「加入购物车」的抛物线动画是电商前端的高频需求:用户点击加购按钮,一个小球沿抛物线轨迹飞入购物车图标。这个效果的核心难点在于用 CSS 画出一条抛物线

本文将介绍三种 CSS 实现方式,不涉及复杂的 DOM 结构。


方案一:两层嵌套(最经典)

核心思想:外层负责水平匀速,内层负责垂直抛物。

<div class="box">
  <div class="ball"></div>
</div>
/* 外层:水平匀速 */
.box {
  animation: moveX 1s linear forwards;
}

/* 内层:垂直抛物线(先向上冲,再落下来) */
.ball {
  width: 20px;
  height: 20px;
  background: #ff6b35;
  border-radius: 50%;
  animation: moveY 1s cubic-bezier(0.5, -0.5, 0.5, 1) forwards;
}

@keyframes moveX {
  to { transform: translateX(300px); }
}

@keyframes moveY {
  to { transform: translateY(200px); }
}

原理图解:

        外层 translateX(linear 匀速)
        ─────────────────────────────▶
        
  内层 translateY
      ⬆ 前半段往上冲(cubic-bezier y 跑到负值)
      ⬇ 后半段落回来(cubic-bezier y 回到正值)

关键参数 cubic-bezier(0.5, -0.5, 0.5, 1)

  • 前两个参数 (0.5, -0.5) 是第一个控制点,y 为负值 → 动画前半段冲到目标上方
  • 后两个参数 (0.5, 1) 是第二个控制点,y 拉回 1 → 动画后半段落回目标位置
  • 这是一个三次贝塞尔曲线,不是真正的二次抛物线,但视觉上足够逼真

优点:代码最少,无需 JS 计算轨迹点
缺点:轨迹由贝塞尔曲线近似,不是精确抛物线;起点终点固定时好用,动态距离需要结合 JS 调整参数


方案二:单层 @keyframes 手写关键帧

如果不想嵌套两层,也可以在一个 div 上手动定义轨迹关键帧:

.ball {
  width: 20px;
  height: 20px;
  background: #ff6b35;
  border-radius: 50%;
  animation: parabola 1s ease-in-out forwards;
}

@keyframes parabola {
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(60px, -40px); }
  40%  { transform: translate(120px, -70px); }
  60%  { transform: translate(180px, -70px); }
  80%  { transform: translate(240px, -40px); }
  100% { transform: translate(300px, 0); }
}

优点:一层 DOM,结构简单
缺点:关键帧写死,终点变化时需要重写所有百分比;轨迹不够平滑(6 个关键帧折线近似)


方案三:CSS Motion Path(现代方案)

利用 offset-path 直接画一条 SVG 路径,让元素沿路径运动:

.ball {
  width: 20px;
  height: 20px;
  background: #ff6b35;
  border-radius: 50%;
  offset-path: path("M 0,0 Q 150,-100 300,200");
  animation: fly 1s ease-in-out forwards;
}

@keyframes fly {
  to { offset-distance: 100%; }
}

path 解析M 0,0 Q 150,-100 300,200 是一条二次贝塞尔曲线(Quadratic Bézier),起点 (0,0),控制点 (150,-100),终点 (300,200)

优点:轨迹精确可控,path 可以画任意曲线
缺点:兼容性一般(Chrome 116+ / Safari 16+),移动端需注意;path 写死,动态终点需要 JS 配合 style 属性修改


三种方案对比

方案DOM 层级轨迹精度兼容性动态终点
两层嵌套2 层贝塞尔近似优秀需 JS 配合参数
手写关键帧1 层折线近似优秀需重写关键帧
Motion Path1 层精确曲线Chrome 116+需 JS 改 path

在线示例

三种 CSS 抛物线与工程推荐示例

包含两层嵌套、手写关键帧、Motion Path,以及 CSS 变量 + 动态距离的工程版示例。


实际工程中的推荐做法

真实业务中,加购按钮和购物车图标的位置是不固定的(响应式布局、不同商品列表位置不同),所以推荐方案一 + JS 动态计算

// 1. 获取起点(按钮中心)和终点(购物车图标中心)
const btnRect = addBtn.getBoundingClientRect();
const cartRect = cartIcon.getBoundingClientRect();

const dx = cartRect.left - btnRect.left;
const dy = cartRect.top - btnRect.top;

// 2. 动态创建小球,inline style 设置动画
const ball = document.createElement('div');
ball.className = 'fly-ball';
ball.style.setProperty('--dx', dx + 'px');
ball.style.setProperty('--dy', dy + 'px');
container.appendChild(ball);

// 3. 动画结束后移除
ball.addEventListener('animationend', () => ball.remove());
.fly-ball {
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #ff6b35;
  animation: fly 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

@keyframes fly {
  to {
    transform: translate(var(--dx), var(--dy));
  }
}

这里用 CSS 自定义属性 --dx --dy 传递动态距离,配合 cubic-bezier 的垂直抛物效果,就能适配任意起点和终点。


总结

  1. CSS 中的这类“抛物线”本质上是视觉近似
  2. 最经典的做法是两层嵌套:外层 linear + 内层 cubic-bezier,利用贝塞尔曲线 y 值超出 0-1 的特性实现“先上后下”
  3. Motion Path 适合轨迹需要更明确可控的场景,但要注意兼容性
  4. 工程中推荐方案一 + CSS 变量 + JS 动态计算距离,能适配任意位置
Last Updated 5/28/2026, 5:09:56 AM