Skip to main content

simple_reveal 0.1.2 copy "simple_reveal: ^0.1.2" to clipboard
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.

A travel page whose photo settles, whose title comes in piece by piece, and whose sections, figures and photos come in as it scrolls

Rows coming in from the left, then from the right   Rows revealed by a zoom, a blur, a flip, a turn and a slide

Photos uncovered by a wipe, a circle, a panel and a tint that clears   Rows following the scroll, in on the way down and back out on the way up

Video tour Live demo Pub Version Build Maintainer Licence Platforms

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,
);
  • threshold is how much of the block has to be on screen for it to start, 0.2 by default. A block taller than the screen is measured against the screen.
  • once: false hides 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, a SliverGrid or a SliverToBoxAdapter. Nested ones too: a card in a carousel inside a page waits until both have brought it into view. With scrub, scrollAxis picks 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, a BlurProperties and an OverlayProperties of 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.

2
likes
0
points
549
downloads

Publisher

verified publishercomapps.be

Weekly Downloads

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.

Homepage
Repository (GitHub)
View/report issues

Topics

#animation #scrolling #reveal #transition #ui

License

unknown (license)

Dependencies

flutter

More

Packages that depend on simple_reveal