How to Build a Stunning Like Animation with CSS and Canvas for Live Streams

This tutorial walks through creating a dynamic, multi‑stage like animation for H5 live streams using CSS keyframe animations and a Canvas‑based implementation, covering trajectory analysis, sprite handling, SCSS loops, performance comparison, and practical code snippets for seamless integration.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
How to Build a Stunning Like Animation with CSS and Canvas for Live Streams

1. Introduction

When watching WeChat video‑channel live streams, the rising like counter and vibration feedback are appealing, which inspired the development of a custom like animation for a Tencent Classroom H5 live room.

Final effect shows a more complex trajectory than the original, consisting of three stages: appearance with scaling, upward movement with random horizontal sway, and fade‑out while shrinking.

2. CSS Implementation of the Like Effect

2.1 Trajectory Analysis

The motion is split into vertical (y‑axis) uniform upward movement and horizontal (x‑axis) simple harmonic sway.

2.2 Trajectory Design

Keyframes combine scaling, opacity, and margin‑bottom changes to create the vertical motion, while multiple keyframe sets define different horizontal sway patterns.

@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;}}
@keyframes bubble_swing_1 {0% {margin-left:0;}25% {margin-left:-12px;}75% {margin-left:12px;}100% {margin-left:0;}}

2.3 Random Sprite Selection

Sprites are defined with SCSS loops to assign one of eight icons.

@for $i from 0 through 7 {.b#{$i} {background:url('../../images/like_sprites.png') $i * -24px 0;}}

2.4 Generating a Like Icon

JavaScript creates a container div, appends a new bubble element with random class names, and removes it after a timeout.

const addBubble = () => { const d=document.createElement('div'); d.className=`like-bubble b${cacheRef.current.bubbleIndex} bl_${swing}_${speed}`; bubbleCnt?.appendChild(d); cacheRef.current.bubbleIndex++; setTimeout(()=>bubbleCnt?.removeChild(d),2600); };

Click handling adds a bounce animation to the like button.

const onClick = () => { if(timer){clearTimeout(timer); timer=null;} likeIcon.classList.remove('bounce-click'); setTimeout(()=>likeIcon.classList.add('bounce-click'),0); timer=window.setTimeout(()=>{likeIcon.classList.remove('bounce-click');},300); addBubble(); };

2.5 Final CSS Effect

3. Canvas Implementation of the Like Effect

3.1 Canvas Creation

The canvas element is obtained by ID and its 2D context is stored along with width, height, and a scaling factor.

constructor(canvasId,canvasScale){ const canvas=document.getElementById(canvasId); this.context=canvas.getContext('2d'); this.width=canvas.width; this.height=canvas.height; this.canvasScale=canvasScale; this.img=null; this.loadImages(); }

3.2 Pre‑loading Sprite Image

loadImages=()=>{ const p=new Promise(resolve=>{ const img=new Image(); img.onerror=()=>resolve(img); img.onload=()=>resolve(img); img.src=likeSprites; }); p.then(img=>{ if(img && img.width>0){ this.img=img; } }); };

3.3 Trajectory Decomposition

Vertical motion mirrors the CSS version; horizontal motion uses a sine function y = A sin(Bx + C) + D with random amplitude and frequency.

const getTranslateX = (progress)=>{ if(progress<ENLARGE_STAGE){return basicX;} return basicX + amplitude*Math.sin(frequency*(progress-ENLARGE_STAGE)); };
const getTranslateY = (progress)=>{ return IMAGE_WIDTH/2 + (this.height-IMAGE_WIDTH/2)*(1-progress); };

3.4 Size and Opacity Calculation

const getScale = (p)=>{ let r=1; if(p<ENLARGE_STAGE){ r=p/ENLARGE_STAGE; } else if(p>FADE_OUT_STAGE){ r=(1-p)/(1-FADE_OUT_STAGE); } return r; };
const getAlpha = (p)=>{ if(p<FADE_OUT_STAGE){return 1;} return 1-(p-FADE_OUT_STAGE)/(1-FADE_OUT_STAGE); };

3.5 Canvas Rendering

return (progress)=>{ if(progress>=1) return true; context.save(); const scale=getScale(progress); const tx=getTranslateX(progress); const ty=getTranslateY(progress); context.translate(tx,ty); context.scale(scale,scale); context.globalAlpha=getAlpha(progress); context.drawImage(this.img, SOURCE_IMAGE_WIDTH*curImgIndex,0, SOURCE_IMAGE_WIDTH, SOURCE_IMAGE_WIDTH, -newWidth/2, -newWidth/2, newWidth, newWidth); context.restore(); return false; };

3.6 Animation Loop

The start function creates a render task, pushes it to a list, and triggers a requestAnimationFrame loop that clears the canvas, renders each active bubble, and removes completed tasks.

scan=()=>{ this.context.clearRect(0,0,this.width,this.height); let i=0; while(i<renderList.length){ const child=renderList[i]; if(!child.render(child.progress())){ renderList.splice(i,1); } else { i++; } } if(renderList.length) requestAnimationFrame(this.scan); };

3.7 Triggering the Animation

Clicking the like button calls start(), which adds a new render task to the canvas.

const onClick=()=>{ cacheRef.current.LikeAni?.start?.(); };

4. Performance Comparison

4.1 Frame Rendering Stats

Chrome DevTools shows that the CSS version generates many DOM elements, causing higher GPU memory usage and frequent repaint highlights, while the Canvas version draws directly on a single canvas, resulting in smoother frames and lower memory consumption.

4.2 Detailed Performance

In the Performance panel, the CSS implementation shows layout shifts and higher CPU/GPU load, whereas the Canvas implementation maintains stable frame rates and lower resource usage.

5. Related Resources

Implementation reference: https://github.com/antiter/praise-animation

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PerformanceCanvasWeb developmentcss animationlike button
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.