京东购物车动效实现:贝塞尔曲线的妙用
前言
大家好,我是奈德丽。前两天在逛京东想买Pocket 3的时候,注意到了它的购物车动效,当点击"加入购物车"按钮时,一个小红球从商品飞入购物车,我觉得很有意思,于是花了点时间来研究。
实现效果
看了图才知道我在讲什么,那么先看Gif吧!
代码演示
代码已经上传到了码上掘金,感兴趣的可以自行查看,文章中没有贴全部代码了,主要讲讲思路,
code.juejin.cn/pen/7503150…
实现思路
下面这个思路,小白也能会,我们将通过以下几个步骤来实现这个效果:
画页面——>写逻辑实现动画效果
好了,废话不多说,开始进入正题
第一步:先让AI帮我们写出来UI结构
像我们这种工作1坤年以上的切图仔,能偷懒当然偷懒啦,这种画页面的活可以丢给AI来干了,下面是Taro帮我生成的页面部分,没什么难点,就是一些普普通通的页面元素。
<template>
<div class="rolling-ball-container">
<!-- 商品列表 -->
<div class="item-list">
<div class="item" v-for="item in 10" :key="item">
<div class="product-card">
<div class="product-tag">秒杀</div>
<div class="product-image">
<img src="/product.jpg" alt="商品图片" />
</div>
<div class="product-info">
<div class="product-title">大疆 DJI Osmo Pocket 3 一英寸口袋云台相机</div>
<div class="product-features">
<span class="feature-tag">三轴防抖</span>
<span class="feature-tag">防抖稳定</span>
<span class="feature-tag">高清画质</span>
</div>
<div class="product-price">
<span class="price-symbol">¥</span>
<span class="price-value">4788</span>
<span class="price-original">¥4899</span>
</div>
<div class="product-meta">
<span class="delivery-time">24分钟达</span>
<span class="rating">好评率96%</span>
</div>
<div class="product-shop">京东之家-凯德汇新店</div>
</div>
<div class="add-to-cart" @click="startRolling($event)">+</div>
</div>
</div>
</div>
<!-- 购物车图标 -->
<div class="point end-point">
<div style="position: relative;">
<img src="/cart.png" />
<div class="cart-count">{{ totalCount }}</div>
</div>
</div>
<!-- 小球容器 -->
<div
v-for="(ball, index) in balls"
:key="index"
class="ball"
v-show="ball.show"
:style="getBallStyle(ball)"
></div>
</div>
</template>
第二步:设计小球数据模型
有了页面元素了,我们需要创建小球数组和计数器
import { reactive, ref } from 'vue';
// 购物车商品计数
const totalCount = ref(0);
// 创建小球数组(预先创建3个小球以应对连续点击)
const balls = reactive(Array(3).fill(0).map(() => ({
show: false, // 是否显示
startX: 0, // 起点X坐标
startY: 0, // 起点Y坐标
endX: 0, // 终点X坐标
endY: 0, // 终点Y坐标
pathX: 0, // 路径X偏移量
pathY: 0, // 路径Y偏移量
progress: 0 // 动画进度
})));
为什么小球要用一个数组来存储呢?因为我看到京东上用户是可以连续点击+号将商品加入购入车的,页面上可以同时存在很多个飞行的小球。
第三步:实现动画触发函数
当用户点击"+"按钮时,我们需要计算起点和终点坐标,然后启动动画,这儿有一个细节,为了让小球刚好落到在购物车中间,对终点坐标进行了微调。
// 开始滚动动画
const startRolling = (event: MouseEvent) => {
// 获取起点和终点元素
const startPoint = event.currentTarget as HTMLElement;
const endPoint = document.querySelector('.end-point') as HTMLElement;
if (startPoint && endPoint) {
// 找到一个可用的小球
const ball = balls.find(ball => !ball.show);
if (ball) {
// 获取起点位置
const startRect = startPoint.getBoundingClientRect();
ball.startX = startRect.left + startRect.width / 2;
ball.startY = startRect.top + startRect.height / 2;
// 获取终点位置
const endRect = endPoint.getBoundingClientRect();
const endX = endRect.left + endRect.width / 2;
const endY = endRect.top + endRect.height / 2;
// 微调终点位置
ball.endX = endX - 4;
ball.endY = endY - 7;
// 设置路径偏移量
ball.pathX = 0;
ball.pathY = 100;
// 显示小球并重置进度
ball.show = true;
ball.progress = 0;
// 使用requestAnimationFrame实现动画
let startTime = Date.now();
const duration = 400; // 动画持续时间(毫秒)
function animate() {
const currentTime = Date.now();
const elapsed = currentTime - startTime;
ball.progress = Math.min(elapsed / duration, 1);
if (ball.progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画结束后隐藏小球
setTimeout(() => {
ball.show = false;
}, 100);
}
}
requestAnimationFrame(animate);
// 增加购物车商品数量
totalCount.value++;
}
}
};
第四步:使用贝塞尔曲线计算小球轨迹
点击"+"按钮,不能让小球做自由落体运动吧,那是伽利略研究的,你看这自由落体好看嘛,指定不行,要是长这样,那东哥的商城还能卖出去东西吗?Hah
为了不让它自由落体,给它一个向左的偏移量100px
// 获取小球样式
const getBallStyle = (ball: any) => {
if (!ball.show) return {};
// 使用二次贝塞尔曲线计算路径
const t = ball.progress;
const mt = 1 - t;
// 判断起点和终点是否在同一垂直线上
const isVertical = Math.abs(ball.startX - ball.endX) < 20;
// 计算控制点(确保有弧度)
let controlX, controlY;
if (isVertical) {
// 如果在同一垂直线上,向左偏移一定距离
controlX = ball.startX - 100;
controlY = (ball.startY + ball.endY) / 2;
} else {
// 否则使用向左偏移
controlX = (ball.startX + ball.endX) / 2 - 100;
controlY = (ball.startY + ball.endY) / 2 + (ball.pathY || 100);
}
// 二次贝塞尔曲线公式
const x = mt * mt * ball.startX + 2 * mt * t * controlX + t * t * ball.endX;
const y = mt * mt * ball.startY + 2 * mt * t * controlY + t * t * ball.endY;
return {
left: `${x}px`,
top: `${y}px`,
transform: `rotate(${ball.progress * 360}deg)` // 添加旋转效果
};
};
技术要点解析
1. 贝塞尔曲线原理
贝塞尔曲线是一种参数化曲线,广泛应用于计算机图形学。二次贝塞尔曲线由三个点定义:起点P₀、控制点P₁和终点P₂。
曲线上任意点的坐标可以通过以下公式计算:
B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂ (0 ≤ t ≤ 1)
在我们的实现中,通过调整控制点的位置,可以控制曲线的形状,从而实现小球的抛物线运动效果。
2. requestAnimationFrame的优势
与setTimeout或setInterval相比,requestAnimationFrame有以下优势:
- 性能更好:浏览器会在最合适的时间(通常是下一次重绘之前)执行回调函数,避免不必要的重绘
- 节能:当页面不可见或最小化时,动画会自动暂停,节省CPU资源
- 更流畅:与显示器刷新率同步,动画更平滑
3. 动态计算元素位置
我们使用getBoundingClientRect()
方法获取元素在视口中的精确位置,这确保了无论页面如何滚动或调整大小,动画始终能准确地从起点到达终点。
总结
通过这个小球飞入购物车的动画效果,我们不仅提升了用户体验,还学习了:
- 如何使用贝塞尔曲线创建平滑动画
- 如何用requestAnimationFrame实现高性能动画
- 如何动态计算元素位置
- 如何使用rem单位实现移动端适配
这个小小的交互设计虽然看起来简单,但能大大提升用户体验,让你的电商网站更加生动有趣。从京东商城的灵感到实际代码实现,我们完成了一个专业级别的交互效果。
恩恩……懦夫的味道
来源:juejin.cn/post/7502647033401704484