AnimateMarquee
Content scrolling steadily past, forever. The content is laid down twice and each copy travels exactly its own length, so there is no seam and no frame where the strip is empty.
import { AnimateMarquee } from 'neba';
<AnimateMarquee speed={45} gap="1.5rem">
{customers.map((name) => (
<Chip key={name}>{name}</Chip>
))}
</AnimateMarquee>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| orientationshared | 'horizontal' | 'vertical' | 'horizontal' | Which way the strip runs |
| reverse | boolean | false | Runs it the other way: left to right, or bottom to top |
| speed | number | 60 | How fast the content travels, in pixels per second. A speed rather than a duration, so a short strip and a long one move at the same pace |
| gap | number | string | '2rem' | The gap between items, and between the last item and the first of the next pass |
| copies | number | 2 | How many copies are laid end to end. Raise it only when the content is short enough to leave a hole behind itself |
| pauseOnHover | boolean | true | Stops while the pointer is on it or the focus is inside it, because content moving past a pointer cannot be clicked |
| durationshared | number | — | How long one run takes, in milliseconds |
| delayshared | number | 0 | How long before it starts, in milliseconds |
| easing | string | — | The easing curve, as CSS writes it. Defaults to the house curve |
| repeatshared | number | 'infinite' | 'infinite' | How many times it runs |
| alternateshared | boolean | false | Runs every other pass backwards, so a repeat returns instead of jumping |
| triggershared | 'mount' | 'visible' | 'hover' | 'manual' | 'mount' | What starts it. visible is on scrolling into view, hover is under the pointer (focus counts), manual is whatever play says |
| play | 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 on every return |
| thresholdshared | number | 0.2 | With trigger="visible", how much of the element has to be on screen, from 0 to 1 |
| pausedshared | boolean | false | Holds the animation where it is |
| children | ReactNode | — | The things that scroll past |
Every other <div> attribute passes through to the root. The settings shared by every Animate* are defined in prop conventions.
Examples
speed and reverse
Pixels per second, measured against the strip's own width, so four logos and forty move at the same pace instead of the long one becoming a blur. duration is still accepted and overrides the measurement. reverse runs it the other way.
orientation
vertical runs the strip down the box instead of across it, for a log or an activity feed. The box needs a height for it to have anywhere to run.
pauseOnHover
On by default, and not decoration: content moving past a pointer cannot be clicked reliably, and a link inside a strip that never stops is a link nobody can follow. The strip answers the focus as well as the pointer, so a link reached by tabbing to it stops travelling too.
copies and gap
gap is the space between items, and also between the last item of one pass and the first of the next. copies is how many times the content is laid end to end: two is enough for anything at least as wide as its container, and raising it is the fix for content short enough to leave a hole behind itself.
<AnimateMarquee copies={4} gap="3rem">
<Chip>One short item</Chip>
</AnimateMarquee>Accessibility
- Only the first copy is read out; the rest carry
aria-hidden, or a screen reader would announce the whole strip as many times as it was laid down. - A reduced-motion preference stops the strip and leaves the content in place.
pauseOnHoveris what makes anything interactive inside it usable, and it covers both a pointer resting on the strip and the focus landing inside it. Do not turn it off for a strip with links or buttons in it.