Simple Parallax

Parallax widgets for Flutter, in pure Dart. Two modes, either axis, any ImageProvider, and no dependencies beyond the Flutter SDK.

Container mode, scrolling down   Container mode, scrolling sideways

Item mode, scrolling down   Item mode, scrolling sideways

Container mode above, item mode below; scrolling down on the left, sideways on the right.

Build Pub Version Maintainer License Maintenance Platforms

Install

flutter pub add simple_parallax

Requires Flutter 3.22 or later.

Container mode

One background drifting behind a scrolling area. autoSpeed derives the speed from the real scroll extent, so the background uses exactly the travel overscan gives it and never runs out of image:

SimpleParallaxContainer(
  image: const AssetImage('assets/images/background.webp'),
  autoSpeed: true,
  overscan: 1.5,
  child: Column(children: items),
);
Parameter Default Effect
image required Any ImageProvider: asset, network, file or memory.
child required The scrolling content, laid out as a single box sliver.
slivers required The scrolling content as slivers, on the .slivers constructor.
scrollDirection Axis.vertical The axis the content scrolls and the background drifts along.
speed 0.3 Background travel per pixel scrolled. Ignored when autoSpeed is set.
autoSpeed false Derives the speed from the scroll extent.
overscan 1.5 How much larger than the viewport the background is drawn along the scroll axis.
height null Forces the viewport height instead of using the constraints.
width null Forces the viewport width instead of using the constraints.
fit BoxFit.cover How the background fills its layer.
alignment Alignment.center How the background sits inside its layer.

Slivers

The container is a CustomScrollView, and child is put in a single box sliver. Use the .slivers constructor instead to hand it the slivers yourself, so the content builds as it scrolls and other slivers can ride over the background:

SimpleParallaxContainer.slivers(
  image: const AssetImage('assets/images/background.webp'),
  autoSpeed: true,
  slivers: <Widget>[
    const SliverAppBar(title: Text('Chapters'), floating: true),
    SliverList.builder(
      itemCount: 500,
      itemBuilder: (BuildContext context, int index) =>
          ListTile(title: Text('Chapter $index')),
    ),
  ],
);

Everything else behaves the same: the background still drifts along scrollDirection, and autoSpeed still reads the real scroll extent. On a long list, prefer a fixed speed: autoSpeed spreads the travel overscan allows over the whole extent, so the drift becomes imperceptible.

Item mode

Each block slides its own background as it crosses the viewport. The item finds the enclosing Scrollable by itself, so it works in a ListView, a CustomScrollView, or anything else that scrolls:

ListView(
  children: <Widget>[
    SimpleParallaxItem(
      image: const NetworkImage('https://example.com/cover.jpg'),
      height: 300,
      child: const Center(child: Text('Chapter one')),
    ),
  ],
);
Parameter Default Effect
image required Any ImageProvider.
child null Content drawn over the background.
speed 1.0 Fraction of the available travel used, from 0 to 1.
overscan 1.5 How much larger than the item the background is drawn along the scroll axis.
height screen height, or constraints when horizontal Item height.
width constraints, or screen width when horizontal Item width.
fit BoxFit.cover How the background fills its layer.

SimpleParallaxWidget is a convenience scroll view for a list of items. It is a CustomScrollView over one SliverList, so the blocks build as they come into view and each one is laid out across the full cross axis, the way a ListView lays its children out.

SimpleParallaxWidget(
  children: <Widget>[
    const SimpleParallaxItem(image: AssetImage('assets/a.webp'), height: 300),
    Container(height: 400, color: Colors.blueGrey),
  ],
);

Scrolling sideways

Both modes work on either axis. The container takes a scrollDirection, exactly like a ListView:

SimpleParallaxContainer(
  image: const AssetImage('assets/images/background.webp'),
  scrollDirection: Axis.horizontal,
  autoSpeed: true,
  child: Row(children: items),
);

An item has nothing to pass: it reads the axis from the scrollable it sits in, so dropping it into a horizontal list is enough. Give it a width there, the way you give it a height in a vertical one:

ListView(
  scrollDirection: Axis.horizontal,
  children: <Widget>[
    SimpleParallaxItem(
      image: const AssetImage('assets/images/background.webp'),
      width: 300,
      child: const Center(child: Text('Chapter one')),
    ),
  ],
);

SimpleParallaxWidget takes the same scrollDirection and lays its blocks out along that axis.

How it performs

Scrolling repaints the background and nothing else. In container mode the moving layer sits behind a RepaintBoundary and only its transform is rebuilt, so your content is built once. In item mode the background is painted by a Flow bound directly to the scroll position, which repaints without rebuilding a single widget.

Both scroll views are CustomScrollViews, so content handed over as slivers is built only as far as the viewport reaches.

Migrating from 0.1.x

Before Now
imagePath: 'assets/a.webp' image: AssetImage('assets/a.webp')
decal: 1.5 overscan: 1.5
SimpleParallaxItem(speed: 0.3) speed is a 0..1 fraction now, default 1.0
SimpleParallaxItem only inside SimpleParallaxWidget works inside any scrollable
autoSpeed needed a GlobalKey on your child nothing to pass

The package is no longer a Flutter plugin: the native platform folders are gone, and so is the provider dependency.

Dependencies

None beyond the Flutter SDK.

Example

example/ is one app with four screens, one per combination: container mode and item mode, each vertical and horizontal.

cd example && flutter run

Tests

flutter test

License

MIT, see LICENSE.

Libraries

simple_parallax
Parallax widgets for Flutter, in pure Dart and with no dependencies.