PlAnimateReveal
Content uncovered behind a moving edge. The only entrance in the set where nothing moves and no colour changes. Every pixel it has drawn is already where it will finally be.
import { PlAnimateReveal } from 'plass-ui';
<PlAnimateReveal render={<h2 />}>Everything is where it was.</PlAnimateReveal>;
<PlAnimateReveal from="top" trigger="visible" duration={700}>
<PlDivider />
</PlAnimateReveal>;import 'package:plass_ui/plass_ui.dart';
const PlAnimateReveal(child: PlTypography('Everything is where it was.', level: PlTypographyLevel.h3));
const PlAnimateReveal(
from: PlassSide.top,
trigger: PlassAnimateTrigger.visible,
duration: Duration(milliseconds: 700),
child: PlDivider(),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | 'in' | 'out' | 'in' | Whether the content is uncovered or covered again. out is the same wipe run backwards, so it closes from the edge it opened towards |
| fromshared | 'top' | 'right' | 'bottom' | 'left' | 'left' | Which edge the wipe starts at. Physical, as it is everywhere in the library: a heading uncovered from the top is uncovered from the top in every writing direction |
| fade | boolean | false | Fades in behind the wipe. Off by default, the opposite of every other effect, because a reveal not being a fade is the whole point of it |
| durationshared | number | 520 | How long one run takes, in milliseconds. A number, never a CSS string |
| delayshared | number | 0 | How long before it starts, in milliseconds |
| easingshared | string | the house curve | The easing curve, written the way CSS writes it |
| repeatshared | number | 'infinite' | 1 | How many times it runs. 'infinite' rather than Infinity, because that word is what reaches CSS |
| alternateshared | boolean | false | Runs every other pass backwards, so a repeat returns instead of jumping |
| pausedshared | boolean | false | Holds the animation where it is |
| triggershared | 'mount' | 'visible' | 'hover' | 'manual' | 'mount' | What starts it: mount as soon as it is on the page, visible when it is scrolled into view, hover while the pointer or focus is on it, manual only when play says so |
| playshared | boolean | — | Runs it when trigger is manual. Each false → true starts it over |
| onceshared | boolean | true | With trigger="visible", whether it runs only the first time. Off, it runs again every time the element comes back into view |
| thresholdshared | number | 0.2 | With trigger="visible", how much of the element has to be on screen before it counts as visible, from 0 to 1 |
| timelineshared | 'auto' | 'view' | 'auto' | What advances the animation: the clock, or the reader's scroll position. view ignores duration, delay, repeat and trigger, and runs against range instead |
| rangeshared | string | 'entry 0% cover 45%' | As CSS writes an animation-range. Only read when timeline is view |
| staggershared | number | 0 | Milliseconds added to each child's delay. 0 plays the box itself; anything else moves the effect onto the children and takes it off the box |
| durationStepshared | number | 0 | Milliseconds added to each child's duration. Negative is allowed; floored at 0 |
| reverseshared | boolean | false | Runs the set from the last child to the first. Only the order turns round; each child still plays forwards |
| render | ReactElement | (props, state) => ReactElement | — | Renders something other than a <div> |
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | PlassAnimateMode | PlassAnimateMode.enter | Whether the content is uncovered or covered again. exit is the same wipe run backwards, so it closes from the edge it opened towards. enter/exit rather than in/out, because in is a reserved word in Dart |
| fromshared | PlassSide | PlassSide.left | Which edge the wipe starts at. Physical, as it is everywhere in the library: a heading uncovered from the top is uncovered from the top in every writing direction |
| fade | bool | false | Fades in behind the wipe. Off by default, the opposite of every other effect, because a reveal not being a fade is the whole point of it |
| durationshared | Duration | Duration(milliseconds: 520) | How long one run takes, in milliseconds. A number, never a CSS string |
| delayshared | Duration | Duration.zero | How long before it starts, in milliseconds |
| curveshared | Curve? | the house curve | The easing curve, written the way CSS writes it |
| repeatshared | int? | 1 | How many times it runs. null is what never stops: there is no 'infinite' to write, and -1 would be a sentinel a caller has to look up |
| alternateshared | bool | false | Runs every other pass backwards, so a repeat returns instead of jumping |
| pausedshared | bool | false | Holds the animation where it is |
| triggershared | PlassAnimateTrigger | PlassAnimateTrigger.mount | What starts it: mount as soon as it is on the page, visible when it is scrolled into view, hover while the pointer or focus is on it, manual only when play says so |
| playshared | bool | false | Runs it when trigger is manual. Each false → true starts it over |
| onceshared | bool | true | With trigger="visible", whether it runs only the first time. Off, it runs again every time the element comes back into view |
| thresholdshared | double | 0.2 | With trigger="visible", how much of the element has to be on screen before it counts as visible, from 0 to 1 |
| child * | Widget | — | What is uncovered |
Every native <div> attribute passes straight through, and render swaps the element for another one, which is worth using here more than anywhere else in the set, because a reveal is usually wrapped around a heading or a rule that is already the right element.
The clip is applied while painting, so the widget is laid out once at its full size and nothing beside it is laid out again. That is the difference from an Align with a widthFactor, which would resize the box and push its neighbours around.
from is physical (top, right, bottom, left) as PlassSide is everywhere in the library. A heading uncovered from the top is uncovered from the top in every writing direction.
The ten shared settings (duration, delay, easing, repeat, alternate, paused, trigger, play, once, threshold) are the same on every PlAnimate* component. The four trigger values are shown on the PlAnimateFade page. timeline="view" and range are there too, and hand the effect to the reader's scroll position instead of the clock.
Three more move the effect off the box and onto the things inside it: stagger holds each child back by its position, durationStep gives each one a longer or shorter run than the last, and reverse starts from the end of the set. They are on all six single-keyframe effects and are shown on the PlAnimateFade page.
Choosing an entrance
The set has five other ways of arriving, and each of them changes something about the element while it does it. This one changes how much of the element is drawn and nothing else.
- PlAnimateFade changes the ink. Safe on any block of text, and the first one to use, but a faded heading is a heading somebody has to read twice.
- PlAnimateSlide changes the position. What it says is "this has arrived from somewhere", which is a lie about a rule that has always belonged between those two sections.
- PlAnimateGrow and PlAnimateZoom change the size, so the text inside is resampled at every frame.
- PlAnimateReveal changes neither. Use it where the position is the information: a heading over the paragraph it belongs to, a divider between two sections, the plot area of a chart, a column of figures that must not be read from the wrong place.
It is also the cheapest of the five to lay out, because there is nothing to lay out. No wrapper, no overflow box, no second element in the flow. The clip paints less of the element and the page around it never learns that anything happened.
Examples
from
Four edges, and mode="out" closes from whichever one it opened towards.
fade
Off by default, which is the opposite of every other effect that offers it. Turning it on asks for two entrances at once, and the reason to have reached for this one is usually that the first was the problem.
Accessibility
- Under
prefers-reduced-motionthe animation is dropped entirely and the content is simply there, including the clip, so nothing is left half drawn. - Nothing reflows while it runs, and nothing is resampled. That makes it as safe on a block of text as a fade, and safer than anything that scales.
- The clipped part of the element is still in the document and still read out. This is an entrance, not a way to hide something: if it should be gone, unmount it.
- A caller's own
clip-pathon the same element is overwritten while the effect runs. Put one of them on a wrapper.
- When the platform has animations turned off (
MediaQuery.disableAnimations) the effect is dropped entirely and the content is simply there. - Nothing is laid out again while it runs. The clip happens at paint time, so neither the widget nor anything beside it changes size.
- The clipped part of the widget is still in the tree and still in the semantics. This is an entrance, not a way to hide something.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
clip-path: inset() | ClipRect with a clipper | The same rectangle, named the way each platform names it. |
mode="in" | "out" | PlassAnimateMode.enter / .exit | in is a reserved word in Dart. |
render | — | Flutter has no polymorphic element. |
duration, delay in milliseconds | Duration | The framework already has the type. |
easing as a CSS string | curve, a Curve | Dart's own name for the same thing. |
repeat: number | 'infinite' | int?, null never stops | There is no 'infinite' to write, and -1 would be a sentinel a caller has to look up. |
trigger="visible" via IntersectionObserver | watches the nearest Scrollable | There is no observer here; with no scrollable above it there is nothing to watch, so it runs. |
prefers-reduced-motion | MediaQuery.disableAnimations | The platform's own signal. |
stagger, durationStep, reverse | — | The React build writes the effect onto the children themselves, so the caller's own layout is untouched. Flutter has no stylesheet to lay a set out with, so a staggered effect would have to own the row or the column as well, which is what PlAnimateAppear is, and six more of it would be six more of it. |
timeline="view" | — | animation-timeline is a CSS property with no counterpart here. A scroll-linked effect in Flutter is an AnimationController driven from a ScrollPosition, which is an application's own wiring rather than something a widget takes as a prop. |
className, style | — | There is no class list and no style attribute to pass through. |