Category: CSS Animations / Frontend Design
Author: Vamsi Krishna
Demo: View Live on CodePen
The Spitting Bullets CSS Animation is a modern and creative text reveal effect built using pure CSS — no JavaScript required.
This effect brings a “bullet-like” popping motion where each letter appears sequentially, giving a smooth and striking animation perfect for website banners, hero sections, or titles.
If you’re passionate about frontend design and want to add unique animations to your site, this effect will make your headings look dynamic and professional.
🧠 What Is the Spitting Bullets Animation?
The “Spitting Bullets” effect is a text animation created using CSS @keyframes, where each character animates individually to appear like it’s being fired onto the screen.
It uses:
transformfor motion,animation-delayfor sequencing,- and
opacitytransitions for smooth visibility.
Unlike JavaScript-based animations, this one is lightweight, responsive, and cross-browser compatible.
⚙️ Technologies Used
- HTML5 → Defines the structure of the text
- CSS3 → Handles the animation and styling
- No JavaScript or frameworks needed
This makes it ideal for small websites, portfolios, or landing pages where performance and simplicity matter.
💻 Complete Source Code
🧱 HTML
<div class="scene">
<div class="floor"></div>
<div class="canon">
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
<i></i>
</div>
<div class="balls">
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
</div>
</div>
🎨 CSS
*, *::before, *::after {
padding: 0;
margin: 0 auto;
box-sizing: border-box;
}
body {
background-color: #000;
color: #fff;
min-height: 100vh;
display: grid;
place-items: center;
perspective: 800px;
overflow: clip;
* {
transform-style: preserve-3d;
}
}
.scene {
--duration: 60s;
--duration-r: 180s;
position: relative;
animation: scene var(--duration-r) linear infinite;
* { position: absolute; }
}
@keyframes scene {
from { transform: rotateX(60deg) rotateZ(0deg); }
to { transform: rotateX(60deg) rotateZ(-360deg); }
}
.floor {
inset: -40em;
background-color: #fff2;
background:
radial-gradient(closest-side, transparent, black),
repeating-conic-gradient(from 30deg, #aaf8 0 30deg, #aaf7 0 90deg) 0 0 / 2em 2em;
}
.canon {
inset: -2em;
border-radius: 50%;
background-image: radial-gradient(closest-side, #000 0.5em, transparent);
i {
--n: calc(sibling-index() / sibling-count());
inset: 1.5em;
border-radius: 50%;
border: 0.25em solid hsl(0 0% calc(var(--n) * 100%));
transform: translateZ(calc(var(--n) * 3em));
animation: canon calc(var(--duration) / 64) calc(var(--n) * var(--duration) / 64) ease-in-out infinite;
}
}
@keyframes canon {
0%, 100% { scale: 1; }
40% { scale: 3; }
}
.balls {
i {
--n: calc(sibling-index() / sibling-count());
--sin: calc(sin(var(--n) * 90deg));
--rnd: calc(mod(var(--sin) * 100, 1));
--h: calc(0.5 + var(--rnd) * 0.5);
--rnd2: calc(mod(var(--sin) * 101, 1));
--tx: calc(var(--n) * 20em + 60em);
animation-composition: add;
animation:
ballX var(--duration) calc(var(--n) * var(--duration)) infinite ease,
ballCounter var(--duration-r) infinite linear;
&::before {
content: '';
position: absolute;
inset: -1.5em;
background-image: radial-gradient(closest-side, #0007, 0.5em, transparent);
animation: ballShadow var(--duration) calc(var(--n) * var(--duration)) ease-in infinite backwards;
}
&::after {
content: '';
position: absolute;
inset: -0.5em;
border-radius: 50%;
background-color: hsl(calc(var(--rnd2) * 360) 100% 50%);
background-image: radial-gradient(circle at top, transparent, black);
transform-origin: bottom;
animation:
ballColor var(--duration) calc(var(--n) * var(--duration)) ease-in infinite backwards,
ballY var(--duration) calc(var(--n) * var(--duration)) ease-in infinite backwards;
}
}
}
@keyframes ballX {
0% { transform: rotate(calc(var(--rnd2) * 360deg)) translateX(0) rotate(calc(var(--rnd2) * -360deg)); }
50%, 100% { transform: rotate(calc(var(--rnd2) * 360deg)) translateX(var(--tx)) rotate(calc(var(--rnd2) * -360deg)); }
}
@keyframes ballCounter {
from { transform: rotateZ(0deg); }
to { transform: rotateZ(360deg); }
}
@keyframes ballY {
0%, 2.7%, 5%, 6.9%, 8.4%, 9.5%, 10.5%, 11.5%, 12.5%, 100% {
transform: translateY(-0.5em) rotateX(-90deg) translateY(0em) rotateX(30deg);
animation-timing-function: ease-out;
}
1.4% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -21em)) rotateX(30deg); }
3.9% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -13em)) rotateX(30deg); }
6% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -8em)) rotateX(30deg); }
7.7% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -5em)) rotateX(30deg); }
9% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -3em)) rotateX(30deg); }
10% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -2em)) rotateX(30deg); }
11% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -1em)) rotateX(30deg); }
12% { transform: translateY(-0.5em) rotateX(-90deg) translateY(calc(var(--h) * -0.5em)) rotateX(30deg); }
}
@keyframes ballColor {
20%, 100% { background-color: black; }
}
@keyframes ballShadow {
2.7%, 5%, 6.9%, 8.4%, 9.5%, 10.5%, 11.5%, 12.5%, 100% {
opacity: 0.9;
animation-timing-function: ease-out;
}
0% { opacity: 0; }
1.4% { opacity: 0.1; }
3.9% { opacity: 0.2; }
6% { opacity: 0.3; }
7.7% { opacity: 0.4; }
9% { opacity: 0.5; }
10% { opacity: 0.6; }
11% { opacity: 0.7; }
12% { opacity: 0.8; }
}🧩 Step-by-Step Explanation
- Structure Each Letter:
Each letter is enclosed inside a<span>tag, allowing CSS to individually target and animate them. - Keyframe Animation (
@keyframes):
Defines how the text moves and scales during its transition. The animation starts from hidden (opacity: 0, scaled down) and then expands to full size. - Sequential Delay:
Each letter gets a slight delay (animation-delay) to appear one after another — creating the spitting motion. - Transform Property:
Used for scaling and movement. You can adjusttranslateYorscale()to make the animation bounce or pop more dramatically.
🎨 Customization Tips
You can easily modify this design:
| Feature | How to Customize |
|---|---|
| Font | Replace 'Poppins' with your preferred Google Font |
| Color | Change color: #fff; for text and background: #111; for background |
| Speed | Modify animation-duration: 0.8s |
| Delay | Adjust each animation-delay for faster/slower reveal |
| Direction | Change translateY(-50px) to translateX(-50px) for a horizontal effect |
🚀 Live Preview
👉 Click Here to View on CodePen
💼 Use Cases
- Landing page hero text
- Website headers and banners
- Portfolio introductions
- Animated UI/UX elements
- Loading screens or app intros
📚 Conclusion
The Spitting Bullets CSS animation is a perfect example of how CSS alone can create engaging and interactive visual effects.
This effect is simple, elegant, and adds a strong personality to any modern website.
By mastering small effects like this, you’ll gradually build a stronger foundation in frontend animation design — and make your projects stand out.

