Skip to content

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.

React
tsx
import { PlAnimateHeadline } from 'plass-ui';

<PlAnimateHeadline interval={2200}>
  <span>ships on Friday</span>
  <span>reads like prose</span>
  <span>weighs almost nothing</span>
</PlAnimateHeadline>;
dart
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

PropTypeDefaultDescription
intervalnumber2600How 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
indexnumberWhich line is showing. Pass it to drive the reel yourself, and the component stops running a timer of its own
defaultIndexnumber0Where an uncontrolled reel starts
onIndexChange(index: number) => voidCalled with the line that has just come up
loopbooleantrueStarts again after the last line. Off, the reel stops on the last one and stays there
risenumber | string'100%'How far a line travels as it comes up or leaves. '100%' is one line's own height
durationsharednumber460How long one run takes, in milliseconds. A number, never a CSS string
delaysharednumber0How long before it starts, in milliseconds
easingsharedstringthe house curveThe easing curve, written the way CSS writes it
repeatsharednumber | 'infinite''infinite'How many times it runs. 'infinite' rather than Infinity, because that word is what reaches CSS
pausedsharedbooleanfalseHolds 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
playsharedbooleanRuns it when trigger is manual. Each false → true starts it over
oncesharedbooleantrueWith trigger="visible", whether it runs only the first time. Off, it runs again every time the element comes back into view
thresholdsharednumber0.2With trigger="visible", how much of the element has to be on screen before it counts as visible, from 0 to 1
PropTypeDefaultDescription
children * List<Widget>The lines, in the order they should be read
intervalDurationDuration(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
indexint?Which line is showing. Pass it to drive the reel yourself, and the component stops running a timer of its own
defaultIndexint0Where an uncontrolled reel starts
onIndexChangeValueChanged<int>?Called with the line that has just come up
loopbooltrueStarts again after the last line. Off, the reel stops on the last one and stays there
risedouble?one line's own heightHow far a line travels as it comes up or leaves, in logical pixels. null is one line's own height
durationsharedDurationDuration(milliseconds: 460)How long one run takes, in milliseconds. A number, never a CSS string
delaysharedDurationDuration.zeroHow long before it starts, in milliseconds
curvesharedCurve?the house curveThe easing curve, written the way CSS writes it
repeatsharedint?nullHow 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
pausedsharedboolfalseHolds the animation where it is
triggersharedPlassAnimateTriggerPlassAnimateTrigger.mountWhat 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
playsharedboolfalseRuns it when trigger is manual. Each false → true starts it over
oncesharedbooltrueWith trigger="visible", whether it runs only the first time. Off, it runs again every time the element comes back into view
thresholdshareddouble0.2With 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.

React

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.

React

Accessibility

  • Under prefers-reduced-motion 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. 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 visibility rather 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: false for 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

ReactFlutterWhy
every line in one grid cella StackThe 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 visibilitydrawn at zero opacitySame 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 lengthdouble?, null is one line's own heightA fraction of a line's own height is what FractionalTranslation already means.
renderFlutter has no polymorphic element.
duration, delay in millisecondsDurationThe framework already has the type.
easing as a CSS stringcurve, a CurveDart's own name for the same thing.
repeat: number | 'infinite'int?, null never stopsThere is no 'infinite' to write, and -1 would be a sentinel a caller has to look up.
trigger="visible" via IntersectionObserverwatches the nearest ScrollableThere is no observer here; with no scrollable above it there is nothing to watch, so it runs.
prefers-reduced-motionMediaQuery.disableAnimationsThe platform's own signal.
className, styleThere is no class list and no style attribute to pass through.

Released under the MIT License