Create Beautiful Bubble Animations with CSS
A tutorial on building floating bubble effects using pure CSS and JavaScript
When you want to add some movement as an eye-catching accent to your website, a bubble (bubble) animation is super effective. In this article, I’ll walk you through how to implement a nice bubble effect using pure CSS and JavaScript.
Final look
This effect makes bubbles that float like they’re underwater slowly rise up from the bottom of the screen. Each bubble has a random size, speed, and a bit of sideways wobble.
Step 1: HTML structure
First, prepare a container where the bubbles will live.
<div class="bubble-container">
<!-- Bubbles are generated dynamically with JavaScript -->
</div>
Step 2: Basic CSS
Define the basic styles for the container and bubbles.
.bubble-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
z-index: -1;
}
.bubble {
position: absolute;
bottom: -100px;
background: radial-gradient(
circle at 30% 30%,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.2) 50%,
transparent 70%
);
border-radius: 50%;
opacity: 0.6;
animation: rise linear infinite;
}
ヒント
By setting pointer-events: none, you ensure the bubbles don’t interfere with clicking or hovering.
Step 3: Rising animation
Define the animation that makes the bubbles float up from the bottom.
@keyframes rise {
0% {
transform: translateY(0) translateX(0);
opacity: 0;
}
10% {
opacity: 0.6;
}
90% {
opacity: 0.6;
}
100% {
transform: translateY(-110vh) translateX(var(--drift, 0px));
opacity: 0;
}
}
Step 4: Side-to-side wobble
Add some sideways wobble to make the movement feel more natural.
@keyframes wobble {
0%, 100% {
transform: translateX(0);
}
25% {
transform: translateX(-20px);
}
75% {
transform: translateX(20px);
}
}
.bubble {
/* Add to the existing styles */
animation:
rise linear infinite,
wobble ease-in-out infinite;
}
Step 5: Dynamically generate with JavaScript
Generate bubbles with random sizes and speeds.
function createBubble() {
const bubble = document.createElement('div');
bubble.className = 'bubble';
// Random size (10px ~ 60px)
const size = Math.random() * 50 + 10;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
// Random horizontal position
bubble.style.left = `${Math.random() * 100}%`;
// Random rising speed (8s ~ 15s)
const duration = Math.random() * 7 + 8;
bubble.style.animationDuration = `${duration}s, ${duration / 2}s`;
// Random sideways drift
const drift = (Math.random() - 0.5) * 100;
bubble.style.setProperty('--drift', `${drift}px`);
// Random delay
bubble.style.animationDelay = `${Math.random() * 5}s`;
return bubble;
}
function initBubbles(count = 15) {
const container = document.querySelector('.bubble-container');
for (let i = 0; i < count; i++) {
const bubble = createBubble();
container.appendChild(bubble);
}
}
// Run on page load
document.addEventListener('DOMContentLoaded', () => {
initBubbles(15);
});
Step 6: Full code
Here’s the complete code with everything put together.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bubble Animation</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
}
.bubble-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
z-index: 1;
}
.bubble {
position: absolute;
bottom: -100px;
background: radial-gradient(
circle at 30% 30%,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.3) 40%,
rgba(255, 255, 255, 0.1) 60%,
transparent 70%
);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
box-shadow:
inset 0 -5px 20px rgba(255, 255, 255, 0.1),
0 0 20px rgba(255, 255, 255, 0.1);
animation:
rise linear infinite,
wobble ease-in-out infinite;
}
@keyframes rise {
0% {
transform: translateY(0) scale(1);
opacity: 0;
}
10% {
opacity: 0.6;
}
90% {
opacity: 0.4;
}
100% {
transform: translateY(-110vh) translateX(var(--drift, 0px)) scale(0.8);
opacity: 0;
}
}
@keyframes wobble {
0%, 100% {
margin-left: 0;
}
25% {
margin-left: -15px;
}
75% {
margin-left: 15px;
}
}
</style>
</head>
<body>
<div class="bubble-container"></div>
<script>
function createBubble() {
const bubble = document.createElement('div');
bubble.className = 'bubble';
const size = Math.random() * 50 + 10;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
bubble.style.left = `${Math.random() * 100}%`;
const duration = Math.random() * 7 + 8;
bubble.style.animationDuration = `${duration}s, ${duration / 2}s`;
const drift = (Math.random() - 0.5) * 100;
bubble.style.setProperty('--drift', `${drift}px`);
bubble.style.animationDelay = `${Math.random() * 5}s`;
return bubble;
}
function initBubbles(count = 15) {
const container = document.querySelector('.bubble-container');
for (let i = 0; i < count; i++) {
container.appendChild(createBubble());
}
}
document.addEventListener('DOMContentLoaded', () => initBubbles(15));
</script>
</body>
</html>
Customization tips
Colored bubbles
.bubble.blue {
background: radial-gradient(
circle at 30% 30%,
rgba(100, 200, 255, 0.8),
rgba(100, 200, 255, 0.2) 50%,
transparent 70%
);
}
.bubble.pink {
background: radial-gradient(
circle at 30% 30%,
rgba(255, 150, 200, 0.8),
rgba(255, 150, 200, 0.2) 50%,
transparent 70%
);
}
Performance optimization
注意
If you’re displaying a lot of bubbles, add will-change: transform to enable GPU acceleration. Just be careful—using it too much increases memory usage.
.bubble {
will-change: transform, opacity;
}
Add bubbles on click
As an interactive touch, you can also add bubbles when the user clicks.
document.addEventListener('click', (e) => {
const bubble = createBubble();
bubble.style.left = `${e.clientX}px`;
bubble.style.bottom = `${window.innerHeight - e.clientY}px`;
document.querySelector('.bubble-container').appendChild(bubble);
// Remove after the animation ends
setTimeout(() => bubble.remove(), 15000);
});
Summary
By combining CSS animations with JavaScript, you can create a beautiful bubble effect. This technique can be used in situations like these:
- Background decoration on landing pages
- Websites themed around aquariums or the ocean
- Accent animations on loading screens
- Enhancing interactive user experiences with motion
Feel free to customize it for your own project and try it out.
Related Articles
Efficient Styling with Tailwind CSS
A practical guide to using Tailwind, a utility-first CSS framework
Beauty Hidden Within Simplicity
An exploration of the essence of minimalist design—and why it’s so important to be simple.

Build a lightning-fast blog with Astro
Learn how to build a performance-focused blog site by leveraging Astro’s powerful features.