perspective_space
Buttery-smooth 3D perspective and parallax widgets for Flutter β tilt, shake, and stack layers with depth. Gesture-driven or auto-animated.
perspective_space gives you depth on every Flutter platform with two
primitives and three presets β and zero third-party dependencies.
π Live demo β Β· δΈζζζ‘£ β
Features
PerspectiveSpace+PerspectiveLayerβ a tiny pair of widgets that publish a shared rotation/perspective and let descendant layers render with depth-aware parallax.- Gesture-driven tilt β drag to rotate, configurable sensitivity, clamped max angle, elastic spring-back on release.
- Entry shake β one-shot 3D flip-and-settle when the widget mounts.
- Presets for the 90% case:
PerspectiveTiltCardβ single tilting card, one-line setup.PerspectiveParallaxβ multi-layer parallax stack from a list.PerspectiveShakeEntryβ wrap any widget, play a shake on entry.
- Controller for triggering shakes from outside the subtree.
- Zero dependencies, pure Dart + Flutter SDK.
Install
dependencies:
perspective_space: ^0.1.0
flutter pub add perspective_space
Quick start
A tilting card
import 'package:perspective_space/perspective_space.dart';
PerspectiveTiltCard(
child: Container(
width: 280,
height: 360,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFF5BAA), Color(0xFF8B5CF6)],
),
borderRadius: BorderRadius.circular(28),
),
alignment: Alignment.center,
child: const Text('TILT'),
),
);
Drag it β every Flutter platform, no glue code.
Multi-layer parallax
PerspectiveParallax(
layers: [
PerspectiveLayerSpec(elevation: 0, child: backgroundCard),
PerspectiveLayerSpec(elevation: 40, child: middleCard),
PerspectiveLayerSpec(elevation: 90, child: foregroundContent),
],
)
Higher elevation = closer to the camera = stronger parallax offset.
One-shot entry shake
PerspectiveShakeEntry(
delay: const Duration(milliseconds: 200),
child: yourRewardCard,
)
Hand-rolled with the primitives
When you outgrow the presets, drop straight to PerspectiveSpace +
PerspectiveLayer:
PerspectiveSpace(
enableGesture: true,
maxRotation: 30,
child: Stack(
children: [
PerspectiveLayer(elevation: 0, child: background),
PerspectiveLayer(elevation: 50, child: card),
PerspectiveLayer(elevation: 90, child: content),
],
),
);
Imperative shake
final controller = PerspectiveSpaceController();
PerspectiveSpace(
controller: controller,
child: ...,
);
// Later, from a button handler:
controller.shake();
API at a glance
| Widget | Purpose |
|---|---|
PerspectiveSpace |
Root container; publishes rotation + perspective. |
PerspectiveLayer |
Child layer; applies a depth-aware transform. |
PerspectiveSpaceController |
Imperative handle (controller.shake()). |
PerspectiveTiltCard |
Preset: a single tilting card. |
PerspectiveParallax |
Preset: a parallax stack from PerspectiveLayerSpecs. |
PerspectiveShakeEntry |
Preset: shake on first mount, then rest. |
Useful PerspectiveSpace parameters
| Parameter | Default | Notes |
|---|---|---|
rotateX, rotateY |
0 |
Initial tilt, in radians. |
perspective |
0.0015 |
1 / cameraDistance; bigger = stronger foreshortening (0.001β0.002 is the sweet spot). |
enableGesture |
false |
Drag to tilt in real time. |
sensitivity |
0.005 |
Radians per logical pixel of drag. |
maxRotation |
60 |
Hard cap on the gesture-driven tilt, in degrees. |
resetOnRelease |
true |
Spring back to (rotateX, rotateY) on pointer up. |
resetDuration |
800ms |
Spring-back duration. |
resetCurve |
Curves.elasticOut |
Spring-back curve. |
entryShake |
false |
Play a one-shot shake on first mount. |
Platform support
| iOS | Android | Web | macOS | Windows | Linux |
|---|---|---|---|---|---|
| β | β | β | β | β | β |
perspective_space is pure Dart; everything renders through Flutter's own
3D Transform. There are no plugin channels and no native code.
FAQ
Q. How does PerspectiveSpace differ from a plain Transform?
It publishes its rotation to descendants and lets PerspectiveLayers
contribute their own Z offset. The result is a single, coherent perspective
camera with independent parallax depth per layer β instead of nested
Transforms that double-stack matrices.
Q. Why does my nested PerspectiveLayer look subtle?
By design β an inner PerspectiveLayer only contributes additional
parallax offset, never re-applies the perspective + rotation matrices.
Increase its elevation, or move it up to the same Stack as the outer
layer if you want a stronger effect.
Q. Is the entry shake configurable? Today the shake amplitude and timing are fixed for snappy feel. PRs welcome if you need it tunable.
Example
A full showcase app (the source of the GIFs above) lives in
example/. To run it locally:
cd example
flutter run # any platform
Contributing
Issues and PRs welcome β particularly around new presets, platform-specific tuning, and golden tests. Run the test suite with:
flutter test
cd example && flutter test
License
MIT Β© 2026 cccmax.
Libraries
- perspective_space
- Buttery-smooth 3D perspective and parallax widgets for Flutter.