I recently built my developer portfolio entirely in Flutter Web. Instead of using a template, I wanted something with real engineering depth — cinematic backgrounds, interactive particles, and proper architecture.
The most interesting technical challenge was the particle system. Here's how I solved the performance problem.
The Problem: O(n²) Neighbor Lookups
A constellation particle effect draws connecting lines between nearby particles. The naive approach checks every pair:
// O(n²) — kills performance at 50+ particles
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]);
}
}
}
With 100 particles, that's 4,950 distance checks per frame. At 60fps, it's choppy.
The Solution: Spatial Grid Hashing
Divide the viewport into cells. Each particle registers in its cell. To find neighbors, only check the 9 surrounding cells:
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 each particle only checks ~9 cells × ~5 particles = ~45 checks instead of 100. O(n) instead of O(n²).
The Full System
The particle system has more going on:
- Cursor repulsion — particles within 200px of the mouse get pushed away with spring physics
- Connection lines — drawn between particles within 120px, with opacity based on distance
-
Scene-aware — particle density and speed change based on which section you're scrolling through (controlled by a
SceneDirectorstate machine) - Lifecycle-aware — animations pause when the browser tab is hidden
- RepaintBoundary — isolated from the rest of the widget tree to avoid unnecessary repaints
The Cinematic Background Stack
The portfolio layers 7 visual elements:
- Dark base color
- Animated mesh gradient (Lissajous-curve blob movement with mouse parallax)
- Film grain overlay (pre-rasterized 256×256 texture via
toImageSync) - Constellation particles (spatial grid system)
- Vignette overlay (per-scene intensity)
- Scrollable content
- Fixed UI (sidebars, scroll dots, cursor)
Each scene (Hero, About, Experience, Projects, Contact) maps to a movie color palette — Blade Runner 2049, Dune, The Matrix, Spider-Verse, and Interstellar. A SceneDirector controller crossfades between them with 200px blend zones.
Architecture
The project uses Clean Architecture with Dart 3.x patterns:
-
abstract interface classfor domain contracts -
final classon all concrete classes -
switchexpressions andcase whenpattern guards - GetX for reactive state (scroll, scene, language, sound controllers)
- 185 automated tests
Try It
Live demo: developeryusuf.com
GitHub: Yusufihsangorgel/Flutter-Web-Portfolio
It's fully forkable — change your JSON data, photo, and meta tags. All content is data-driven.
Would love feedback, especially on the particle system performance and the scroll-driven scene architecture.
Built with Flutter 3.41, Dart 3.7, zero external UI dependencies.
Top comments (0)