Element Animations
Apply animations to individual DOM elements
SSGOI
from0
to1
spring
Transition Examples
How to use various transition effects:
import { transition } from '@ssgoi/react';
import { fade, scale, blur, slide, fly, rotate, bounce } from '@ssgoi/react/transitions';
/**
* [React] Auto Key Plugin
*
* With the plugin, you can omit the key property.
* Unique keys are auto-generated based on file:line:column.
*
* Next.js Setup:
* ```ts
* // next.config.ts
* import SsgoiAutoKey from "@ssgoi/react/unplugin/webpack";
*
* const nextConfig = {
* webpack: (config) => {
* config.plugins.push(SsgoiAutoKey());
* return config;
* },
* };
* ```
*
* ⚠️ Note: In .map() lists, JSX key is enough - the plugin handles it automatically.
* Without the plugin, key is required.
*
* Example:
* ```jsx
* // In .map() lists, JSX key is sufficient
* // Plugin auto-generates file:line:col:${jsxKey}
* {items.map((item) => (
* <div
* key={item.id} // JSX key is enough
* ref={transition(fade())}
* >
* {item.name}
* </div>
* ))}
* ```
*/
// Fade transition
<div ref={transition(fade({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Fade effect
</div>
// Scale transition
<div ref={transition(scale({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Scale effect
</div>
// Blur transition
<div ref={transition(blur({ amount: 10, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Blur effect
</div>
// Slide transition (with direction)
<div ref={transition(slide({ direction: 'left', physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Slide effect
</div>
// Fly transition (custom position)
<div ref={transition(fly({ x: 200, y: -50, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Fly effect
</div>
// Rotate transition
<div ref={transition(rotate({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Rotate effect
</div>
// Bounce transition
<div ref={transition(bounce({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
Bounce effect
</div>
// Explicit key usage (required when not using plugin)
<div ref={transition({
key: "scale-element",
...scale({ physics: { spring: { stiffness: 300, damping: 30 } } })
})}>
Scale effect
</div>
Basic Structure
TransitionConfig Interface
interface TransitionConfig {
physics?: {
// Spring: Spring physics (ease-out effect, default)
spring?: {
stiffness: number; // Spring stiffness (default: 300)
damping: number; // Damping coefficient (default: 30)
doubleSpring?: boolean | number; // Double spring effect (default: false)
};
// Inertia: Inertia physics (ease-in effect)
inertia?: {
acceleration: number; // Acceleration (default: 500)
resistance: number; // Resistance coefficient (default: 10)
};
// Custom Integrator: Custom physics engine
integrator?: () => Integrator;
};
// Choose one of two animation modes (cannot use both)
tick?: (progress: number) => void; // RAF-based animation
css?: (progress: number) => StyleObject; // Web Animation API based (recommended)
prepare?: (element: HTMLElement) => void; // Initial setup before animation starts
onStart?: () => void;
onEnd?: () => void;
}
Physics Engine Options
SSGOI supports various physics engines to create natural animations:
Spring Physics
Provides ease-out effects, which is the default physics engine. Animations start fast and naturally decelerate as they reach their target.
{
in: (element) => ({
physics: {
spring: {
stiffness: 300, // Spring stiffness (higher = faster)
damping: 30, // Damping coefficient (higher = less bounce)
doubleSpring: false // Double spring effect
}
},
css: (progress) => ({
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}),
})
}
Inertia Physics
Provides ease-in effects. Animations start slowly and gradually accelerate.
{
in: (element) => ({
physics: {
inertia: {
acceleration: 500, // Acceleration (higher = faster acceleration)
resistance: 10 // Resistance coefficient (higher = more deceleration)
}
},
css: (progress) => ({
opacity: progress,
transform: `scale(${0.8 + progress * 0.2})`,
}),
})
}
Custom Integrator
You can implement your own physics engine.
{
in: (element) => ({
physics: {
integrator: () => ({
next: (dt, state) => {
// Custom physics calculation logic
return newState;
}
})
},
css: (progress) => ({
opacity: progress,
}),
})
}
Animation Modes: css vs tick
SSGOI supports two animation modes:
css Mode (Recommended)
{
in: (element) => ({
physics: { spring: { stiffness: 300, damping: 30 } },
css: (progress) => ({
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}),
})
}
- Uses Web Animation API to minimize main thread load
- Pre-computes spring physics and converts to keyframes
- Smooth animations with GPU acceleration
- Recommended for most cases due to better performance
tick Mode
{
in: (element) => ({
physics: { spring: { stiffness: 300, damping: 30 } },
tick: (progress) => {
element.style.opacity = String(progress);
element.style.transform = `translateY(${(1 - progress) * 20}px)`;
},
})
}
- requestAnimationFrame based, executes callback every frame
- Suitable for complex animations requiring direct DOM manipulation
- Useful when values need to change dynamically during animation
css and tick cannot be used simultaneously. Choose one.
Transition Definition
interface Transition {
in?: (element: HTMLElement) => TransitionConfig;
out?: (element: HTMLElement) => TransitionConfig;
}
How it Works
- On Mount: Execute
infunction when element is added to DOM - On Unmount: Execute
outfunction before element is removed - Animation: Spring physics engine generates progress
- in: 0 → 1
- out: 1 → 0
- tick Callback: Called every frame to update styles
Transition Presets
import { fade, scale /** etc */ } from "@ssgoi/react/transitions";
Framework-specific Usage
import { transition } from "@ssgoi/react";
<div
ref={transition({
key: "unique-key",
in: (element) => ({
tick: (progress) => {
element.style.opacity = progress;
element.style.transform = `translateY(${20 * (1 - progress)}px)`;
},
}),
out: (element) => ({
tick: (progress) => {
element.style.opacity = 1 - progress;
},
}),
})}
>
Content
</div>
Progress Behavior
key
keymust be unique within the page to track animation state even when DOM is created/deleted- Without a
key, SSGOI cannot distinguish between different elements with the same transition - React Auto Key Plugin: In React, if
keyis not provided, you can use the Auto Key Plugin to generate keys automatically based on file location (file:line:column) - Important: In
.map()lists, JSX key is enough - the plugin automatically generates unique keys using the patternfile:line:col:${jsxKey} - See the React examples above for setup instructions
in Animation
- progress: 0 → 1
- Executes when element appears
- Opacity from 0 to 1, from small size to original size
out Animation
- progress: 1 → 0
- Executes when element disappears
- Opacity from 1 to 0, from original size to small size
// Example: difference between in and out
{
in: (element) => ({
tick: (progress) => {
// progress: 0 → 1
element.style.opacity = progress; // 0 → 1
}
}),
out: (element) => ({
tick: (progress) => {
// progress: 1 → 0
element.style.opacity = progress; // 1 → 0
}
})
}
prepare Callback
The stage where DOM elements are prepared before animation starts:
{
in: {
prepare: (element) => {
// Set initial state before tick is executed
element.style.willChange = 'opacity, transform';
},
tick: (progress) => ({
opacity: progress,
transform: `translateY(${20 * (1 - progress)}px)`
})
}
}
TransitionScope
TransitionScope is a container that controls the animation behavior of child elements. It can skip unnecessary animations when parent and children mount/unmount simultaneously.
Use Cases
- Page Transitions: Prevent individual elements from animating separately during page transitions
- Modal/Dialog: Prevent duplicate animations for content appearing with the container
- Lists: Skip individual item animations when entire list mounts/unmounts
scope Option
interface TransitionOptions {
// ...
scope?: 'global' | 'local';
}
global(default): Always run animationslocal: Skip animations when mounting/unmounting simultaneously with TransitionScope
Usage Example
import { TransitionScope, transition } from '@ssgoi/react';
function MyComponent() {
const [show, setShow] = useState(true);
return (
<>
<button onClick={() => setShow(!show)}>Toggle</button>
{show && (
<TransitionScope>
<div style={{ padding: '2rem', border: '1px dashed #ccc' }}>
{/* local: Skip animation when mounting/unmounting with scope */}
<div
ref={transition({
key: 'local-child',
scope: 'local',
in: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
out: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
})}
>
Local Scope (appears/disappears with Scope)
</div>
{/* global: Always run animation */}
<div
ref={transition({
key: 'global-child',
// scope: 'global' is default
in: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
out: () => ({
css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
}),
})}
>
Global Scope (always animates)
</div>
</div>
</TransitionScope>
)}
</>
);
}
Behavior
| Situation | scope: 'local' | scope: 'global' |
|---|---|---|
| Mount with Scope simultaneously | Skip animation | Run animation |
| Unmount with Scope simultaneously | Skip animation | Run animation |
| Toggle individually within Scope | Run animation | Run animation |