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<LikeCache>({
   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实现点赞动效的方式
❤️欢迎大家关注我,文章小白上路,你们是我继续整理分享的动力❤️
❤️公众号: 前端别搞我❤️

❤️关注+点赞收藏+评论+分享❤️,手留余香,谢谢🙏大家。

03-05 13:57