Animate
Wrap anything (logo, screenshot, card, image) to give it a themed enter and exit. The swiss-army knife of reelcn.
Animate, defaults
Installation
npx shadcn add https://www.reelcn.dev/r/animate.jsonFiles land in src/reelcn/ with the items it builds on.
Usage
<Animate effect="up" delay={10}>
<Img src={staticFile("logo.png")} />
</Animate>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.effectAnimateEffect Default
"up".exitEffectAnimateEffect Default
"fade".distancenumberTravel distance for directional effects, in design units. Default
60.childrenReactNode
styleCSSProperties
classNamestring
Built from
Use
- Entrances and exits for logos, screenshots, cards and anything without its own animation
- Quick prototypes before reaching for a dedicated component
Avoid
- Headlines. Use
text-reveal
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/animate.tsx
/**
* @title Animate
* @category motion
* @description Wrap anything (logo, screenshot, card, image) to give it a themed enter and exit. The swiss-army knife of reelcn.
* @duration 18
* @use Entrances and exits for logos, screenshots, cards and anything without its own animation
* @use Quick prototypes before reaching for a dedicated component
* @avoid Headlines. Use `text-reveal`
* @tags wrapper, entrance, exit, fade, slide
* @example
* <Animate effect="up" delay={10}>
* <Img src={staticFile("logo.png")} />
* </Animate>
*/
import type React from "react";
import { type MotionProps, useMotion, useViewport } from "./core";
export type AnimateEffect = "fade" | "up" | "down" | "left" | "right" | "scale" | "pop" | "blur" | "zoom" | "none";
export type AnimateProps = MotionProps & {
effect?: AnimateEffect;
exitEffect?: AnimateEffect;
/** Travel distance for directional effects, in design units. */
distance?: number;
children?: React.ReactNode;
style?: React.CSSProperties;
className?: string;
};
/** Style at progress `p` (0 = hidden, 1 = shown). */
export function effectStyle(effect: AnimateEffect, p: number, travel: number): React.CSSProperties {
const hidden = 1 - p;
const opacity = Math.min(Math.max(p, 0), 1);
switch (effect) {
case "none":
return {};
case "fade":
return { opacity };
case "up":
return { opacity, translate: `0 ${hidden * travel}px` };
case "down":
return { opacity, translate: `0 ${-hidden * travel}px` };
case "left":
return { opacity, translate: `${hidden * travel}px 0` };
case "right":
return { opacity, translate: `${-hidden * travel}px 0` };
case "scale":
return { opacity, scale: String(0.85 + 0.15 * p) };
case "pop":
return { opacity, scale: String(0.4 + 0.6 * p) };
case "blur":
return { opacity, filter: `blur(${Math.max(hidden, 0) * travel * 0.25}px)` };
case "zoom":
return { opacity, scale: String(1.25 - 0.25 * p) };
}
}
export function Animate({
effect = "up",
exitEffect = "fade",
distance = 60,
children,
style,
className,
...motion
}: AnimateProps) {
const { u } = useViewport();
const m = useMotion(motion);
const { opacity: enterOpacity = 1, ...enter } = effectStyle(effect, m.enter, u(distance));
const { opacity: exitOpacity = 1, ...exit } = effectStyle(exitEffect, 1 - m.exit, u(distance));
return (
<div
className={className}
style={{ ...style, opacity: Number(style?.opacity ?? 1) * Number(enterOpacity) * Number(exitOpacity) }}
>
<div style={exit}>
<div style={enter}>{children}</div>
</div>
</div>
);
}