PlAnimateHeadline
One line replacing the one above it, on a timer. Every line sits in the same grid cell, so the box is as tall as the longest of them from the first frame and never resizes as the reel turns.
import { PlAnimateHeadline } from 'plass-ui';
<PlAnimateHeadline interval={2200}>
<span>ships on Friday</span>
<span>reads like prose</span>
<span>weighs almost nothing</span>
</PlAnimateHeadline>;import 'package:plass_ui/plass_ui.dart';
const PlAnimateHeadline(
interval: Duration(milliseconds: 2200),
children: <Widget>[
Text('ships on Friday'),
Text('reads like prose'),
Text('weighs almost nothing'),
],
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| interval | number | 2600 | How long each line is held before the next one comes up, in milliseconds. Counted from the moment a line arrives, so it is reading time rather than a cycle length |
| index | number | — | Which line is showing. Pass it to drive the reel yourself, and the component stops running a timer of its own |
| defaultIndex | number | 0 | Where an uncontrolled reel starts |
| onIndexChange | (index: number) => void | — | Called with the line that has just come up |
| loop | boolean | true | Starts again after the last line. Off, the reel stops on the last one and stays there |
| rise | number | string | '100%' | How far a line travels as it comes up or leaves. '100%' is one line's own height |
| durationshared | number | 460 | 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' | 'infinite' | How many times it runs. 'infinite' rather than Infinity, because that word is what reaches CSS |
| 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 |
| Prop | Type | Default | Description |
|---|---|---|---|
| children * | List<Widget> | — | The lines, in the order they should be read |
| interval | Duration | Duration(milliseconds: 2600) | How long each line is held before the next one comes up, in milliseconds. Counted from the moment a line arrives, so it is reading time rather than a cycle length |
| index | int? | — | Which line is showing. Pass it to drive the reel yourself, and the component stops running a timer of its own |
| defaultIndex | int | 0 | Where an uncontrolled reel starts |
| onIndexChange | ValueChanged<int>? | — | Called with the line that has just come up |
| loop | bool | true | Starts again after the last line. Off, the reel stops on the last one and stays there |
| rise | double? | one line's own height | How far a line travels as it comes up or leaves, in logical pixels. null is one line's own height |
| durationshared | Duration | Duration(milliseconds: 460) | 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? | null | 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 |
| 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 |
Every native <div> attribute passes straight through. There is no render and no alternate: the component owns its grid, and a reel has no other direction to run in.
rise is a double? in logical pixels, and null — the default — is one line's own height, the same trade PlAnimateSlide's distance makes. There is no alternate: a reel has no other direction to run in.
interval is counted from the moment a line arrives rather than from the start of the cycle, so raising duration does not quietly eat the reading time.
The rest of the shared settings — duration, delay, easing, repeat, paused, trigger, play, once, threshold — mean what they mean everywhere else. delay is what happens before the reel starts turning at all, so it is added once rather than to every line.
Examples
Controlled
Pass index and the reel stops running a timer of its own — a controlled headline is somebody else's clock, and a second one underneath it would fight for the same state. Drive it from a step in a form, a tab, or a timer you own.
rise
How far a line travels as it comes up or leaves. '100%' is one line's own height, which is what makes it read as a reel; a few pixels is closer to a crossfade with a hint of direction.
Accessibility
- Under
prefers-reduced-motionthe lines still change, but nothing slides: the outgoing line is dropped rather than animated away. The reel is the content, so switching it off entirely would leave only the first line. - Not for content a reader has to see. There is no guarantee anyone is looking during the two seconds a line is up, and a screen reader is given whichever line happens to be showing rather than the set. Use it for phrases where any one of them would have done.
- Every line is in the document from the first frame; the ones not showing keep their space with
visibilityrather than being taken out of the layout. That is what keeps the box from resizing, and it also means nothing is announced twice. - Consider
loop={false}for anything with a natural end. A reel that never stops is motion in the corner of a page somebody is reading.
- When the platform has animations turned off (
MediaQuery.disableAnimations) the lines still change, but nothing slides: the outgoing line is dropped rather than animated away. The reel is the content, so switching it off entirely would leave only the first line. - Not for content a reader has to see. There is no guarantee anyone is looking during the two seconds a line is up, and a screen reader is given whichever line happens to be showing rather than the set.
- Every line is in the tree from the first frame; the ones not showing are drawn at zero opacity rather than taken out of the layout. That is what keeps the box from resizing.
- Consider
loop: falsefor anything with a natural end. A reel that never stops is motion in the corner of a screen somebody is reading.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
| every line in one grid cell | a Stack | The framework's own way of putting things in the same place; a stack is as tall as its tallest child, which is the property the effect needs. |
the lines that are not showing keep their space with visibility | drawn at zero opacity | Same outcome — they overlap in the stack either way, so nothing has to be taken out of the layout to begin with. |
rise as a CSS length | double?, null is one line's own height | A fraction of a line's own height is what FractionalTranslation already means. |
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. |
className, style | — | There is no class list and no style attribute to pass through. |