simple_reveal 0.1.2
simple_reveal: ^0.1.2 copied to clipboard
Scroll reveal widgets for Flutter, in pure Dart with no dependencies. Blocks fade, slide, zoom, blur, turn or flip into place as they come into view, or with the scroll.
Simple Reveal #
Scroll reveal for Flutter, in pure Dart with no dependencies. Blocks fade, slide, zoom, blur, turn, flip, clear or open into place as they come into view, the way the sections of a web page do, and the pieces inside them can follow one after the other.
Install #
flutter pub add simple_reveal
Requires Flutter 3.13 or later.
One block #
SimpleReveal(
child: Card(child: Text('Fades in once seen')),
);
From either side #
ListView(
children: <Widget>[
for (int i = 0; i < sections.length; i++)
SimpleReveal(
slide: i.isEven
? const SlideProperties.fromLeft()
: const SlideProperties.fromRight(),
child: sections[i],
),
],
);
That is all it takes. Everything below is optional.
Effects #
Each effect describes the block before it is revealed, and the reveal runs it to the block as it is laid out. They combine freely:
SimpleReveal(
fade: const FadeProperties(),
slide: const SlideProperties.fromBottom(40),
zoom: const ZoomProperties(0.9),
blur: const BlurProperties(8),
rotate: const RotateProperties(-0.02),
flip: const FlipProperties.aroundX(-0.15),
overlay: const OverlayProperties.darken(0.6),
wipe: const WipeProperties.fromStart(),
child: card,
);
| Parameter | Before the reveal |
|---|---|
fade |
Transparent, 0 by default. On unless you pass fade: null. |
slide |
Away from its place, in pixels: .fromLeft, .fromRight, .fromTop, .fromBottom, and .fromStart and .fromEnd, which follow the reading direction. |
zoom |
At another scale: 0.8 grows into place, 1.2 settles into it. |
blur |
Soft, as a sigma in pixels. |
rotate |
Turned in the plane of the screen, in turns. |
flip |
Turned in depth, in perspective: .aroundX like a flap, .aroundY like a door. |
overlay |
Tinted: .darken, .lighten, a colour or a .gradient, clearing as it comes in. The tint keeps to what the block draws, rounded corners and text included. |
wipe |
Covered, then uncovered from an edge, from a point as a rectangle, or as a .circle. Given a color, it comes out from under a panel, which keeps to its shape as a tint does. |
zoom, rotate and flip turn about an alignment, the middle of the block by default.
clipBehavior: Clip.hardEdge keeps the block within its bounds while it comes in, so a title
sliding up out of .fromBottom rises from an invisible line.
Pieces one after the other #
Wrap pieces of a block in RevealPart, as deep as they sit. Once the block is revealed they come
in one after the other, stagger apart in reading order, each with its own effects and delay:
SimpleReveal(
fade: null, // the block holds still, its pieces move
stagger: const Duration(milliseconds: 150),
child: Column(
children: <Widget>[
RevealPart(
slide: const SlideProperties.fromBottom(40),
clipBehavior: Clip.hardEdge,
child: Text('Built to last', style: headline),
),
RevealPart(blur: const BlurProperties(6), child: Text(body)),
RevealPart(
zoom: const ZoomProperties(0.6),
curve: Curves.easeOutBack,
delay: const Duration(milliseconds: 200),
child: FilledButton(onPressed: onStart, child: const Text('Start')),
),
],
),
);
Parts take the block's duration and curve unless given their own, and follow it in everything else: hidden with it, played again with it, in place with it.
Timing #
SimpleReveal(
duration: const Duration(milliseconds: 800),
delay: const Duration(milliseconds: 200),
curve: Curves.easeOutBack,
threshold: 0.5,
once: false,
child: card,
);
thresholdis how much of the block has to be on screen for it to start,0.2by default. A block taller than the screen is measured against the screen.once: falsehides the block again once it is out of sight, and plays the reveal each time it comes back.- A curve that overshoots, such as
Curves.easeOutBack, gives a slide or a zoom a bounce.
A SimpleRevealGroup has the blocks below it that come into view together come in one after the
other, in reading order, so a grid needs no delay worked out by hand, whatever its number of
columns:
SimpleRevealGroup(
interval: const Duration(milliseconds: 120),
child: GridView.count(crossAxisCount: columns, children: revealedCards),
);
A SimpleRevealDefaults sets the duration, curve, threshold and once of every block below it,
and enabled: false turns them all off, in a test for instance.
From code #
final RevealController controller = RevealController();
SimpleReveal(
controller: controller,
manual: true, // waits for the controller alone
onReveal: () => analytics.sectionViewed('pricing'),
child: results,
);
controller.reveal(); // then hide(), replay(), isRevealed
onReveal and onHide fire as the block comes and goes, whether it was seen or told to.
Following the scroll #
With scrub, the reveal follows the scroll instead of a clock: a block half way in is half
revealed, and scrolling back runs it backwards.
SimpleReveal(
slide: const SlideProperties.fromLeft(200),
scrub: const ScrubProperties(reach: 0.5, mirror: true),
child: card,
);
The reveal starts as the leading edge of the block comes in, and is done once that edge is reach
of the way across the viewport. mirror hides it again as it leaves at the other end.
Reduced motion #
When the platform asks for reduced motion, nothing moves, whatever the effect: blocks and their
parts are drawn in place from the first frame, groups hold nothing back, a scrubbed block does not
follow the scroll, and a reveal or a hide asked of a controller happens at once. onReveal and
onHide still fire, so a section is still counted as viewed. Pass respectReducedMotion: false
on a block to keep its reveal regardless; SimpleRevealDefaults deliberately cannot turn it off
for a whole app.
Good to know #
-
Only the painting moves. The block keeps its place in the layout from the first frame, so nothing around it jumps.
-
It works in any scrollable, either axis, reversed or not, slivers included: put it in a
SliverList, aSliverGridor aSliverToBoxAdapter. Nested ones too: a card in a carousel inside a page waits until both have brought it into view. Withscrub,scrollAxispicks which one it follows. -
Outside a scrollable, the block is revealed as soon as it is laid out on screen, which suits the top of a landing page.
-
A block not yet revealed stays in the semantics tree, so a screen reader reads it all the same. While nothing of it shows it does not answer taps.
-
In a list built lazily, a block scrolled far away is disposed. Give it a
PageStorageKey, or give one to the item the list builds around it, and it comes back in place rather than playing again. A key on the list itself is shared by every block in it, so it is not used. -
On a desktop and in a browser, Flutter lands each wheel notch in one step, and a scrubbed block steps with it. The example eases the wheel with a controller of its own, in
example/lib/smooth_wheel.dart; the package leaves the scroll views to you. -
simple_parallax has a
ZoomProperties, aBlurPropertiesand anOverlayPropertiesof its own. In a file that uses both packages, import one of them with a prefix:import 'package:simple_reveal/simple_reveal.dart' as reveal; reveal.SimpleReveal( zoom: const reveal.ZoomProperties(0.8), child: card, );
Every parameter is documented in the API reference.
Performance #
A frame of the reveal repaints the block alone and rebuilds no widget: while it plays the block is a repaint boundary of its own, its content is recorded once, and only the layers of the effects over it change. A delay waited out paints nothing. A block in place pushes no effect layer at all, so a page of revealed blocks costs little more than the same page without them. A block waiting to be revealed looks where it stands once per frame drawn at most, first against the nearest scrollable, which is where most are told to be out of view. No frame drawn, no look taken; a block on a hidden tab or a covered page does not look at all, and a block stops looking once revealed. The blur is the one effect worth profiling on an older phone while it runs.
Example #
example/ holds one screen per feature.
cd example && flutter run
License #
MIT, see LICENSE.