SSGOI LogoSSGOI

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:

{
  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

  1. On Mount: Execute in function when element is added to DOM
  2. On Unmount: Execute out function before element is removed
  3. Animation: Spring physics engine generates progress
    • in: 0 → 1
    • out: 1 → 0
  4. 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

  • key must 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 key is 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 pattern file: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 animations
  • local: 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

Situationscope: 'local'scope: 'global'
Mount with Scope simultaneouslySkip animationRun animation
Unmount with Scope simultaneouslySkip animationRun animation
Toggle individually within ScopeRun animationRun animation