Rotate Animation

Create dynamic and lively effects by rotating elements

Rotate Animation

The Rotate animation creates effects by rotating elements in 2D or 3D space. It's a fun and eye-catching animation that can draw user attention.

Basic Usage

import { transition } from '@ssgoi/react';
import { rotate } from '@ssgoi/react/transitions';

function Component() {
  const [isVisible, setIsVisible] = useState(true);
  
  return (
    <div>
      {isVisible && (
        <div ref={transition({ key: 'rotate-element', ...rotate() })}>
          Element with rotate animation
        </div>
      )}
    </div>
  );
}

Options

interface RotateOptions {
  degrees?: number;        // Rotation angle (default: 360)
  clockwise?: boolean;     // Clockwise direction (default: true)
  scale?: boolean;         // Add scale effect (default: false)
  fade?: boolean;          // Add fade effect (default: false)
  origin?: string;         // Rotation origin (default: 'center')
  axis?: '2d' | 'x' | 'y' | 'z';  // Rotation axis (default: '2d')
  perspective?: number;    // 3D perspective (default: 800)
  spring?: {
    stiffness?: number;    // Spring stiffness (default: 500)
    damping?: number;      // Damping coefficient (default: 25)
  };
}

Option Details

  • degrees: Rotation angle (360 = one full rotation)
  • clockwise: true for clockwise, false for counter-clockwise
  • scale: Add scaling effect with rotation
  • fade: Add fade effect with rotation
  • origin: Rotation origin point (CSS transform-origin value)
  • axis: Rotation axis
    • '2d': Planar rotation (default)
    • 'x': X-axis rotation (flip up/down)
    • 'y': Y-axis rotation (flip left/right)
    • 'z': Z-axis rotation (same as planar)
  • perspective: Perspective distance for 3D rotation
  • spring: Spring physics settings

Usage Examples

Basic Rotation Variations

// Half rotation
const halfRotate = rotate({ 
  degrees: 180 
});

// Counter-clockwise rotation
const counterClockwise = rotate({ 
  clockwise: false 
});

// Double rotation
const doubleRotate = rotate({ 
  degrees: 720 
});

// Small rotation
const smallRotate = rotate({ 
  degrees: 45 
});

3D Rotation

// X-axis rotation (card flip effect)
const flipX = rotate({ 
  axis: 'x',
  degrees: 180,
  perspective: 1000
});

// Y-axis rotation (door opening effect)
const flipY = rotate({ 
  axis: 'y',
  degrees: 90,
  perspective: 800
});

// Z-axis rotation (planar rotation)
const rotateZ = rotate({ 
  axis: 'z',
  degrees: 360
});

Changing Rotation Origin

// Rotate from top-left
const topLeftRotate = rotate({ 
  origin: 'top left',
  degrees: 90
});

// Rotate from bottom-right
const bottomRightRotate = rotate({ 
  origin: 'bottom right',
  degrees: -90
});

// Custom origin
const customOrigin = rotate({ 
  origin: '25% 75%',
  degrees: 180
});

Combined Effects

// Rotate + Scale
const rotateScale = rotate({ 
  degrees: 360,
  scale: true
});

// Rotate + Fade
const rotateFade = rotate({ 
  degrees: 720,
  fade: true
});

// Rotate + Scale + Fade
const rotateAll = rotate({ 
  degrees: 360,
  scale: true,
  fade: true,
  spring: { stiffness: 300, damping: 20 }
});

Practical Use Cases

Loading Spinner

function LoadingSpinner({ isLoading }) {
  return (
    <>
      {isLoading && (
        <div 
          ref={transition({ 
            key: 'spinner', 
            ...rotate({ 
              degrees: 360,
              spring: { stiffness: 100, damping: 10 }
            }) 
          })}
          className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full"
        />
      )}
    </>
  );
}

Card Flip

function FlipCard({ front, back }) {
  const [isFlipped, setIsFlipped] = useState(false);
  
  return (
    <div 
      className="relative w-64 h-96 cursor-pointer"
      onClick={() => setIsFlipped(!isFlipped)}
    >
      {/* Front side */}
      {!isFlipped && (
        <div 
          ref={transition({ 
            key: 'card-front', 
            ...rotate({ 
              axis: 'y',
              degrees: 180,
              perspective: 1000
            }) 
          })}
          className="absolute inset-0 bg-white rounded-lg shadow-lg p-6"
        >
          {front}
        </div>
      )}
      
      {/* Back side */}
      {isFlipped && (
        <div 
          ref={transition({ 
            key: 'card-back', 
            ...rotate({ 
              axis: 'y',
              degrees: 180,
              perspective: 1000,
              clockwise: false
            }) 
          })}
          className="absolute inset-0 bg-gray-800 text-white rounded-lg shadow-lg p-6"
        >
          {back}
        </div>
      )}
    </div>
  );
}

Refresh Button

function RefreshButton({ onRefresh }) {
  const [isRefreshing, setIsRefreshing] = useState(false);
  
  const handleRefresh = async () => {
    setIsRefreshing(true);
    await onRefresh();
    setTimeout(() => setIsRefreshing(false), 1000);
  };
  
  return (
    <button
      onClick={handleRefresh}
      disabled={isRefreshing}
      className="p-2 rounded-full hover:bg-gray-100"
    >
      <svg 
        ref={isRefreshing ? transition({ 
          key: 'refresh-icon', 
          ...rotate({ 
            degrees: 360,
            spring: { stiffness: 200, damping: 20 }
          }) 
        }) : undefined}
        className="w-6 h-6"
        viewBox="0 0 24 24"
      >
        <path d="M4 12a8 8 0 0 1 8-8V2.5L16 6l-4 3.5V8a6 6 0 1 0 6 6h1.5a7.5 7.5 0 1 1-7.5-7.5z"/>
      </svg>
    </button>
  );
}

Icon Transition

function IconTransition({ isActive }) {
  return (
    <div className="relative w-8 h-8">
      {isActive ? (
        <CheckIcon 
          ref={transition({ 
            key: 'check-icon', 
            ...rotate({ 
              degrees: 360,
              scale: true,
              spring: { stiffness: 600, damping: 30 }
            }) 
          })}
          className="absolute inset-0"
        />
      ) : (
        <CloseIcon 
          ref={transition({ 
            key: 'close-icon', 
            ...rotate({ 
              degrees: -360,
              scale: true,
              spring: { stiffness: 600, damping: 30 }
            }) 
          })}
          className="absolute inset-0"
        />
      )}
    </div>
  );
}

Advanced Usage

Multi-stage Rotation

function MultiStageRotate() {
  const [stage, setStage] = useState(0);
  
  const rotations = [
    { degrees: 90, origin: 'top left' },
    { degrees: 180, origin: 'center' },
    { degrees: 270, origin: 'bottom right' },
    { degrees: 360, origin: 'center' }
  ];
  
  return (
    <div>
      <div 
        ref={transition({ 
          key: `rotate-stage-${stage}`, 
          ...rotate(rotations[stage]) 
        })}
        className="w-32 h-32 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg"
      />
      
      <button onClick={() => setStage((s) => (s + 1) % 4)}>
        Next Stage
      </button>
    </div>
  );
}

Mouse Tracking Rotation

function MouseTrackingRotate() {
  const [rotation, setRotation] = useState(0);
  const elementRef = useRef(null);
  
  const handleMouseMove = (e) => {
    if (!elementRef.current) return;
    
    const rect = elementRef.current.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;
    
    const angle = Math.atan2(
      e.clientY - centerY,
      e.clientX - centerX
    ) * (180 / Math.PI);
    
    setRotation(angle);
  };
  
  return (
    <div 
      className="relative w-full h-64"
      onMouseMove={handleMouseMove}
    >
      <div 
        ref={(el) => {
          elementRef.current = el;
          if (el) {
            transition({ 
              key: `mouse-rotate-${Math.floor(rotation / 10)}`, 
              ...rotate({ 
                degrees: rotation,
                spring: { stiffness: 300, damping: 30 }
              }) 
            })(el);
          }
        }}
        className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-20 h-20"
      >
        →
      </div>
    </div>
  );
}

3D Cube Rotation

function RotatingCube() {
  const [face, setFace] = useState(0);
  const faces = ['front', 'right', 'back', 'left'];
  
  return (
    <div className="perspective-1000">
      <div 
        ref={transition({ 
          key: `cube-${face}`, 
          ...rotate({ 
            axis: 'y',
            degrees: face * 90,
            perspective: 1000,
            spring: { stiffness: 200, damping: 25 }
          }) 
        })}
        className="relative w-32 h-32 transform-style-preserve-3d"
      >
        {/* Cube faces */}
        <div className="absolute inset-0 bg-red-500">Front</div>
        <div className="absolute inset-0 bg-blue-500 rotate-y-90">Right</div>
        <div className="absolute inset-0 bg-green-500 rotate-y-180">Back</div>
        <div className="absolute inset-0 bg-yellow-500 rotate-y-270">Left</div>
      </div>
      
      <button onClick={() => setFace((f) => (f + 1) % 4)}>
        Next Face
      </button>
    </div>
  );
}

Performance Optimization

  • transform: rotate() uses GPU acceleration
  • Using will-change: transform can improve performance for 3D rotations
  • Be cautious with simultaneous rotation of many elements as it may impact performance

Accessibility Considerations

<div 
  ref={transition({ 
    key: 'accessible-rotate', 
    ...rotate() 
  })}
  role="img"
  aria-label="Rotating logo"
  aria-live="polite"
>
  <Logo />
</div>
  • Loading indicators: Spinners, progress indicators
  • Icon transitions: Icon rotation on state changes
  • Card interactions: Flip effects for front/back
  • Refresh actions: Refresh button animations
  • Game elements: Roulette wheels, dice, and other game UI