How to Build Complex CSS Animations for a Real‑World Mini‑Game
This article reviews the CSS animation techniques used in the 京喜工厂 mini‑game, covering animation principles, comparisons with SVG and JavaScript, detailed implementations of linear and step‑based animations, path animations using offset‑path and layered timing functions, and tools for visualizing and fine‑tuning keyframes.
0 Opportunity and Background
In Q1 2020 the author participated in the 京喜工厂 front‑end development, a WeChat mini‑program where users can “produce” items like masks and rice, earn prizes, and view the activity from the 京喜 mini‑program homepage.
The activity quickly reached millions of page views, attracting front‑end developers curious about the animation implementations.
The project includes native WeChat mini‑program pages and H5 pages, both using extensive CSS animations with good compatibility.
This article reviews and summarizes several animation effects, providing a hands‑on guide to the principles and implementation details of CSS animations in a real project.
The homepage effect is shown below:
0.0 Computer Animation Principles
Animation consists of many static frames displayed at a certain speed (e.g., 16 frames per second) so that the eye perceives continuous motion due to
persistence of vision. Basic visual persistence requires at least 10 fps; movies use 24 fps; typical displays refresh at 60 fps.
The two GIFs below use the same six frames but different playback speeds; 10 fps gives a smooth animation, while 2 fps feels choppy.
10 fps
2 fps
1 How Complex Can CSS Animations Be?
1.1 Animation Demonstration
京喜工厂 character walking animation (4× speed):
1.2 Animation Description and Analysis
The character enters from the right, walks around the factory, and exits to the right.
During walking, the character shows a walking motion:
When moving from right to left the character faces right; when moving left to right it faces left:
At several points the character pauses to perform work actions:
2 Why Use CSS for Complex Animations? (Comparison with SVG, JavaScript, CSS)
First, a comparison.
2.1 SVG
SVG natively supports SMIL, which allows:
(1) Animate numeric attributes (e.g.,
<animate>)
(2) Animate transform attributes (
<animateTransform>)
(3) Animate color (
<animate>or deprecated
<animateColor>)
(4) Synchronize object direction with motion path (
<animateMotion>)
These are standard animation capabilities, and SVG‑specific features like stroke‑dasharray can create stroke animations. However, WeChat mini‑programs do not support SVG or SMIL.
2.2 JavaScript
In theory JavaScript can animate anything. Typical JS animations manipulate DOM properties or canvas via
window.requestAnimationFrame()or
window.setTimeout(), achieving 60 fps. The Web Animations API offers deeper control but has poor compatibility. WeChat mini‑programs provide their own
WX Animation API, which is not web‑standard.
2.3 CSS
CSS animations are declarative, using
@keyframes. The browser computes each 16.7 ms frame automatically, avoiding JavaScript GC overhead. CSS can leverage
translateZfor GPU acceleration, and in 2020 compatibility is excellent.
Path animation via
offset-pathstill has limited support.
WeChat mini‑programs support CSS animations.
Selection
Considering the project runs on H5 and WeChat mini‑programs, the author chose CSS animations for their compatibility and familiarity.
3 Classification of CSS Animations
Based on
animation-timing-function, CSS animations are divided into linear and non‑linear:
Linear animations use
animation-timing-function = linearor cubic‑bezier curves.
Non‑linear animations use step‑timing functions.
3.1 Linear Animations
A useful Bézier curve visualizer: cubic‑bezier.com [1].
3.2 Non‑Linear Animations
Non‑linear animations are often frame‑based, using a sprite sheet and animating
background-positionwith
step‑timing‑function. Example with a character walking:
Two frames (left foot up, right foot up) are combined into a single sprite (120 × 320).
Code:
<code><div class="anim linear"></div>
<div class="anim steps"></div></code> <code>.anim{width:120px;height:160px;background-image:url(./spirit.png);background-position:0 0;background-size:100% auto;}
.linear{animation:anim-walk 0.4s linear 0s infinite;}
.steps{animation:anim-walk 0.4s steps(1) 0s infinite;}
@keyframes anim-walk{0%{background-position:0px 0px;}50%{background-position:0px -160px;}100%{background-position:0px 0px;}}</code>Result:
linear
steps
The keyframe
anim-walkchanges
background-position-yfrom 0 → -160 → 0. Linear timing produces intermediate values (0 → -40 → -80 …), while steps timing jumps directly (0 → 0 → … → -160 → -160 …).
3.3 Path Animations (How to Animate Curved Paths in CSS?)
In the project the character moves among several points.
Each point becomes a keyframe. Straight lines are easy; curves require more work.
The solution uses layered animations and different timing functions for X and Y axes. By animating two nested elements, X can use a linear timing function while Y uses a cubic‑bezier, producing a curved trajectory.
<code><div class="anim-x">
<div class="anim-y">
</div>
</div></code> <code>.anim-x{animation:anim-x 1000ms 0s linear infinite;}
.anim-y{animation:anim-y 1000ms 0s linear infinite;}
@keyframes anim-x{0%{transform:translate3d(0,0,0);}100%{transform:translate3d(300px,0,0);}}
@keyframes anim-y{0%{transform:translate3d(0,0,0);animation-timing-function:cubic-bezier(0,.26,.74,1);}100%{transform:translate3d(0,300px,0);}}</code>Reversing the timing functions (X ease‑in, Y ease‑out) creates a curve in the opposite direction.
<code>.anim-x{animation:anim-x 1000ms 0s ease-in infinite;}
.anim-y{animation:anim-y 1000ms 0s ease-out infinite;}</code>Reference: "CSS layered animation can move elements along a curved path" [3].
3.4 Putting It All Together
Additional issues:
The walking and working frame animations must not play simultaneously.
The character’s orientation must flip when moving left vs. right.
Solution: use a turn animation layer and two separate layers for walking and working, all using step‑based timing.
<code><div class="anim-turn">
<div class="anim-walk"></div>
<div class="anim-work"></div>
</div></code> <code>.anim-turn{animation:anim-turn ${allTime}ms 0s steps(1) infinite;}
.anim-walk{animation:anim-walk-opacity ${allTime}ms 0s steps(1) infinite,anim-walk 0.4s steps(1) 0s infinite;}
.anim-work{animation:anim-work-opacity ${allTime}ms 0s steps(1) infinite,anim-working 0.4s steps(1) 0s infinite;}
@keyframes anim-turn{0%{transform:scale(1,1);}50%{transform:scale(-1,1);}100%{transform:scale(1,1);}}
@keyframes anim-walk-opacity{0%{opacity:1;}50%{opacity:0;}100%{opacity:1;}}
@keyframes anim-work-opacity{0%{opacity:0;}50%{opacity:1;}100%{opacity:0;}}</code>Combine X, Y, turn, walk, and work layers:
<code><div class="anim-x">
<div class="anim-y">
<div class="anim-turn">
<div class="anim-walk"></div>
<div class="anim-work"></div>
</div>
</div>
</div></code> <code>.anim-x{animation:anim-x ${allTime}ms 0s linear infinite;}
.anim-y{animation:anim-y ${allTime}ms 0s linear infinite;}
.anim-turn{animation:anim-turn ${allTime}ms 0s steps(1) infinite;}
/* additional keyframes omitted for brevity */</code>3.5 Building a Visualization Tool to Boost Efficiency
The author created a Vue‑based tool to generate and edit keyframes, preview animations, and export CSS.
Key UI actions:
Add keyframe
Adjust each keyframe’s properties
Generate test animation and output CSS
Implementation details include drawing the animation path by sampling X and Y positions according to their respective timing functions, then connecting the sampled points.
<code>const moveTo=[0,0];
const step=100;
const dX=300;
const dY=300;
const timeFunX="linear";
const timeFunY="cubic-bezier(0,.26,.74,1)";
if(Math.abs(dX)>0||Math.abs(dY)>0){
ctx.moveTo(moveTo[0],moveTo[1]);
for(let i=0;i<=step;i++){
const x=getTimeFunctionValue(timeFunX,i/step)*dX+moveTo[0];
const y=getTimeFunctionValue(timeFunY,i/step)*dY+moveTo[1];
ctx.fillRect(x,y,1,1);
if(i%10===0){
ctx.font="16px serif";
ctx.fillText(`(${x},${y.toFixed(2)})`,x+20,y+20);
}
}
}</code>The resulting visualization shows the sampled points and their coordinates.
3.6 Computing Bézier Timing Function Values
To evaluate a cubic‑bezier timing function, solve the parametric cubic equation for T (time progress) given the control points (P1, P2). Then compute P (progress) using the solved T. Only the real root within [0,1] is valid.
3.7 How Animation Timing Is Calculated
The path length can be approximated by sampling points; animation duration = length / speed, where speed is user‑defined.
4 Other Topics
4.1 Fixing Layer Order Issues (translateZ)
In a conveyor‑belt animation, later items overlapped earlier ones. Using
translateZwith perspective creates a 3D effect that makes the middle items appear higher.
<code>@keyframes anim-z{0%{transform:perspective(500px) translateZ(0);}50%{transform:perspective(500px) translateZ(50px);}100%{transform:perspective(500px) translateZ(0);}}</code>4.2 Solving Sprite‑Animation Jitter
Jitter occurs due to rounding errors when converting rem/rpx to px on different devices. The author adopted a media‑query approach, defining explicit pixel dimensions for each breakpoint to avoid rounding.
<code>@media screen and (min-width:300px) and (max-width:349px){
.m_worker_employee{width:51px;height:68px;}
@keyframes anim-working{0%{background-position:0px -204px;}50%{background-position:0px -272px;}100%{background-position:0px -204px;}}
/* additional breakpoints omitted for brevity */</code>5 Formula Related
The formulas in this article were created with TeX and rendered as SVG via MathJax. See https://www.mathjax.org/#demo [5].
References
[1] ✿ cubic-bezier.com: https://cubic-bezier.com/
[2] offset-path-css-animation: https://www.zhangxinxu.com/wordpress/2017/03/offset-path-css-animation/
[3] CSS layered animation can move elements along a curved path: https://jinlong.github.io/2016/01/14/moving-along-a-curved-path-in-css-with-layered-animation/
[4] 《CSS技巧:逐帧动画抖动解决方案》: https://aotu.io/notes/2017/08/14/fix-sprite-anim/
[5] https://www.mathjax.org/#demo: https://www.mathjax.org/#demo
WecTeam
WecTeam (维C团) is the front‑end technology team of JD.com’s Jingxi business unit, focusing on front‑end engineering, web performance optimization, mini‑program and app development, serverless, multi‑platform reuse, and visual building.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.