2 min read

Create Beautiful Bubble Animations with CSS

A tutorial on building floating bubble effects using pure CSS and JavaScript

This article was automatically translated from the original Japanese version and may contain mistranslations. Please refer to the Japanese original for the most accurate wording.
Create Beautiful Bubble Animations with CSS

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