perspective_space

Buttery-smooth 3D perspective and parallax widgets for Flutter β€” tilt, shake, and stack layers with depth. Gesture-driven or auto-animated.

pub package Flutter License: MIT Platforms

Basic tilt Gesture tilt Entry shake Parallax stack Dialog

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.