PlAnimateGrow
Content unfolding from a point. It starts close to its final size and can be anchored to any edge, so it reads as something opening out of the thing beside it.
import { PlAnimateGrow } from 'plass-ui';
<PlAnimateGrow origin="top">
<PlBox>Sort, group and column visibility.</PlBox>
</PlAnimateGrow>;import 'package:plass_ui/plass_ui.dart';
const PlAnimateGrow(
origin: Alignment.topCenter,
child: PlBox(child: Text('Sort, group and column visibility.')),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | 'in' | 'out' | 'in' | Whether the content unfolds or folds away. out is the same keyframe run backwards |
| from | number | 0.8 | The scale it starts from, as a multiple of its final size. Above 1 it settles down onto the page instead of up out of it |
| origin | string | 'center' | Which point stays put while the rest moves — any CSS transform-origin. top unfolds downwards, bottom left out of a corner |
| fade | boolean | true | Fades in as it grows. Turn it off for something already on the page that is only changing size |
| durationshared | number | 320 | 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 unfolds or folds away. enter/exit rather than in/out, because in is a reserved word in Dart |
| from | double | 0.8 | The scale it starts from, as a multiple of its final size. Above 1 it settles down onto the page instead of up out of it |
| origin | Alignment | Alignment.center | Which point stays put while the rest moves. An Alignment rather than a CSS string: topCenter unfolds downwards, bottomLeft out of a corner |
| fade | bool | true | Fades in as it grows. Turn it off for something already on the page that is only changing size |
| durationshared | Duration | Duration(milliseconds: 320) | 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 unfolds |
Every native <div> attribute passes straight through, and render swaps the element for another one.
origin is an Alignment rather than a CSS transform-origin string, because the framework already has the type. duration and delay are Durations, curve is a Curve, and repeat is an int? where null never stops.
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.
Examples
origin
The anchor is the whole difference between this and PlAnimateZoom. A panel that unfolds from top is a panel coming out of the control above it; one that unfolds from bottom right is coming out of the corner it is pinned to. Anything anchored to the middle is a zoom, and there is only one component for that idea.
from
Above 1 it arrives oversized and settles back. Short travel is what keeps it safe on glass: a sheet growing from 0.8 stays recognisably the same sheet the whole way, and the blur behind it is never asked to resolve a surface a fifth of the size it is about to be.
Opening a panel
The common use, and the one the defaults were chosen for: origin="top", a short distance, a quick duration. The panel unfolds from the control that opened it rather than appearing beside it.
Accessibility
- Under
prefers-reduced-motionthe animation is dropped entirely and the content is simply there. - The wrapper adds no role and no label. It is a
<div>around content that already says what it is. - Scaling resamples whatever is inside, so keep the travel short over text — that is what
fromdefaults to0.8for. Long travel belongs on a shape, an icon or a picture. - This is a wrapper, not a disclosure. Mounting and unmounting the content is the caller's job, and so is whatever
aria-expandedbelongs on the control that did it.
- When the platform has animations turned off (
MediaQuery.disableAnimations) the effect is dropped entirely and the content is simply there. - The widget adds no semantics of its own. It is a
Transformaround content that already says what it is. - Scaling resamples whatever is inside, so keep the travel short over text — that is what
fromdefaults to0.8for. Long travel belongs on a shape, an icon or a picture. - This is a wrapper, not a disclosure. Adding and removing the content is the caller's job, and so is whatever a screen reader should be told about it.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
origin as a CSS transform-origin string | Alignment | The framework already has the type, and Alignment.topCenter reads better than 'top'. |
fade draws an always-present opacity layer | no Opacity widget at all when fade is off | One fewer layer to composite, and nothing in the tree claiming to be doing something it is not. |
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. |