BackgroundsFree

Floating Particles

30 CSS discs loop upward from the container bottom — position, delay, duration, size and color randomised at injection time, no canvas, no dependency.

JSParticlesLoop
Particles
Hover or click the scene to interact

This effect is free — the Backgrounds category contains 50 effects total, including 6 free. Effect.Labs has 811 vanilla effects. Explore the category →

Usage examples

① Hero background — Dark-mode tech landing page

WhenA SaaS homepage, portfolio, or dev tool wants an animated, unobtrusive background across the full viewport height.
WhyParticles spread evenly over a large surface thanks to randomised horizontal positions and staggered delays — no empty zones, no visible density spikes. The dark ground lets text content take priority without competition.
Settings30 particles, default indigo/violet/fuchsia/cyan palette. Switch translateY(-100vh) instead of -350px when the viewport is taller than 350 px.

② Success page — Sign-up or payment confirmation

WhenThe user has just completed a key action (account creation, purchase, subscription) and lands on a full-width confirmation screen.
Why30 particles rising in a continuous loop cover the entire surface and mimic a minimalist confetti effect — they visually reward the user without a heavyweight animation library. The smooth CSS loop avoids any jank.
Settings30 particles, full palette (indigo/violet/fuchsia/cyan), opacity 0.4–0.9 (0.4 + Math.random() * 0.5), translateY(-400px) for a broad sweep over a typical full-height viewport.

③ Lobby or main menu — Casual game or indie application

WhenThe dark UI of a casual game or indie app shows the main menu or a waiting room and needs an animated ambience without sacrificing performance.
WhyRising particles on an infinite loop replace a background video (heavy) or WebGL canvas (complex) with pure CSS. Slow cycle durations (10–20 s) make the animation imperceptible performance-wise and keep the menu typography fully readable.
Settings20 particles, violet/fuchsia palette (['#8b5cf6', '#d946ef']), slow cycles (10 + Math.random() * 10 → 10–20 s), low opacity (0.15 + Math.random() * 0.45), translateY(-400px).

How it works

A JavaScript IIFE targets the container via getElementById('particlesBg') and injects 30 <div class="particle"> elements into the DOM at page load. Each particle receives inline styles computed once: random horizontal position (left 0–100%), start delay (animationDelay 0–10 s), cycle duration (animationDuration 5–15 s), size (width/height 2–6 px), and initial opacity (0.3–1.0). The color is picked at random from a 4-tone array: #6366f1, #8b5cf6, #d946ef, #06b6d4.

The CSS @keyframes float animation runs on an infinite loop for every particle. It goes from translateY(0) rotate(0deg) to translateY(-350px) rotate(720deg) — 350 px upward and two full turns. Opacity fades from 0 to 1 between 0% and 10% of the cycle (fade-in), stays at 1 from 10% to 90%, then drops back to 0 (fade-out). The bottom: -20px on .particle ensures each disc emerges from below the container's bottom edge.

The .bg-particles container uses position: relative; overflow: hidden — particles overflowing the top or sides are clipped cleanly. No requestAnimationFrame, no canvas: all kinematics are delegated to the browser's CSS compositor, keeping animations smooth without blocking the JS main thread.

Accessibility

  • No prefers-reduced-motion support: the code does not detect this OS preference — the animation loops indefinitely for all users. To fix this, add @media (prefers-reduced-motion: reduce) { .particle { animation: none; opacity: 0; } } to the CSS.
  • The .bg-particles container has no role or aria-label — screen readers ignore the area. Acceptable for a purely decorative background; if overlay content is present, mark it up separately.
  • The centred .bg-label (white text on rgb(10,10,15)) provides a contrast ratio above 7:1 (WCAG AAA level), but any custom overlay text must be checked independently.
  • No keyboard interaction or focusable zone — the effect is 100% decorative. If you add a button in the overlay, ensure it stays in the normal tab order.
  • Particles are lightweight CSS circles. The 720° rotation (two full turns) may still be noticeable for users sensitive to vestibular motion — the lack of prefers-reduced-motion detection remains the effect's main accessibility limitation.

Browser compatibility

Requires CSS animations and 2D transforms — natively supported in all modern browsers without prefixes. Zero external dependencies.

Chrome 88+✓ Full
Firefox 87+✓ Full
Safari 15+✓ Full
Edge 88+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

If CSS animations are unsupported, particles remain positioned at <code>bottom: -20px</code> below the container, invisible and generating no JS errors. Overlay content stays intact and readable.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="bg-particles" id="particlesBg" style="width:100%;min-height:300px;">
  <span class="bg-label">Particles</span>
  <div class="particle" style="left: 81.3473%; animation-delay: 2.23608s; animation-duration: 5.2817s; width: 4.22835px; height: 4.22835px; opacity: 0.955616; background: rgb(99, 102, 241);">
  </div>
  <div class="particle" style="left: 70.7537%; animation-delay: 0.241083s; animation-duration: 7.42418s; width: 5.65692px; height: 5.65692px; opacity: 0.92056; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 75.672%; animation-delay: 4.05243s; animation-duration: 12.6442s; width: 4.03745px; height: 4.03745px; opacity: 0.613098; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 23.7548%; animation-delay: 6.59898s; animation-duration: 7.30182s; width: 4.68637px; height: 4.68637px; opacity: 0.997694; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 44.7656%; animation-delay: 7.28707s; animation-duration: 8.80555s; width: 5.90259px; height: 5.90259px; opacity: 0.310997; background: rgb(6, 182, 212);">
  </div>
  <div class="particle" style="left: 81.7612%; animation-delay: 8.81586s; animation-duration: 7.87704s; width: 3.18595px; height: 3.18595px; opacity: 0.642642; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 78.4019%; animation-delay: 3.56918s; animation-duration: 12.7866s; width: 5.32692px; height: 5.32692px; opacity: 0.562289; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 31.7758%; animation-delay: 8.78552s; animation-duration: 14.4494s; width: 3.18608px; height: 3.18608px; opacity: 0.947093; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 5.26617%; animation-delay: 0.0824003s; animation-duration: 9.79734s; width: 5.93249px; height: 5.93249px; opacity: 0.311894; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 83.6788%; animation-delay: 1.18016s; animation-duration: 5.3337s; width: 2.83547px; height: 2.83547px; opacity: 0.927981; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 51.7278%; animation-delay: 3.86724s; animation-duration: 7.14767s; width: 3.37888px; height: 3.37888px; opacity: 0.968284; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 7.70877%; animation-delay: 5.27914s; animation-duration: 7.62976s; width: 3.64616px; height: 3.64616px; opacity: 0.972167; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 23.1152%; animation-delay: 7.74466s; animation-duration: 12.5337s; width: 3.69188px; height: 3.69188px; opacity: 0.505243; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 95.7918%; animation-delay: 1.96144s; animation-duration: 7.13189s; width: 3.18404px; height: 3.18404px; opacity: 0.81407; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 80.5541%; animation-delay: 5.05999s; animation-duration: 14.565s; width: 4.36892px; height: 4.36892px; opacity: 0.551264; background: rgb(99, 102, 241);">
  </div>
  <div class="particle" style="left: 22.2578%; animation-delay: 0.454724s; animation-duration: 8.57647s; width: 5.41375px; height: 5.41375px; opacity: 0.686757; background: rgb(99, 102, 241);">
  </div>
  <div class="particle" style="left: 8.84408%; animation-delay: 2.93561s; animation-duration: 8.89404s; width: 4.54025px; height: 4.54025px; opacity: 0.65617; background: rgb(6, 182, 212);">
  </div>
  <div class="particle" style="left: 62.9086%; animation-delay: 9.74476s; animation-duration: 8.75279s; width: 4.68559px; height: 4.68559px; opacity: 0.927903; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 39.373%; animation-delay: 3.33201s; animation-duration: 6.02767s; width: 2.17088px; height: 2.17088px; opacity: 0.937757; background: rgb(6, 182, 212);">
  </div>
  <div class="particle" style="left: 31.1378%; animation-delay: 0.0207585s; animation-duration: 8.18352s; width: 4.40257px; height: 4.40257px; opacity: 0.847564; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 70.8041%; animation-delay: 9.84913s; animation-duration: 6.34645s; width: 5.64475px; height: 5.64475px; opacity: 0.399727; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 63.0477%; animation-delay: 1.59297s; animation-duration: 10.5432s; width: 5.17464px; height: 5.17464px; opacity: 0.571377; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 13.5519%; animation-delay: 2.21412s; animation-duration: 8.68294s; width: 5.68967px; height: 5.68967px; opacity: 0.653604; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 23.2941%; animation-delay: 5.13453s; animation-duration: 9.1288s; width: 4.0929px; height: 4.0929px; opacity: 0.729983; background: rgb(99, 102, 241);">
  </div>
  <div class="particle" style="left: 47.6701%; animation-delay: 2.03842s; animation-duration: 10.9313s; width: 3.66998px; height: 3.66998px; opacity: 0.646271; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 5.5714%; animation-delay: 3.88957s; animation-duration: 13.6884s; width: 4.12863px; height: 4.12863px; opacity: 0.554226; background: rgb(217, 70, 239);">
  </div>
  <div class="particle" style="left: 62.4054%; animation-delay: 6.76345s; animation-duration: 12.4475s; width: 4.30277px; height: 4.30277px; opacity: 0.932268; background: rgb(139, 92, 246);">
  </div>
  <div class="particle" style="left: 16.994%; animation-delay: 5.42798s; animation-duration: 8.73918s; width: 3.54539px; height: 3.54539px; opacity: 0.836459; background: rgb(6, 182, 212);">
  </div>
  <div class="particle" style="left: 39.5805%; animation-delay: 6.84787s; animation-duration: 11.154s; width: 5.40263px; height: 5.40263px; opacity: 0.705239; background: rgb(99, 102, 241);">
  </div>
  <div class="particle" style="left: 32.2585%; animation-delay: 2.6477s; animation-duration: 7.11463s; width: 3.95658px; height: 3.95658px; opacity: 0.871536; background: rgb(6, 182, 212);">
  </div>
</div>
CSS
.bg-particles  {
   position: relative;
   overflow: hidden;
   background: rgb(10, 10, 15);
   }

.particle  {
   position: absolute;
   bottom: -20px;
   width: 4px;
   height: 4px;
   background: rgb(99, 102, 241);
   border-radius: 50%;
   animation: 10s ease 0s infinite normal none running float;
   }

@keyframes float  {
   
  0%  {
   transform: translateY(0px) rotate(0deg);
   opacity: 0;
   }
  10%  {
   opacity: 1;
   }
  90%  {
   opacity: 1;
   }
  100%  {
   transform: translateY(-350px) rotate(720deg);
   opacity: 0;
   }
}

.bg-label  {
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
   font-weight: 700;
   font-size: 1.5rem;
   color: white;
   text-shadow: rgba(0, 0, 0, 0.5) 0px 2px 10px;
   z-index: 10;
   }
JavaScript (fx-0052)
// Generate particles
    (function() {
      var el = document.getElementById('particlesBg');
      if (!el) return;
      var colors = ['#6366f1', '#8b5cf6', '#d946ef', '#06b6d4'];
      for (var i = 0; i < 30; i++) {
        var particle = document.createElement('div');
        particle.className = 'particle';
        particle.style.left = Math.random() * 100 + '%';
        particle.style.animationDelay = Math.random() * 10 + 's';
        particle.style.animationDuration = (5 + Math.random() * 10) + 's';
        particle.style.width = (2 + Math.random() * 4) + 'px';
        particle.style.height = particle.style.width;
        particle.style.opacity = 0.3 + Math.random() * 0.7;
        particle.style.background = colors[Math.floor(Math.random() * colors.length)];
        el.appendChild(particle);
      }
    })();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
colors (JS array) ['#6366f1','#8b5cf6','#d946ef','#06b6d4'] Particle color palette. Swap in your brand palette — any CSS color value is accepted.
i < 30 (JS loop) 30 Number of generated particles. 15–20 for a light background, 40–60 for high density (desktop perf only).
animationDuration (JS) 5 + Math.random() * 10 → 5–15 s Full-cycle duration range. Raise the base (e.g. 8+) for slower, more contemplative particles.
animationDelay (JS) Math.random() * 10 → 0–10 s Maximum delay before first launch. Raise to 15–20 s for a more gradual spread at page load.
width / height (JS) 2 + Math.random() * 4 → 2–6 px Disc size range. Increase (e.g. 4 + Math.random() * 8) for more visible particles on large screens.
opacity (JS) 0.3 + Math.random() * 0.7 → 0.3–1.0 Initial opacity range. Reduce to 0.15–0.5 for a very subtle background, raise toward 0.8–1.0 for a bolder look.
translateY (CSS @keyframes float) -350px Upward travel distance. Switch to -100vh to cover the exact container height regardless of its size.
background (CSS .bg-particles) rgb(10, 10, 15) Container background color. Set to transparent to overlay particles on your own background.

FAQ

Can I display multiple Floating Particles instances on the same page?

The IIFE targets the fixed id particlesBg — one instance by default. For multiple zones, duplicate the JS block and change the targeted id (getElementById('particlesBg2')) plus the matching HTML container id. CSS classes .bg-particles and .particle stay shared — no need to duplicate them.

Does the effect respect prefers-reduced-motion?

No, the current code does not detect this preference. To add support, insert this CSS: @media (prefers-reduced-motion: reduce) { .particle { animation: none; opacity: 0; } }. Particles are simply hidden rather than animated — the dark background remains and overlay content is untouched.

How many particles can I display without a performance hit?

CSS animations run on the browser compositor (a thread separate from JS), so 30–60 particles stay smooth at 60 fps on desktop. On mobile, keep it between 15 and 25 to avoid GPU overheating. Animation duration (animationDuration) has no impact on performance — only the DOM node count matters.