欢迎关注我的公众号:前端侦探
好久不见!
大部分点击效果都平平无奇,如果你碰到一个这样的点击效果,会不会忍不住多点几次呢?

看似有些复杂,其实分解开来,其实就两个动画
- 粒子的抛物线动画
- 数字的缩放动画
下面来一步步实现它,花两分钟一起看看吧
一、CSS 抛物线运动
先从单个粒子看,其实就是一个抛物线运动,我们先从平抛运动开始。
众所周知,抛物线运动是一个水平方向上匀速、垂直方向上匀加速的合成运动

这个其实用 CSS 动画也很好实现,水平和垂直两个方向的位移动画分别用不同的动画缓存函数
有兴趣的可以参考张鑫旭的这篇文章:这回试试使用CSS实现抛物线运动效果
这里简单介绍一下
实现这样的效果需要一个嵌套结构
ini
<div class="ball-x">
<div class="ball-y"></div>
</div>
然后里外设置不同的动画缓冲函数
css
.ball-x {
animation-timing-function: linear; /*匀速直线运动*/
}
.ball-y {
animation-timing-function: cubic-bezier(.55, 0, .85, .36); /*大致匀加速运动*/
}
运动分解效果如下:

当然,这里为了兼容性考虑,使用了两层标签,其实还可以使用单层标签实现,需要用到动画合成属性,有兴趣可以参考之前这篇文章: 了解一下全新的CSS动画合成属性animation-composition
回到我们的例子,假设单个粒子的HTML
是这样的
css
<div class="custom-tips" style="left: 50%; top: 50%;">
<div class="custom-tips-dot" emoji="🎉"></div>
</div>
关键样式如下
css
.custom-tips {
position: absolute;
width: 1em;
height: 1em;
margin-left: -.5em;
margin-top: -.5em;
left: 0;
top: 0;
transform: translate(var(--left, 50%), var(--top, 50%));
}
.custom-tips-dot {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
animation: custom-x 1s linear forwards;
}
.custom-tips-dot::before {
content: attr(emoji, '🎉');
animation: custom-y 1s cubic-bezier(0.55, 0, 0.85, 0.36);
}
/* x方向 */
@keyframes custom-x {
100% {
transform: translateX(300%);
}
}
/* y方向 */
@keyframes custom-y {
100% {
transform: translateY(300%);
}
}
效果如下(绿色线框是水平运动)

这样就简单实现了一个平抛运动
二、CSS 斜抛运动
平抛运动可能没有那种礼花绽放的感觉,有点平淡,我们还需要有一定角度朝上做斜抛运动。
看着好像有点复杂?其实就是多了一个向上的初始速度,示意如下

这个在 JS
中很好实现,给一个初始值就行,垂直方向上先往上减速,一直到 0,然后开始加速,这样整体就是一个斜抛运动了。
那么,CSS
如何处理呢?好像并没有这种概念。
其实呢,可以从缓冲函数入手,比如上面用到的缓冲函数是cubic-bezier(0.55, 0, 0.85, 0.36)
,是一种逐渐变快的运动

这时,我们可以改变第一个锚点,往下拉,直到第二个值出现负数,如下

这样会出现一个轻微的回弹效果,效果如下

是不是有点像斜抛运动了?
我们可以继续调整幅度,让回弹效果更强烈一些,比如

这样就更接近真实的斜抛运动了

关键 CSS 如下
css
.custom-tips-dot::before {
content: attr(emoji, '🎉');
animation: custom-y 1s cubic-bezier(0.56, -1.35, 0.85, 0.36) forwards;
}
是不是也比较容易?
三、借助JS批量生产
单个粒子可能比较死板,也不够随机。
要实现随机也比较容易,每次生成例子的水平位移不同就行了,然后多个粒子的动画有一定的延时就可以了。

为了方便JS
透传过去,可以借助CSS
变量来生成
这里用--x
表示水平位移,--d
表示延迟
css
.custom-tips-dot {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
outline: 1px solid yellowgreen;
animation: custom-x 1s var(--d, 0s) linear forwards;
}
.custom-tips-dot::before {
content: attr(emoji, '🎉');
animation: custom-y 1s var(--d, 0s) cubic-bezier(0.56, -1.35, 0.85, 0.36) forwards;
}
/* x方向 */
@keyframes custom-x {
0% {
opacity: 0;
transform: translateX(0%)
}
10%, 90% {
opacity: 1;
}
100% {
opacity: 0;
transform: translateX(var(--x, 300%)); /*随机的水平位移*/
}
}
然后用 JS
动态生成这些 dom
javascript
function createDots(emojis) {
const temp = document.createDocumentFragment();
const random_emojis = emojis.slice(0, Math.ceil(Math.random() * emojis.length)).sort(() => Math.random() - .5)
random_emojis.forEach(emoji => {
const dot = document.createElement('div');
dot.className = 'custom-tips-dot';
dot.setAttribute('emoji', emoji);
dot.style.setProperty('--d', `${Math.random() * 0.2}s`);
dot.style.setProperty('--x', `${(Math.random() - 0.5) * 1000}%`);
temp.appendChild(dot);
/*动画结束后自动移除dom*/
dot.addEventListener('animationend', () => {
console.log('dot.parentNode', dot.parentNode, dot.parentNode?.childElementCount)
if (dot?.parentNode?.childElementCount <= 1) {
dot.parentNode.remove();
} else {
dot.remove();
}
});
})
return temp;
}
document.addEventListener('click',(ev) => {
const { clientX, clientY } = ev;
console.log(clientX, clientY);
document.body.style.setProperty('--left', `${clientX}px`);
document.body.style.setProperty('--top', `${clientY}px`);
const tips = document.createElement('div');
tips.style.setProperty('--left', `${clientX}px`);
tips.style.setProperty('--top', `${clientY}px`);
tips.className = 'custom-tips';
const dots = createDots(['🎉', '😘', '🎊', '🤡', '🥳', '🤪', '💗']);
tips.appendChild(dots);
document.body.appendChild(tips);
})
这样就能每次随机生成一定数量的emoji
了,效果如下

四、+1 动画
数字变化动画是最简单的,就是一个缩放+透明度变化的动画。

假设HTML
是这样的
ini
<div class="custom-num" num="1"></div>
我们用伪元素来生成+1
的字符,动画很简单,就两个关键帧,实现如下
css
.custom-num {
position: absolute;
left: 0;
top: 0;
display: flex;
width: 2em;
height: 2em;
font-size: 2em;
color: #fff;
justify-content: center;
align-items: center;
margin-left: -1em;
margin-top: -2em;
font-weight: bold;
text-shadow: 4px 4px 0 rgba(255,0,0);
transform: translate(var(--left), var(--top));
}
.custom-num::before {
content: '+' attr(num);
opacity: 0;
animation: count-shark 1s var(--d, 0s);
}
@keyframes count-shark {
0%,100%{
opacity: 0;
transform: scale(.4);
}
30%,70%{
opacity: 1;
transform: scale(1);
}
}
效果如下

然后我们借助JS
来实现数字增加的逻辑,其实就是移除上一个,新增一个dom,新增的会自动执行动画,实现如下
ini
function createNum() {
const current = document.querySelector('.custom-num');
let num = 1
if (current) {
num = parseInt(current.getAttribute('num')) + 1;
current.remove();
}
const numDiv = document.createElement('div');
numDiv.className = 'custom-num';
if (num > 1) {
// numDiv.style.setProperty('--d','-.3s' )
}
numDiv.setAttribute('num', num);
numDiv.addEventListener('animationend', () => {
numDiv.remove();
});
return numDiv;
}
最后两者结合起来,就实现了文章开头所示效果

你也可以访问在线demo真实体验:codepen.io/xboxyan/pen...
五、其实canvas可能更适合
一般来说,粒子动画可能canvas来实现更合适一点,性能也更好,不过可能对新手不太友好。这里推荐一个开箱即用的库:叫做 Canvas Confetti
,专门用来做这种礼花绽放特效的,如果自己手搓不来,可以试试这个。

不过我们这里案例的粒子也不是很多,所以使用CSS
也无所谓了。
关于动画的合成、分解、叠加小技巧,你学会了吗,赶紧在项目中用起来吧~最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发 ❤❤❤