实现购物车飞入动画时,最核心的问题是如何让一个DOM元素从商品图片位置沿着平滑曲线移动到购物车图标。直接使用CSS过渡或线性动画只能得到直线运动,视觉上缺乏真实感。要得到抛物线轨迹,必须让元素在水平方向保持匀速,同时在竖直方向施加一个恒定的向下加速度,这样每一帧的位置都由运动学公式计算得出。jQuery Fly插件正是基于这种思路,把位移计算封装成轻量级动画循环,开发者只需要传入起点、终点和必要的物理参数,就可以触发一次抛物线飞行。

抛物线动画的物理基础:运动分解与参数方程
抛物线运动可以拆解为两个互相垂直的独立运动。水平方向上,如果不考虑空气阻力,元素做匀速直线运动,位移与时间成正比。竖直方向上,元素受到一个恒定的重力加速度影响,做匀变速运动。在网页坐标系中,y轴方向向下,因此重力加速度取正值时,物体会加速下落;如果给定一个向上的初始竖直速度,物体先减速上升再加速下降,形成典型的抛物线形状。
设起点坐标为(x0, y0),终点坐标为(x1, y1),动画总时长为T,水平速度为vx。水平方向满足vx = (x1 - x0) / T,任意时刻t的水平位移为x(t) = x0 + vx * t。竖直方向需要满足在T时刻的位移恰好为dy = y1 - y0。如果采用重力模型,设竖直初速度为vy0,重力加速度为g,则竖直位移公式为y(t) = y0 + vy0 * t + 0.5 * g * t * t。将t = T代入可得vy0 = (dy - 0.5 * g * T * T) / T。只要确定了g的取值,就能唯一计算出vy0,进而得到每一帧的竖直位置。
在实际动画中,g并不是真实的重力加速度9.8米每二次方秒,而是一个可调节的像素参数。较大的g值会让曲线弯曲得更明显,元素上升和下降的速度变化更快;较小的g值则让轨迹接近直线。通常我们会根据起点和终点的距离动态设置g,例如距离越远,g适当增大,以保证视觉效果更自然。
jQuery Fly插件内部轨迹计算源码剖析
jQuery Fly插件的核心逻辑并不复杂,它主要做三件事:读取起点和终点元素的位置信息、创建用于飞行的临时元素、启动动画循环并在每一帧中更新临时元素的位置。位置更新函数内部就是上一节推导的物理公式。下面是一个简化版的核心实现,展示了轨迹计算的关键代码。
// 计算飞行元素每一帧的位置
function calculatePosition(startX, startY, endX, endY, duration, gravity, elapsed) {
var t = Math.min(elapsed / duration, 1);
// 水平匀速
var x = startX + (endX - startX) * t;
// 竖直运动:先根据终点位移和重力反推初速度
var dy = endY - startY;
var vy0 = (dy - 0.5 * gravity * duration * duration) / duration;
var y = startY + vy0 * elapsed + 0.5 * gravity * elapsed * elapsed;
// 如果动画结束则直接对齐到终点,避免累加误差
if (t >= 1) {
x = endX;
y = endY;
}
return { x: x, y: y };
}
上面的代码中,duration表示动画总时长,elapsed表示从动画开始到当前帧已经过去的时间。Math.min(elapsed / duration, 1)用于限制时间比例不超过1,防止最后一帧超出终点。水平位置使用简单的线性插值,竖直位置则使用二次函数计算。注意这里的vy0在每一帧都会重新计算,虽然它理论上是一个常量,但这种写法避免了在动画开始时存储额外的状态,代码更集中。
在实际的jQuery Fly插件中,动画循环通常使用requestAnimationFrame来驱动。每一帧根据时间戳计算出elapsed,然后调用位置计算函数更新飞行元素的style.left和style.top。为了避免频繁的布局重排,一些实现会使用transform: translate(x, y)来替代改变left和top,性能更好,特别是在移动端。
实现加入购物车动画的完整步骤与代码示例
要在一个电商页面中实现完整的飞入购物车效果,需要完成以下几个步骤:准备商品图片和购物车图标的HTML结构;为两个元素设置合适的定位参照;在用户点击“加入购物车”按钮时获取起止坐标;创建飞行元素并执行动画;动画结束后移除飞行元素并更新购物车角标。下面给出一个可直接运行的完整示例。
<div class="product-card">
<img src="product.jpg" class="product-img" id="productImg">
<button id="addBtn">加入购物车</button>
</div>
<div class="cart-icon" id="cartIcon">
<span>购物车</span>
<span id="cartCount">0</span>
</div>
.product-img {
width: 120px;
height: 120px;
cursor: pointer;
}
.fly-item {
position: fixed;
width: 40px;
height: 40px;
border-radius: 50%;
background: #ff6600;
pointer-events: none;
z-index: 9999;
transition: none;
}
.cart-icon {
position: fixed;
right: 30px;
top: 80px;
display: flex;
align-items: center;
gap: 6px;
font-size: 16px;
}
$('#addBtn').on('click', function () {
var $product = $('#productImg');
var $cart = $('#cartIcon');
// 获取相对视口的起点和终点坐标
var startOffset = $product.offset();
var endOffset = $cart.offset();
// 飞行元素缩放到商品图片的1/3大小,起点位于商品中心
var startX = startOffset.left + $product.width() / 2 - 20;
var startY = startOffset.top + $product.height() / 2 - 20;
var endX = endOffset.left + $cart.width() / 2 - 20;
var endY = endOffset.top + $cart.height() / 2 - 20;
var $fly = $('<div class="fly-item"></div>').appendTo('body');
$fly.css({ left: startX, top: startY });
var duration = 800; // 毫秒
var gravity = 1500; // 重力系数,可根据距离调整
var startTime = null;
var vy0 = (endY - startY - 0.5 * gravity * (duration / 1000) * (duration / 1000)) / (duration / 1000);
function animateFly(timestamp) {
if (!startTime) startTime = timestamp;
var elapsed = timestamp - startTime;
var t = Math.min(elapsed / duration, 1);
var x = startX + (endX - startX) * t;
var y = startY + vy0 * (elapsed / 1000) + 0.5 * gravity * (elapsed / 1000) * (elapsed / 1000);
if (t >= 1) {
x = endX;
y = endY;
$fly.remove();
// 更新购物车角标
var count = parseInt($('#cartCount').text()) + 1;
$('#cartCount').text(count);
return;
}
$fly.css({ left: x, top: y });
requestAnimationFrame(animateFly);
}
requestAnimationFrame(animateFly);
});
这段代码直接使用了requestAnimationFrame驱动动画,没有依赖任何动画库。gravity的值可以根据起点和终点的距离动态调整,例如当距离小于200像素时减小gravity,避免曲线过于夸张。duration控制飞行总时长,一般设置在600到1000毫秒之间比较合适。飞行元素使用position: fixed定位,坐标基于视口计算,这样即使页面发生滚动,飞行元素也能准确贴合起止位置。
如果页面在飞行过程中需要实时响应滚动,固定定位的起点和终点坐标可能已经偏移。一个更稳健的做法是在动画开始时保存滚动位置,并在每一帧中叠加滚动偏移量,或者在动画期间暂时禁止页面滚动。对于大多数电商场景,飞行动画持续时间很短,可以忽略滚动干扰。
轨迹优化与边界处理技巧
当商品图片与购物车图标距离非常近时,抛物线动画容易显得过分弯曲,甚至出现元素先向上飞出屏幕再落回的情况。解决方法是根据水平距离动态调整重力系数。例如设定一个基准距离distBase = 400,重力系数gravity = 1200 + Math.max(0, Math.abs(endX - startX) - distBase) * 2。这样距离越远重力越大,距离近时曲线接近直线。同时,如果计算出的vy0为负值,说明初始竖直速度向上,形成的抛物线最高点可能超出视口,此时可以限制vy0的最小值,例如Math.max(vy0, -200),并将终点位移拆分为多段或者降低duration。
移动端的高刷新率屏幕下,使用改变left和top的方式会触发多次布局和绘制,可能造成掉帧。优化方法是使用transform属性,将x和y作为translate的偏移量应用到飞行元素上。例如$fly.css('transform', 'translate(' + x + 'px, ' + y + 'px)'),同时设置left和top为0。这样浏览器可以利用GPU合成层,减少重排成本。不过要注意transform的坐标系是相对于元素自身的初始位置,需要保证飞行元素初始在视口左上角,或者额外计算相对位移。
多商品连续加入购物车时,如果每次点击都立即触发动画,多个飞行元素会同时出现在页面上,路径可能重叠,视觉混乱。常见的处理方式是使用一个飞行队列,将待飞行的商品信息存入数组,当上一个动画结束后再启动下一个。这样可以保证同一时刻只有一个元素在飞行,购物车角标也能按顺序更新。队列的代码实现可以在动画结束回调中检查队列长度并启动下一个任务,配合延迟参数实现依次飞入的效果。
jQuery Fly抛物线动画物理轨迹计算修改时间:2026-08-23 22:47:02