PlAnimateSlide
Content travelling in from one edge. The default distance is the element's own size, so it starts exactly out of frame and is never half drawn somewhere it does not belong.
import { PlAnimateSlide } from 'plass-ui';
<div className="overflow-hidden">
<PlAnimateSlide from="right">
<PlCard title="New message">Ada replied to your review.</PlCard>
</PlAnimateSlide>
</div>;import 'package:plass_ui/plass_ui.dart';
const ClipRect(
child: PlAnimateSlide(
from: PlassSide.right,
child: PlCard(title: Text('New message'), child: Text('Ada replied to your review.')),
),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | 'in' | 'out' | 'in' | Whether the content slides in or slides away. out leaves by the same edge it would have come from |
| fromshared | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Which edge it travels from. Physical, as it is everywhere in the library: a panel coming down from the top comes from the top in every writing direction |
| distance | number | string | '100%' | How far it travels — a CSS length, or a number in pixels. '100%' is the element's own width or height, which is what makes it appear from behind its own edge |
| fade | boolean | true | Fades in as it slides |
| durationshared | number | 360 | 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 slides in or slides away. exit leaves by the same edge it would have come from. enter/exit rather than in/out, because in is a reserved word in Dart |
| fromshared | PlassSide | PlassSide.bottom | Which edge it travels from. Physical, as it is everywhere in the library: a panel coming down from the top comes from the top in every writing direction |
| distance | double? | its own size | How far it travels, in logical pixels. null is the widget's own width or height, which is what makes it appear from behind its own edge |
| fade | bool | true | Fades in as it slides |
| durationshared | Duration | Duration(milliseconds: 360) | 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 travels |
Every native <div> attribute passes straight through, and render swaps the element for another one.
distance is a double? in logical pixels, and null — the default — is the widget's own width or height. There is no CSS length to write: a fraction of a widget's own size is what FractionalTranslation already means, and that is what the widget uses when no distance is given.
from is physical — top, right, bottom, left — as PlassSide is everywhere in the library. A panel coming down from the top comes 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.
Examples
from
Four edges, and mode="out" leaves by whichever one it would have arrived from.
import { PlAnimateSlide, PlChip } from 'plass-ui';
const sides = ['top', 'right', 'bottom', 'left'] as const;
export default function AnimateSlideSides() {
return (
<div className="flex flex-wrap items-center justify-center gap-4">
{sides.map((side) => (
<PlAnimateSlide
key={side}
from={side}
distance={24}
duration={1200}
repeat="infinite"
alternate
>
<PlChip>{side}</PlChip>
</PlAnimateSlide>
))}
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class AnimateSlideSides extends StatelessWidget {
const AnimateSlideSides({super.key});
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 16,
runSpacing: 16,
alignment: WrapAlignment.center,
children: <Widget>[
for (final PlassSide side in PlassSide.values)
PlAnimateSlide(
from: side,
distance: 24,
duration: const Duration(milliseconds: 1200),
repeat: null,
alternate: true,
child: PlChip(child: Text(side.name)),
),
],
);
}
}distance
A number is pixels, a string is any CSS length. '100%' is the element's own width or height — put it in a box with overflow: hidden and the effect is a panel appearing from behind that box's edge. Short distances are a different gesture: a nudge that says something changed, rather than an entrance.
import { PlAnimateSlide, PlBox } from 'plass-ui';
export default function AnimateSlideDistance() {
return (
<div className="flex w-full max-w-sm flex-col gap-3 overflow-hidden">
<PlAnimateSlide from="left" distance="100%" duration={1400} repeat="infinite" alternate>
<PlBox size="sm">100% — its own width, so it starts out of frame</PlBox>
</PlAnimateSlide>
<PlAnimateSlide from="left" distance={16} duration={1400} repeat="infinite" alternate>
<PlBox size="sm">16px — a nudge rather than an entrance</PlBox>
</PlAnimateSlide>
</div>
);
}import 'package:flutter/widgets.dart';
import 'package:plass_ui/plass_ui.dart';
class AnimateSlideDistance extends StatelessWidget {
const AnimateSlideDistance({super.key});
@override
Widget build(BuildContext context) {
return const SizedBox(
width: 320,
child: ClipRect(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 12,
children: <Widget>[
PlAnimateSlide(
from: PlassSide.left,
duration: Duration(milliseconds: 1400),
repeat: null,
alternate: true,
child: PlBox(
size: PlassSize.sm,
child: Text('no distance — its own width, so it starts out of frame'),
),
),
PlAnimateSlide(
from: PlassSide.left,
distance: 16,
duration: Duration(milliseconds: 1400),
repeat: null,
alternate: true,
child: PlBox(
size: PlassSize.sm,
child: Text('16px — a nudge rather than an entrance'),
),
),
],
),
),
);
}
}Accessibility
- Under
prefers-reduced-motionthe animation is dropped entirely and the content is simply there. - Nothing on the page reflows while it runs. This is a
translaterather than a change of layout, so what is around the element does not move. - A slide that starts out of frame will overflow whatever is holding it unless that box clips. Clip it, or the page grows a scrollbar for the length of the animation.
- For a much shorter travel across a list of things, one after another, use PlAnimateAppear — the stagger is what makes that effect, and a slide per child would leave you writing the delays yourself.
- When the platform has animations turned off (
MediaQuery.disableAnimations) the effect is dropped entirely and the content is simply there. - Nothing around it is laid out again while it runs. This moves the widget rather than changing the layout.
- A slide that starts out of frame will overflow whatever is holding it unless that box clips. Wrap it in a
ClipRect. - For a much shorter travel across a list of things, one after another, use PlAnimateAppear.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
distance as a CSS length or a number | double?, null is its own size | A fraction of a widget's own size is what FractionalTranslation already means, so there is nothing to spell as a string. |
mode="in" | "out" | PlassAnimateMode.enter / .exit | in is a reserved word in Dart. |
overflow: hidden on a wrapper | ClipRect | The framework's own name for the same clip. |
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. |