SSGOI

旋转动画

通过旋转元素创建动态生动的效果

旋转动画

旋转(Rotate)动画在2D或3D空间中旋转元素,创建有趣且吸引眼球的动画效果,能够吸引用户的注意力。

基本用法

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() })}>
          应用了旋转动画的元素
        </div>
      )}
    </div>
  );
}

选项

interface RotateOptions {
  degrees?: number;        // 旋转角度(默认:360)
  clockwise?: boolean;     // 是否顺时针(默认:true)
  scale?: boolean;         // 添加缩放效果(默认:false)
  fade?: boolean;          // 添加淡入淡出效果(默认:false)
  origin?: string;         // 旋转中心点(默认:'center')
  axis?: '2d' | 'x' | 'y' | 'z';  // 旋转轴(默认:'2d')
  perspective?: number;    // 3D透视距离(默认:800)
  spring?: {
    stiffness?: number;    // 弹簧刚度(默认:500)
    damping?: number;      // 阻尼系数(默认:25)
  };
}

选项说明

  • degrees: 旋转角度(360 = 一圈)
  • clockwise: true为顺时针,false为逆时针
  • scale: 旋转时添加大小变化效果
  • fade: 旋转时添加淡入淡出效果
  • origin: 旋转中心点(CSS transform-origin值)
  • axis: 旋转轴
    • '2d': 平面旋转(默认)
    • 'x': X轴旋转(上下翻转)
    • 'y': Y轴旋转(左右翻转)
    • 'z': Z轴旋转(与平面旋转相同)
  • perspective: 3D旋转时的透视距离
  • spring: 弹簧物理设置

使用示例

基本旋转变化

// 半圈旋转
const halfRotate = rotate({ 
  degrees: 180 
});

// 逆时针旋转
const counterClockwise = rotate({ 
  clockwise: false 
});

// 两圈旋转
const doubleRotate = rotate({ 
  degrees: 720 
});

// 小幅旋转
const smallRotate = rotate({ 
  degrees: 45 
});

3D旋转

// X轴旋转(卡片翻转效果)
const flipX = rotate({ 
  axis: 'x',
  degrees: 180,
  perspective: 1000
});

// Y轴旋转(开门效果)
const flipY = rotate({ 
  axis: 'y',
  degrees: 90,
  perspective: 800
});

// Z轴旋转(平面旋转)
const rotateZ = rotate({ 
  axis: 'z',
  degrees: 360
});

旋转中心点变更

// 左上角为基准旋转
const topLeftRotate = rotate({ 
  origin: 'top left',
  degrees: 90
});

// 右下角为基准旋转
const bottomRightRotate = rotate({ 
  origin: 'bottom right',
  degrees: -90
});

// 自定义中心点
const customOrigin = rotate({ 
  origin: '25% 75%',
  degrees: 180
});

复合效果

// 旋转 + 缩放
const rotateScale = rotate({ 
  degrees: 360,
  scale: true
});

// 旋转 + 淡入淡出
const rotateFade = rotate({ 
  degrees: 720,
  fade: true
});

// 旋转 + 缩放 + 淡入淡出
const rotateAll = rotate({ 
  degrees: 360,
  scale: true,
  fade: true,
  spring: { stiffness: 300, damping: 20 }
});

实用示例

加载旋转器

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"
        />
      )}
    </>
  );
}

卡片翻转

function FlipCard({ front, back }) {
  const [isFlipped, setIsFlipped] = useState(false);
  
  return (
    <div 
      className="relative w-64 h-96 cursor-pointer"
      onClick={() => setIsFlipped(!isFlipped)}
    >
      {/* 正面 */}
      {!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>
      )}
      
      {/* 背面 */}
      {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>
  );
}

刷新按钮

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>
  );
}

图标过渡

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>
  );
}

高级用法

多阶段旋转

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)}>
        下一阶段
      </button>
    </div>
  );
}

鼠标跟踪旋转

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立方体旋转

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"
      >
        {/* 立方体的每个面 */}
        <div className="absolute inset-0 bg-red-500"></div>
        <div className="absolute inset-0 bg-blue-500 rotate-y-90"></div>
        <div className="absolute inset-0 bg-green-500 rotate-y-180"></div>
        <div className="absolute inset-0 bg-yellow-500 rotate-y-270"></div>
      </div>
      
      <button onClick={() => setFace((f) => (f + 1) % 4)}>
        下一面
      </button>
    </div>
  );
}

性能优化

  • transform: rotate()使用GPU加速
  • 3D旋转时使用will-change: transform可以提升性能
  • 同时旋转大量元素可能影响性能,需谨慎使用

无障碍考虑

<div 
  ref={transition({ 
    key: 'accessible-rotate', 
    ...rotate() 
  })}
  role="img"
  aria-label="旋转的标志"
  aria-live="polite"
>
  <Logo />
</div>

推荐使用场景

  • 加载指示器:旋转器、进度显示
  • 图标过渡:状态改变时的图标旋转
  • 卡片交互:正反面翻转效果
  • 刷新功能:刷新按钮动画
  • 游戏元素:轮盘、骰子等游戏UI