Skip to main content

scroll_scene 0.1.1 copy "scroll_scene: ^0.1.1" to clipboard
scroll_scene: ^0.1.1 copied to clipboard

GSAP ScrollTrigger for Flutter: pin sections, scrub timelines by scroll progress, keyframes, stagger, snap points and debug markers. Pure Flutter.

scroll_scene #

GSAP ScrollTrigger for Flutter. Pin a section for N pixels of scroll, scrub a timeline by scroll progress, drive keyframes and staggers, snap to points, and debug it all with GSAP‑style markers. Pure Flutter, no dependencies.

Pin + stagger Pin + keyframes Horizontal from vertical
hero phone gallery
Reveal (unpinned triggers) Snap points Debug markers
reveal snap markers

Why #

pub.dev has reveal‑on‑scroll widgets, image‑sequence scrubbers and keyframe libraries, but nothing that combines pin + scrub + keyframes/stagger + snap + markers in one scroll‑aware primitive. scroll_scene is that primitive.

Install #

dependencies:
  scroll_scene: ^0.1.0

Quick start #

CustomScrollView(
  slivers: [
    SliverScrollScene(
      pinExtent: 1200,                       // stays pinned for 1200 px of scroll
      smoothing: const ScrubSmoothing.lerp(), // optional, buttery scrub
      builder: (context, scene) => AnimatedBuilder(
        animation: scene.progress,            // Animation<double>, 0 → 1
        builder: (_, _) => Opacity(
          opacity: scene.progress.value,
          child: const FlutterLogo(size: 200),
        ),
      ),
    ),
  ],
)

Inside a ListView or Column use ScrollScene with the same parameters.

Concepts #

Pin #

pinExtent makes the content stick to the viewport's leading edge while the user scrolls that many pixels. The scene occupies content + pinExtent of scroll space, so nothing jumps when it unpins. progress is the distance scrolled inside the pin divided by pinExtent.

By default the pinned content is sized to the viewport; set pinnedExtent to pin something shorter.

Triggers (unpinned scenes) #

Without pinExtent, progress runs between two ScenePositions:

SliverScrollScene(
  start: const ScenePosition.top(0.8),   // GSAP "top 80%"
  end: const ScenePosition.bottom(0.2),  // GSAP "bottom 20%"
  ...
)

ScenePosition(element: 0..1, viewport: 0..1) means "the point at element of the widget reaches the point at viewport of the viewport". Defaults are top(1) → bottom(0): the full pass through the viewport.

Scrub #

scene.progress is an Animation<double>, so it works with everything that takes one: AnimatedBuilder, FadeTransition, Tween.animate, drive.

smoothing: ScrubSmoothing.none                              // exact
smoothing: ScrubSmoothing.lerp(Duration(milliseconds: 120)) // exponential ease
smoothing: ScrubSmoothing.spring(stiffness: 180, damping: 24)

When MediaQuery.disableAnimations is true, smoothing is skipped and progress jumps. scene.rawProgress is always the unsmoothed value.

Keyframes and stagger #

SceneTimeline is an Animatable, so plug it straight into drive:

final x = scene.progress.drive(SceneTimeline.offset([
  Keyframe(0.0, Offset(0, 300)),
  Keyframe(0.4, Offset.zero, curve: Curves.easeOutCubic),
  Keyframe(1.0, Offset(0, -40)),
]));

final color = scene.progress.drive(SceneTimeline.color([
  Keyframe(0, Colors.purple),
  Keyframe(1, Colors.teal),
]));

Factories: number, offset, color, size, rect, alignment, edgeInsets, or SceneTimeline<T>(keyframes, lerp: ...) for anything else.

Stagger offsets each item's window so lists animate one after another:

for (var i = 0; i < items.length; i++)
  FadeTransition(
    opacity: scene.progress.drive(
      Stagger(index: i, count: items.length, each: 0.1),
    ),
    child: items[i],
  )

Snap #

snap: const SceneSnap(points: [0, 0.5, 1]),

When the user stops scrolling inside the scene, the scroll position animates to the nearest point.

Callbacks #

callbacks: SceneCallbacks(
  onEnter: () {},       // progress left 0 scrolling forward
  onLeave: () {},       // progress reached 1
  onEnterBack: () {},   // progress left 1 scrolling back
  onLeaveBack: () {},   // progress reached 0
  onProgress: (p) {},   // raw progress changed
),

Markers #

markers: const SceneMarkers(label: 'hero'),

Draws start / end lines on the element, scroller-start / scroller-end lines on the viewport, the pin band and a live progress badge.

Sharing progress #

Pass your own SceneController to read the progress from outside the scene, for example a fixed header or a background:

final scene = SceneController();
...
SliverScrollScene(controller: scene, pinExtent: 800, child: ...),
...
AnimatedBuilder(animation: scene.progress, builder: ...)

Descendants can also call ScrollSceneScope.of(context).

Performance #

  • Progress is computed from the ScrollPosition before the frame builds, so dependents rebuild in the same frame with no lag and no "build scheduled during frame" assertions.
  • Pinning is pure sliver layout arithmetic; the box variant translates its child at paint time.
  • Only widgets listening to progress rebuild. Content is wrapped in a RepaintBoundary by default (addRepaintBoundary: false to opt out).
  • Smoothing runs a single Ticker per scene that stops when settled.

Limitations #

  • The box variant (ScrollScene) needs an enclosing RenderAbstractViewport (ListView, SingleChildScrollView, ...). It cannot know the viewport size before its first layout, so pinned content falls back to MediaQuery size for one frame when no viewport is measurable.
  • Scenes below the viewport and outside the cache area resolve their position approximately until they are laid out; progress there is 0 anyway.
  • Viewports with a non‑zero anchor or center are not supported for trigger math.
  • Snapping waits for the ballistic scroll to finish, then animates; it does not predict the fling destination like GSAP's snap.inertia.

Example #

example/lib/main.dart (the Example tab on pub.dev) is one self‑contained file: all five scenes above, the theme and the markers toggle, built on material.dart and scroll_scene only. To run it, create an app, add the package and paste the file over lib/main.dart:

flutter create scroll_scene_demo
cd scroll_scene_demo
flutter pub add scroll_scene

Every scene (HeroScene, PhoneScene, GalleryScene, RevealScene, SnapScene) is a sliver you can drop into your own CustomScrollView. Copy it together with the colour constants and SectionLabel from the same file.

The README media is rendered headlessly from the example with tool/render_media.sh.

License #

MIT © 2026 Vachagan Hovhannisyan

2
likes
160
points
74
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

GSAP ScrollTrigger for Flutter: pin sections, scrub timelines by scroll progress, keyframes, stagger, snap points and debug markers. Pure Flutter.

Repository (GitHub)
View/report issues

Topics

#scroll #animation #scrolltrigger #parallax #sliver

License

MIT (license)

Dependencies

flutter

More

Packages that depend on scroll_scene