Remotion LabRemotion Lab
核心概念spring() — 彈簧物理動畫

spring() — 彈簧物理動畫

spring() 是 Remotion 的物理引擎動畫原語,使用彈簧模擬讓動畫更自然、更有生命力。

spring()

spring() 是一個基於物理的動畫原語,透過模擬彈簧的物理特性讓你的動畫看起來更自然流暢。

基本用法

spring-example.ts
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
 
const value = spring({
  frame,
  fps,
  config: {
    stiffness: 100,
  },
});

在以下範例中,value 用於控制 divscale 屬性:

import {useCurrentFrame, useVideoConfig, spring, AbsoluteFill} from 'remotion';
 
export const SpringDemo: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
 
  const scale = spring({
    frame,
    fps,
    config: {
      damping: 10,
      mass: 1,
      stiffness: 100,
    },
  });
 
  return (
    <AbsoluteFill
      style={{
        backgroundColor: '#0b84f3',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <div
        style={{
          width: 100,
          height: 100,
          backgroundColor: 'white',
          borderRadius: 10,
          transform: `scale(${scale})`,
        }}
      />
    </AbsoluteFill>
  );
};

若要關閉預設的彈跳效果,請增加 damping 參數的值(例如設為 100)。

如需更進階的互動式展示,請訪問 remotion.dev/timing-editor

參數

frame

目前的時間值。大多數時候你會傳入 useCurrentFrame() 的回傳值。

彈簧動畫從第 0 幀開始,所以如果你想要延遲動畫,可以傳入不同的值,例如 frame - 20

const frame = useCurrentFrame();
 
// 動畫從第 20 幀開始
const value = spring({
  frame: frame - 20,
  fps,
});

fps

每秒幀數,用於計算彈簧動畫。這應該始終是 useVideoConfig() 回傳值的 fps 屬性。

const {fps} = useVideoConfig();

from?

預設值:0

動畫的初始值

to?

預設值:1

動畫的最終值

請注意,根據參數設定,彈簧動畫可能會稍微超過目標值,然後再彈回最終目標。

reverse? v3.3.92

預設值:false

反向播放動畫。請參閱:操作順序

const value = spring({
  frame,
  fps,
  reverse: true, // 動畫從 1 到 0
});

config?

一個可選的物件,允許你自訂動畫的物理特性。

mass?

預設值:1

彈簧的重量。減少質量會讓動畫變快!

damping?

預設值:10

動畫的減速程度。值越高,彈跳越少;設為 100 可以完全消除彈跳。

stiffness?

預設值:100

彈簧剛性係數。調整這個值會影響動畫的彈跳感。值越高,動畫越快速有力。

overshootClamping?

預設值:false

決定動畫是否可以超越 to 值。

如果設為 true,當動畫超過 to 時,它會直接回傳 to 的值,而不會有任何超衝(overshoot)。

durationInFrames? v3.0.27

將動畫曲線拉伸到你指定的確切長度。

spring-example.ts
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
 
const value = spring({
  frame,
  fps,
  config: {
    stiffness: 100,
  },
  durationInFrames: 40, // 動畫精確持續 40 幀
});

另請參閱:操作順序

durationRestThreshold? v3.0.27

動畫應該接近結尾多少才算「完成」,用於計算持續時間。僅在也指定了 durationInFrames 時才有效。

例如,如果給定 durationRestThreshold0.001,且 durationInFrames30,這意味著在 30 幀後,彈簧已經達到了距離最終值 99.9%(1 - 0.001 = 0.999)的位置。

delay? v3.3.90

延遲動畫的幀數。

例如,如果給定 delay25,則第 0-24 幀會回傳初始值,動畫實際上從第 25 幀開始。

另請參閱:操作順序

const value = spring({
  frame,
  fps,
  delay: 25, // 等待 25 幀後才開始動畫
});

操作順序

以下是 durationInFramesreversedelay 這些操作的應用順序:

  1. 首先,如果你傳入了 durationInFrames,彈簧動畫會被拉伸到你指定的持續時間
  2. 然後,如果你傳入了 reverse: true,動畫會被反向播放
  3. 最後,如果你傳入了 delay,動畫會被延遲

實用範例

結合 interpolate() 使用

spring() 回傳 0 到 1 之間的進度值,你可以用 interpolate() 將其映射到任何範圍:

import {useCurrentFrame, useVideoConfig, spring, interpolate, AbsoluteFill} from 'remotion';
 
export const SlideAndFade: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
 
  const progress = spring({
    frame,
    fps,
    config: {damping: 12},
  });
 
  const translateY = interpolate(progress, [0, 1], [60, 0]);
  const opacity = interpolate(progress, [0, 0.4], [0, 1], {
    extrapolateRight: 'clamp',
  });
 
  return (
    <AbsoluteFill
      style={{
        backgroundColor: '#1a1a2e',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <h1
        style={{
          color: 'white',
          fontSize: 72,
          fontWeight: 'bold',
          transform: `translateY(${translateY}px)`,
          opacity,
        }}
      >
        你好,Remotion!
      </h1>
    </AbsoluteFill>
  );
};

使用 durationInFrames 精確控制時間

import {useCurrentFrame, useVideoConfig, spring} from 'remotion';
 
export const TimedSpring: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
 
  // 動畫精確持續 1 秒(假設 30fps 則為 30 幀)
  const scale = spring({
    frame,
    fps,
    config: {stiffness: 200, damping: 15},
    durationInFrames: 30,
  });
 
  return (
    <div
      style={{
        transform: `scale(${scale})`,
        width: 150,
        height: 150,
        backgroundColor: '#e74c3c',
        borderRadius: '50%',
      }}
    />
  );
};

延遲序列動畫

import {useCurrentFrame, useVideoConfig, spring, AbsoluteFill} from 'remotion';
 
const items = ['第一項', '第二項', '第三項', '第四項'];
 
export const StaggeredList: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
 
  return (
    <AbsoluteFill
      style={{
        backgroundColor: '#2c3e50',
        padding: 60,
        display: 'flex',
        flexDirection: 'column',
        gap: 20,
        justifyContent: 'center',
      }}
    >
      {items.map((item, index) => {
        const opacity = spring({
          frame,
          fps,
          delay: index * 8, // 每個項目延遲 8 幀
          config: {damping: 15},
        });
 
        const translateX = spring({
          frame,
          fps,
          delay: index * 8,
          from: -50,
          to: 0,
          config: {damping: 15},
        });
 
        return (
          <div
            key={item}
            style={{
              opacity,
              transform: `translateX(${translateX}px)`,
              backgroundColor: 'rgba(255,255,255,0.1)',
              padding: '20px 30px',
              borderRadius: 8,
              color: 'white',
              fontSize: 32,
            }}
          >
            {item}
          </div>
        );
      })}
    </AbsoluteFill>
  );
};

反向動畫(淡出效果)

import {useCurrentFrame, useVideoConfig, spring} from 'remotion';
 
export const FadeOut: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
 
  // reverse: true 讓動畫從 1 到 0(淡出)
  const opacity = spring({
    frame,
    fps,
    reverse: true,
    config: {damping: 20},
    durationInFrames: 40,
  });
 
  return (
    <div style={{opacity, fontSize: 60, color: 'white'}}>
      淡出效果
    </div>
  );
};

參數速查表

參數型別預設值說明
framenumber必填目前幀數
fpsnumber必填每秒幀數
fromnumber0動畫起始值
tonumber1動畫目標值
delaynumber0延遲幀數
reversebooleanfalse是否反向播放
durationInFramesnumber自動固定動畫持續幀數
durationRestThresholdnumber0.005完成判定閾值
config.massnumber1質量(越大越慢)
config.dampingnumber10阻尼(越大彈跳越少)
config.stiffnessnumber100剛性(越大越快速)
config.overshootClampingbooleanfalse是否限制超衝

相容性

環境支援
Chrome
Firefox
Safari
Node.js
Bun
Serverless 函式
客戶端渲染
伺服器端渲染
Player
Studio

致謝

此函式取自 Reanimated 2,它本身也是 Remotion 的重要靈感來源。

相關資源