PlAnimateRotate
Content turning about a point. Two angles rather than one, which is what lets a single component cover both a quarter turn into place and a spin that never lands.
import { PlAnimateRotate } from 'plass-ui';
<PlAnimateRotate from={0} to={360} duration={2400} easing="linear" repeat="infinite" fade={false}>
<PlIcon icon={<RefreshGlyph />} label="Syncing" />
</PlAnimateRotate>;import 'package:plass_ui/plass_ui.dart';
const PlAnimateRotate(
from: 0,
to: 360,
duration: Duration(milliseconds: 2400),
curve: Curves.linear,
repeat: null,
fade: false,
child: PlIcon(icon: RefreshGlyph()),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | 'in' | 'out' | 'in' | Whether the content turns into place or out of it |
| from | number | -180 | The angle it starts at, in degrees. Negative is anticlockwise |
| to | number | 0 | The angle it ends at, in degrees. Together with from this is what makes one component cover both a turn into place and an endless spin: from={0} to={360} repeat="infinite" |
| origin | string | 'center' | Which point it turns about — any CSS transform-origin |
| fade | boolean | true | Fades in as it turns. Turn it off for a continuous spin, where a repeating fade would read as flickering |
| durationshared | number | 440 | 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 turns into place or out of it. enter/exit rather than in/out, because in is a reserved word in Dart |
| from | double | -180 | The angle it starts at, in degrees rather than radians: the framework counts in radians and the design language counts in degrees |
| to | double | 0 | The angle it ends at, in degrees. Together with from this is what makes one component cover both a turn into place and an endless spin: from={0} to={360} repeat="infinite" |
| origin | Alignment | Alignment.center | Which point it turns about — any CSS transform-origin |
| fade | bool | true | Fades in as it turns. Turn it off for a continuous spin, where a repeating fade would read as flickering |
| durationshared | Duration | Duration(milliseconds: 440) | 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 turns |
Every native <div> attribute passes straight through, and render swaps the element for another one.
from and to are degrees, not radians. The framework counts in radians and the design language counts in degrees — every gradient in the package is at 135° — so the conversion happens inside the widget, once, rather than at every call site. origin is an Alignment.
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 and to
from alone is an arrival: something swings into place and stops. from and to together with repeat="infinite" and easing="linear" is a spin that never lands, which is what a badge, a loading mark or a decorative glyph wants. Turn fade off for the second one — a fade that repeats reads as flickering.
origin
Any CSS transform-origin. Turning about a corner is a hinge rather than a wheel, and it is what a flag, a tag or a card being dealt onto a pile wants.
Accessibility
- Under
prefers-reduced-motionthe animation is dropped entirely and the content is simply there. That is right for an arrival and worth thinking about for a spin: if the turning is what says something is happening, use PlProgressCircular instead, which slows rather than stopping. - Not for text. A rotated word is resampled along its whole length. Rotation is the one movement the design language allows on a glyph without argument — a chevron is turned rather than redrawn all over the library — and that is the shape of thing it is for.
- Something that turns forever in the corner of a page somebody is reading is the one kind of motion the rest of this library refuses. Give it a reason.
- When the platform has animations turned off (
MediaQuery.disableAnimations) the effect is dropped entirely and the content is simply there. That is right for an arrival and worth thinking about for a spin: if the turning is what says something is happening, use PlProgressCircular instead, which slows rather than stopping. - Not for text. A rotated word is resampled along its whole length.
- Something that turns forever in the corner of a screen somebody is reading is the one kind of motion the rest of this package refuses. Give it a reason.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
from, to in degrees | double degrees, converted inside | The framework counts in radians; the design language counts in degrees, and the conversion belongs in one place. |
origin as a CSS transform-origin string | Alignment | The framework already has the type. |
easing="linear" | curve: Curves.linear | Dart's own name for the same curve. |
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. |