借助CSS實現(xiàn)點贊粒子動效
大部分點擊效果都平平無奇,如果你碰到一個這樣的點擊效果,會不會忍不住多點幾次呢?
Kapture 2025-08-09 at 13.19.13
看似有些復(fù)雜,其實分解開來,其實就兩個動畫。
- 粒子的拋物線動畫
- 數(shù)字的縮放動畫
下面來一步步實現(xiàn)它,花兩分鐘一起看看吧!
一、CSS 拋物線運動
先從單個粒子看,其實就是一個拋物線運動,我們先從平拋運動開始。
眾所周知,拋物線運動是一個水平方向上勻速、垂直方向上勻加速的合成運動。
image-20250809133654026
這個其實用 CSS 動畫也很好實現(xiàn),水平和垂直兩個方向的位移動畫分別用不同的動畫緩存函數(shù)。
這里簡單介紹一下:
實現(xiàn)這樣的效果需要一個嵌套結(jié)構(gòu)。
<div class="ball-x">
<div class="ball-y"></div>
</div>然后里外設(shè)置不同的動畫緩沖函數(shù)。
.ball-x {
animation-timing-function: linear; /*勻速直線運動*/
}
.ball-y {
animation-timing-function: cubic-bezier(.55, 0, .85, .36); /*大致勻加速運動*/
}運動分解效果如下:
Kapture 2023-04-21 at 17.26.05
當(dāng)然,這里為了兼容性考慮,使用了兩層標簽,其實還可以使用單層標簽實現(xiàn),需要用到動畫合成屬性。
回到我們的例子,假設(shè)單個粒子的HTML是這樣的。
<div class="custom-tips" style="left: 50%; top: 50%;">
<div class="custom-tips-dot" emoji="??"></div>
</div>關(guān)鍵樣式如下:
.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 1scubic-bezier(0.55, 0, 0.85, 0.36);
}
/* x方向 */
@keyframes custom-x {
100% {
transform: translateX(300%);
}
}
/* y方向 */
@keyframes custom-y {
100% {
transform: translateY(300%);
}
}效果如下(綠色線框是水平運動):
Kapture 2025-08-09 at 14.03.54
這樣就簡單實現(xiàn)了一個平拋運動。
二、CSS 斜拋運動
平拋運動可能沒有那種禮花綻放的感覺,有點平淡,我們還需要有一定角度朝上做斜拋運動。
看著好像有點復(fù)雜?其實就是多了一個向上的初始速度,示意如下:
image-20250809141915739
這個在 JS中很好實現(xiàn),給一個初始值就行,垂直方向上先往上減速,一直到 0,然后開始加速,這樣整體就是一個斜拋運動了。
那么,CSS 如何處理呢?好像并沒有這種概念。
其實呢,可以從緩沖函數(shù)入手,比如上面用到的緩沖函數(shù)是cubic-bezier(0.55, 0, 0.85, 0.36),是一種逐漸變快的運動。
image-20250809142326339
這時,我們可以改變第一個錨點,往下拉,直到第二個值出現(xiàn)負數(shù),如下:
image-20250809142536888
這樣會出現(xiàn)一個輕微的回彈效果,效果如下:
Kapture 2025-08-09 at 14.26.33
是不是有點像斜拋運動了?
我們可以繼續(xù)調(diào)整幅度,讓回彈效果更強烈一些,比如:
image-20250809143033923
這樣就更接近真實的斜拋運動了。
Kapture 2025-08-09 at 14.29.11
關(guān)鍵 CSS 如下:
.custom-tips-dot::before {
content: attr(emoji, '??');
animation: custom-y 1s cubic-bezier(0.56, -1.35, 0.85, 0.36) forwards;
}是不是也比較容易?
三、借助JS批量生產(chǎn)
單個粒子可能比較死板,也不夠隨機。
要實現(xiàn)隨機也比較容易,每次生成例子的水平位移不同就行了,然后多個粒子的動畫有一定的延時就可以了。
image-20250809235534054
為了方便JS透傳過去,可以借助CSS變量來生成。
這里用--x表示水平位移,--d表示延遲。
.custom-tips-dot {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
outline: 1px solid yellowgreen;
animation: custom-x 1svar(--d, 0s) linear forwards;
}
.custom-tips-dot::before {
content: attr(emoji, '??');
animation: custom-y 1svar(--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動態(tài)生成這些 dom。
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);
/*動畫結(jié)束后自動移除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);
})這樣就能每次隨機生成一定數(shù)量的emoji了,效果如下:
Kapture 2025-08-09 at 14.39.25
四、+1 動畫
數(shù)字變化動畫是最簡單的,就是一個縮放+透明度變化的動畫。
image-20250810000526217
假設(shè)HTML是這樣的:
<div class="custom-num" num="1"></div>我們用偽元素來生成+1的字符,動畫很簡單,就兩個關(guān)鍵幀,實現(xiàn)如下:
.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: 4px4px0rgba(255,0,0);
transform: translate(var(--left), var(--top));
}
.custom-num::before {
content: '+'attr(num);
opacity: 0;
animation: count-shark 1svar(--d, 0s);
}
@keyframes count-shark {
0%,100%{
opacity: 0;
transform: scale(.4);
}
30%,70%{
opacity: 1;
transform: scale(1);
}
}效果如下:
Kapture 2025-08-09 at 14.46.13
然后我們借助JS來實現(xiàn)數(shù)字增加的邏輯,其實就是移除上一個,新增一個dom,新增的會自動執(zhí)行動畫,實現(xiàn)如下:
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;
}最后兩者結(jié)合起來,就實現(xiàn)了文章開頭所示效果:
Kapture 2025-08-09 at 13.19.13
你也可以訪問在線demo真實體驗:https://codepen.io/xboxyan/pen/wBKqpXb
五、其實canvas可能更適合
一般來說,粒子動畫可能canvas來實現(xiàn)更合適一點,性能也更好,不過可能對新手不太友好。這里推薦一個開箱即用的庫:叫做 Canvas Confetti,專門用來做這種禮花綻放特效的,如果自己手搓不來,可以試試這個。
不過我們這里案例的粒子也不是很多,所以使用CSS也無所謂了。






























