ParticleEmitter
1.1.0indexedPhysics-driven particle effects with two renderers: layout-based custom particle content and high-performance canvas; directional gravity, edge behaviors (bounce/stick/wrap), blend modes, multi-emitter orchestration.
Physics-driven particle effects with two renderers: layout-based custom particle content and high-performance canvas; directional gravity, edge behaviors (bounce/stick/wrap), blend modes, multi-emitter orchestration.
A Compose Multiplatform particle effects library for Android, iOS, desktop, and web. Create beautiful, physics-based particle animations with two rendering approaches optimized for different use cases.
📖 API documentation — generated with Dokka, published on every push to main.
ParticlesEmitter — Compose layout-based, supports custom @Composable particles (text, images, shapes)CanvasParticleEmitter — Canvas-based, high-performance rendering for 1 000+ particlesMultiEmitterSee PERFORMANCE.md for measured particle-count budgets per target frame rate (30 / 60 / 120 FPS), benchmark methodology, and sizing guidance for CanvasParticleEmitter on real hardware.
For Android projects, add the dependency to your module's build file:
dependencies {
implementation("io.github.piotrprus:particle-emitter:1.0.5")
}
For KMP projects, add the dependency like this:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.piotrprus:particle-emitter:1.0.5")
}
}
}
| Module | Description |
|---|---|
particle-emitter | Core library — emitters, particle models, configs, rendering |
samples | Demo app showcasing different particle effects on all supported platforms |
CanvasParticleEmitter(
modifier = Modifier.fillMaxSize(),
config = CanvasEmitterConfig(
particlePerSecond = 50,
emitterCenter = DpOffset(200.dp, 400.dp),
startRegionShape = CanvasEmitterConfig.Shape.POINT,
startRegionSize = DpSize(dp, dp),
particleShapes = listOf(ParticleShape.Circle),
lifespanRange = ,
fadeOutTime = ,
scaleTime = ,
colors = listOf(Color.Cyan, Color.Magenta, Color.Yellow),
particleSizes = listOf(DpSize(dp, dp), DpSize(dp, dp)),
spread = IntRange(-, ),
blendMode = BlendMode.Screen,
initialForce = IntRange(, ),
)
)
ParticlesEmitter(
config = EmitterConfig(
particlesCount = 30,
emitDurationMillis = 1000L,
particleLifespanMillis = 2000L,
initialForce = 80,
gravityStrength = 1f,
gravityAngle = 0, // 0 = down
spread = IntRange(-45, 45),
) {
// Any @Composable content as a particle
Text("✨", fontSize = 20.sp)
}
)
MultiEmitter(
modifier = Modifier.fillMaxSize(),
emitterCount = 5,
emitterDelay = 200L,
emitterConfig = EmitterConfig(
particlesCount = 20,
particleLifespanMillis = 1500L,
) {
Box(
modifier = Modifier
.size(8.dp)
.background(Color.White, CircleShape)
)
}
)
Both emitters support configurable directional gravity via two parameters:
// Falling confetti — gravity pulls particles downward
CanvasEmitterConfig(
gravityStrength = 120f,
gravityAngle = 0, // down
spread = IntRange(-45, 45),
initialForce = IntRange(80, ),
)
CanvasEmitterConfig(
gravityStrength = ,
gravityAngle = ,
)
CanvasEmitterConfig(
gravityStrength = ,
gravityAngle = -,
)
The CanvasParticleEmitter uses physics-based motion: each particle has an initial velocity (from initialForce and spread angle) and is continuously accelerated by the gravity vector. This produces natural parabolic arcs — particles launched upward will curve back down, particles with sideways gravity will drift horizontally.
The ParticlesEmitter applies the same directional gravity to its kinematic equations, allowing custom composable particles to follow realistic trajectories.
The CanvasParticleEmitter supports configurable edge behavior — controlling what happens when particles reach the composable boundary.
// Bouncing particles with 70% energy retention
CanvasEmitterConfig(
edgeBehavior = EdgeBehavior.Bounce(damping = 0.7f),
// ...
)
// Particles stick to edges
CanvasEmitterConfig(
edgeBehavior = EdgeBehavior.Stick,
// ...
)
// Particles wrap around
CanvasEmitterConfig(
edgeBehavior = EdgeBehavior.Wrap,
// ...
)
Edge collision accounts for particle size — the visual edge of the particle touches the boundary, not the center.
With a ring-shaped emitter (Shape.OVAL) and a 360° spread, some of the particles travel through the interior of the ring, cluttering the center. Set hideInStartRegion = true to skip drawing any particle whose current position is inside the start region — particles that leave the region become visible again on the far side.
// Ring emitter that keeps its interior clean
CanvasEmitterConfig(
startRegionShape = CanvasEmitterConfig.Shape.OVAL,
startRegionSize = DpSize(180.dp, 180.dp),
spread = IntRange(0, 360),
hideInStartRegion = true,
// ...
)
Particles still exist and move through the region (physics is untouched); they're just invisible while their position falls inside.
// Circle
ParticleShape.Circle
// Image with tinting
ParticleShape.Image(ImageBitmap.imageResource(R.drawable.star))
// Custom path
ParticleShape.PathShape(myCustomPath)
// Text (emoji or regular text) — requires a TextMeasurer
ParticleShape.Text(
text = "🎉",
textStyle = TextStyle(fontSize = 20.sp),
textMeasurer = rememberTextMeasurer(),
)
The sample app includes interactive demos:
| Canvas Emitter | Confetti | Glow Particles |
|---|---|---|
![]() |
| Gravity | Gravity Point | Sticky Edges |
|---|---|---|
![]() |
| Magic Wand | Emoji Rain |
|---|---|
![]() | ![]() |
| Drag a wand to leave a sparkling trail of stars | Fullscreen emoji particles using the Text shape |
Apache License 2.0
| Parameter | Type | Default | Description |
|---|
gravityStrength | Float | 0f (Canvas) / 1f (Compose) | Force magnitude in Dp/s². 0 = no gravity. |
gravityAngle | Int | 0 | Direction in degrees. 0 = down, 180 = up, -90 = right, 90 = left. |
| Behavior | Description |
|---|
EdgeBehavior.None | Particles pass through edges freely (default) |
EdgeBehavior.Bounce(damping) | Particles reflect off edges. damping controls velocity retained per bounce (0–1, default 0.7) |
EdgeBehavior.Stick | Particles stop at the edge and remain there for the rest of their lifespan |
EdgeBehavior.Wrap | Particles exiting one edge reappear on the opposite side |
| Parameter | Type | Default | Description |
|---|
hideInStartRegion | Boolean | false | When true, particles currently inside the startRegionShape + startRegionSize bounds are not drawn. Applies to OVAL and RECT regions. POINT, H_LINE, and V_LINE have no interior and the flag is a no-op. |
![]() |
![]() |
| Star particles with birth rate slider | Multi-emitter confetti with emoji and glowing stars | Glowing particles with blur and color animations |
![]() |
![]() |
| Canvas particles with gravity on/off toggle | Draggable gravity attractor with force slider | Particles bounce, stick, or wrap at edges |
Surfaced from shared tags and platforms — no rankings paid for.