Spatial Grid Particle System in Flutter Web

Building a cinematic portfolio in Flutter Web meant solving one brutal problem: checking distances between 100+ particles without tanking the framerate. The solution isn't new, but how it's implemented here—and why it matters for web developers—reveals something important about optimization thinking.

How One Developer Solved the Particle System Problem That Kills Web Performance — theAIcatchup

Key Takeaways

  • Spatial grids reduce particle neighbor-checking from O(n²) to O(n), transforming 100+ particles from choppy to smooth
  • Real-time web graphics require thoughtful isolation (RepaintBoundary, tab-aware lifecycle) that most developers overlook
  • A portfolio built with production-grade architecture—Clean Architecture, 185 tests, data-driven design—signals different engineering habits than template-based alternatives

At 50 particles, the naive approach starts stuttering. At 100, it’s barely usable. The culprit? Checking every particle against every other particle, every single frame.

That’s the opening problem a developer faced when building a spatial grid particle system for an interactive Flutter Web portfolio. And it’s more relevant than it sounds—not because everyone needs constellation effects, but because the architectural decision made here applies to any real-time system that needs to find neighbors in 2D space: collision detection, proximity-based interactions, spatial queries.

The math is brutal. With n particles, the naive O(n²) algorithm means 100 particles = 4,950 distance checks per frame. Multiply that by 60 frames per second. Your browser melts.

The Spatial Grid Trick That Developers Keep Forgetting About

So here’s the move: don’t check all particles against all other particles. Divide the viewport into a grid of cells. Each particle lives in exactly one cell. To find neighbors, only scan the 9 surrounding cells.

Instead of:

// O(n²) — terrible
for (var i = 0; i < particles.length; i++) {
  for (var j = i + 1; j < particles.length; j++) {
    if (distance(particles[i], particles[j]) < threshold) {
      drawLine(particles[i], particles[j]);
    }
  }
}

You get this:

final class _SpatialGrid {
  final double cellSize;
  final Map<int, List<_Particle>> _cells = {};

  int _key(double x, double y) {
    final cx = (x / cellSize).floor();
    final cy = (y / cellSize).floor();
    return cx * 10000 + cy;
  }

  void insert(_Particle p) {
    (_cells[_key(p.x, p.y)] ??= []).add(p);
  }

  List<_Particle> neighbors(double x, double y) {
    final cx = (x / cellSize).floor();
    final cy = (y / cellSize).floor();
    final result = <_Particle>[];
    for (var dx = -1; dx <= 1; dx++) {
      for (var dy = -1; dy <= 1; dy++) {
        final cell = _cells[(cx + dx) * 10000 + (cy + dy)];
        if (cell != null) result.addAll(cell);
      }
    }
    return result;
  }
}

Now you’re checking ~45 particles instead of 100. That’s not incremental improvement—that’s the difference between 60fps and 15fps.

Why This Matters Beyond Pretty Backgrounds

Here’s the thing: this pattern is ancient. Game engines have used spatial partitioning since the ’90s. Physics engines, rendering systems, network MMOs—they all lean on it. But web developers often rediscover it the hard way, building the naive version first, hitting performance walls, then (maybe) refactoring.

That’s the real story. Not that this developer used spatial grids—but that they thought about it from the start.

“Instead of using a template, I wanted something with real engineering depth — cinematic backgrounds, interactive particles, and proper architecture.”

That’s not flexing. That’s the philosophy. Too many portfolios copy Framer templates, sprinkle in animations, and call it done. This one asked: what if I actually engineered this?

The Layers Beyond the Grid

But the particle system doesn’t stop at spatial grids. The implementation gets more interesting:

  • Cursor repulsion: Particles within 200px of the mouse get pushed away using spring physics. This is real-time interactivity, not a canned animation.

  • Distance-based opacity: Connection lines fade based on distance. This keeps the visual feel of a living system—closer particles are more connected.

  • Scene-aware density: A SceneDirector state machine controls particle speed and density per section. Scrolling to “Experience” changes the particle behavior. It’s not a global effect; it’s responsive to context.

  • Tab-aware lifecycle: When the browser tab loses focus, animations pause. This is a small detail that separates thoughtful optimization from oversight. Running particle updates on a hidden tab is pure waste.

  • Isolated repaints: Using RepaintBoundary means the particle canvas doesn’t force the entire widget tree to rebuild. Again—small decision, massive performance impact.

The portfolio itself is architected as 7 visual layers:

  1. Dark base
  2. Animated mesh gradient (Lissajous curves + mouse parallax)
  3. Film grain overlay (pre-rasterized 256×256 texture)
  4. Constellation particles
  5. Vignette overlay (per-scene intensity)
  6. Scrollable content
  7. Fixed UI

Each scene (Hero, About, Experience, etc.) maps to a movie color palette—Blade Runner 2049, Dune, The Matrix, Spider-Verse, Interstellar. This isn’t random nostalgia. It’s a design system that’s memorable. You don’t just scroll through content; you move through worlds.

The Architecture Underneath

Where this gets serious: the code is built on Clean Architecture principles. Dart 3.x patterns throughout—abstract interface class for contracts, final class for concrete types, switch expressions with pattern guards. GetX handles reactive state (scroll, scene, language, sound).

And there are 185 automated tests.

That’s not something you see in most portfolio projects. Tests are for “real” projects, supposedly. But this developer built the portfolio with the rigor of a real project. That distinction—treating a portfolio like production code—is exactly the kind of signal that matters in hiring.

The whole thing is data-driven. Want to fork it? Change your JSON, swap your photo, update meta tags. It’s fully portable.

The Unspoken Problem With Web Graphics

Flutter Web is a weird space. You’re not building in Canvas directly—you’re riding on top of HTML/CSS rendering, but with Dart’s type system and performance characteristics. It’s powerful, but it exposes you to every efficiency mistake. Build a particle system wrong, and the browser tab becomes a CPU killer.

This project shows that you can build cinematic, performant interfaces in Flutter Web if you respect the constraints. Spatial grids, RepaintBoundary isolation, pre-rasterized textures for film grain—these aren’t Flutter-specific tricks. They’re how you think about performance.

The skeptical take: is this overkill for a portfolio? Maybe. You could do something good-enough with a simpler approach. But “good-enough” doesn’t teach you anything. And when you eventually hit performance problems in a real project, you’ll recognize the patterns because you’ve already lived them.


🧬 Related Insights

Frequently Asked Questions

What is a spatial grid particle system? A spatial grid divides 2D space into cells. Each particle registers in its cell. To find neighbors, you only check surrounding cells instead of all particles. This drops complexity from O(n²) to O(n), making real-time particle effects feasible.

Can I use this particle system in my own Flutter Web project? Yes. The GitHub repo is fully forkable. Change the data (JSON), swap images, and adjust the scene palettes. The architecture is modular enough that you can extract just the particle system if needed.

Why does this matter if I don’t build portfolios? Spatial grids apply to any proximity-based system: collision detection, network visibility culling, geospatial queries. Understanding when and how to apply them separates mediocre performance from solid performance.

Aisha Patel
Written by

Former ML engineer turned writer. Covers computer vision and robotics with a practitioner perspective.

Frequently asked questions

What is a spatial grid particle system?
A spatial grid divides 2D space into cells. Each particle registers in its cell. To find neighbors, you only check surrounding cells instead of all particles. This drops complexity from O(n²) to O(n), making real-time particle effects feasible.
Can I use this particle system in my own Flutter Web project?
Yes. The GitHub repo is fully forkable. Change the data (JSON), swap images, and adjust the scene palettes. The architecture is modular enough that you can extract just the particle system if needed.
Why does this matter if I don't build portfolios?
Spatial grids apply to any proximity-based system: collision detection, network visibility culling, geospatial queries. Understanding *when* and *how* to apply them separates mediocre performance from solid performance.

Worth sharing?

Get the best AI stories of the week in your inbox — no noise, no spam.

Originally reported by Dev.to

Stay in the loop

The week's most important stories from theAIcatchup, delivered once a week.