1. 前言
以前在看微信视频号直播的时候,经常点击右下角的点赞按钮。看着它的数字慢慢从一位数变成五位数,还是挺有氛围感的。特别是长按的时候,有个手机震动的反馈,很带感。
虽然之前很好奇这些飘动的点赞动效是怎么实现的,但没有特别去钻研。直到前阵子投入腾讯课堂 H5 直播间的需求,需要自己去实现一个这样的效果时,才开始摸索。
先看看最后的效果:
相比视频号的点赞动效,轨迹复杂了很多。可以看到课堂直播间的这一段点赞动效,大概分为这么三个阶段:
1.从无到有,在上升过程中放大成正常大小
2.上升过程中左右摇曳,且摇曳的幅度随机
3.左右摇曳上升的过程中,渐隐并缩小
在动手之前,我先想到了使用 CSS animation 去实现这种运动轨迹。在完成之后,又用 Canvas 重构了一版,优化了性能。
接下来我们分别来看看这两种实现方式。
2. CSS 实现点赞动效
2.1 轨迹分析
由于点赞动画是在一个二维平面上的,我们可以将它的运动轨迹拆分为 x 轴 和 y 轴 上的两段。
在 y 轴 上非常简单,我们的点赞图标会做一段垂直上升的匀速运动,从容器底部上升到容器顶部。
而 x 轴 上是左右摇曳的,用数学的角度说,是一段简谐运动。
但用 css 实现的时候,其实不用这么精细。为了简化计算,我们可以用几个关键帧来串联这段运动轨迹,例如:
@keyframes bubble_swing {
0% {
中间
}
25% {
最左
}
75% {
最右
}
100% {
中间
}
}
2.2 轨迹设计
根据上面的分析,我们可以设计一段相同的上升轨迹,以及几段不同的左右摇曳轨迹。
上升轨迹很简单,同时我们还可以加上透明度(opacity)、大小(transform)的变化,如下:
@keyframes bubble_y {
0% {
transform: scale(1);
margin-bottom: 0;
opacity: 0;
}
5% {
transform: scale(1.5);
opacity: 1;
}
80% {
transform: scale(1);
opacity: 1;
}
100% {
margin-bottom: var(--cntHeight);
transform: scale(0.8);
opacity: 0;
}
}
其中,--cntHeight 指的是容器的高度。也就是说,我们通过让 margin-bottom 不断增大,来控制点赞图标从容器底部上升到容器顶部。
而对于横向运动的轨迹,为了增加运动轨迹的多样性,我们可以设计多段左右摇曳的轨迹,比如说一段 “中间 -> 最左 -> 中间 -> 最右” 的轨迹:
@keyframes bubble_swing_1 {
0% {
// 中间
margin-left: 0;
}
25% {
// 最左
margin-left: -12px;
}
75% {
// 最右
margin-left: 12px;
}
100% {
margin-left: 0;
}
}
这里同样使用 margin 来控制图标的左右移动。类似的,我们还可以设计几段别的轨迹:
// 任意轨迹
@keyframes bubble_swing_2 {
0% {
// 中间
margin-left: 0;
}
33% {
// 最左
margin-left: -12px;
}
100% {
// 随机位置
margin-left: 6px;
}
}
// 简谐反向
@keyframes bubble_swing_3 {
0% {
// 中间
margin-left: 0;
}
25% {
// 最右
margin-left: 12px;
}
75% {
// 最左
margin-left: -12px;
}
100% {
margin-left: 0;
}
}
接下来我们把 x 轴 和 y 轴 的轨迹(@keyframes)结合起来,并设置一个随机的动画时间,比如说:
@for$i from 1 through 3 {
@for$j from 1 through 2 {
.bl_#{$i}_#{$j} {
animation: bubble_y calc(1.5s + $j * 0.5s) linear 1 forwards,
bubble_swing_#{$i} calc(1.5s + $j * 0.5s) linear 1 forwards;
}
}
}
这里生成了 3 * 2 = 6 种不同的轨迹。针对这类重复的选择器,用 SCSS 中的循环语法,可以少写很多代码。
2.3 随机选择图片(雪碧图)
我们每次点赞会出现不同的图标,于是这里设计了一系列选择器给不同的图标,让它们呈现不同的图片。首先我们要准备一张雪碧图,保持所有图标的大小一致,然后同样使用 SCSS 的循环语法:
@for$i from 0 through 7 {
.b#{$i} {
background: url('../../images/like_sprites.png') calc(#{$i} * -24px) 0;
}
}
像上面生成了 8 个选择器,我们在程序执行时就可以随机给图标赋予一个选择器。
2.4 生成一个点赞图标
CSS 的部分差不多了,我们现在来看 JS 是怎么执行的。我们需要有一个容器 div,让它来装载要生成的点赞图标。以及一个按钮来绑定点击事件:
const cacheRef = useRef({
bubbleCnt: null,
likeIcon: null,
bubbleIndex: 0,
timer: null,
});
useEffect(() => {
cacheRef.current.bubbleCnt = document.getElementById('like-bubble-cnt');
cacheRef.current.likeIcon = document.getElementById('like-icon');
}, []);
在点击事件中,生成一个新的 div 元素,并为它设置 className。接着将它 append 到容器下,最后在一段时间后销毁这个点赞图标元素。如下:
/**
* 添加 bubble
*/
const addBubble = () => {
const { bubbleCnt } = cacheRef.current;
cacheRef.current.bubbleIndex %= maxBubble;
const d = document.createElement('div');
// 图片类 b0 - b7
// 随机动画类 bl_1_1 - bl_3_2
const swing = Math.floor(Math.random() * 3) + 1;
const speed = Math.floor(Math.random() * 2) + 1;
d.className = `like-bubble b${cacheRef.current.bubbleIndex} bl_${swing}_${speed}`;
bubbleCnt?.appendChild(d);
cacheRef.current.bubbleIndex++;
// 动画结束后销毁元素
setTimeout(() => {
bubbleCnt?.removeChild(d);
}, 2600);
};
到这里,我们就实现得差不多了。不过,我们还可以给点击的图标加点动画,让它有一个被按压后弹起的效果:
/**
* 点击“喜欢”
*/
const onClick = () => {
const { timer, likeIcon } = cacheRef.current;
if (!likeIcon) {
return;
}
if (timer) {
clearTimeout(timer);
cacheRef.current.timer = null;
}
likeIcon.classList.remove('bounce-click');
// 删除并重新添加类,需要延迟添加
setTimeout(() => {
likeIcon.classList.add('bounce-click');
}, 0);
cacheRef.current.timer = window.setTimeout(() => {
likeIcon.classList.remove('bounce-click');
clearTimeout(timer!);
cacheRef.current.timer = null;
}, 300);
addBubble();
};
2.5 最终效果
今天就到这,下期给大家分享canvas实现点赞动效的方式
❤️欢迎大家关注我,文章小白上路,你们是我继续整理分享的动力❤️
❤️公众号: 前端别搞我❤️
❤️关注+点赞收藏+评论+分享❤️,手留余香,谢谢大家。