Communitygithub.com

hiwurun/remotion-saas-boilerplate

以特定結構編寫 Remotion 標記,讓 Remotion Studio 能辨識程式碼結構並實現互動性。

remotion-saas-boilerplate 是什麼?

此技能提供了一套編寫 Remotion 標記的最佳實踐,旨在最大化 Remotion Studio 的互動編輯能力。它能讓開發者透過點擊選取、拖放、縮放、旋轉以及編輯 CSS 樣式等方式,與影片中的元素進行互動。該技能適用於所有使用 Remotion Studio 的專案,特別是需要複雜排版和動畫的影片製作。其核心方法是使用 `Interactive` API 來包裝元件,並遵循特定的模式,如為每個可獨立編輯的項目建立獨立的 JSX 節點、使用 `interpolate()` 進行動畫處理,以及保持 CSS 樣式內聯。透過這種結構化的方式,開發者可以創建出高度可編輯且易於協作的影片模板,同時清楚區分哪些屬性可在 Studio 中由非開發人員直接操作,從而提升團隊生產力並確保專案的可維護性。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/hiwurun/remotion-saas-boilerplate/tree/HEAD/.agents/skills/remotion-interactivity

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

預覽

來自技能 README

remotion-saas-boilerplate preview

說明文件

透過以特定方式編寫 Remotion 標記,Remotion Studio 能夠辨識程式碼結構並使其具備互動性:

  • 允許點擊選取項目
  • 允許拖放、調整大小和旋轉
  • 編輯 CSS 樣式
  • 使關鍵影格和緩動曲線可編輯

如果標記對 Studio 來說過於複雜而無法使其互動,則相關數值會變成灰色顯示。

偏好使用具有自己時間軸的互動式元件

對那些填滿其父容器的元件,使用 Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})。這個包裝器提供了它們的視覺根目錄和時間軸項目,並預設裁切溢出內容,因此內部元件可以返回一個片段 (fragment)。將計時、預掛載、尺寸和每個實例的樣式放在導出的元件上。

將屬於一起的視覺元素放在同一個元件中,這樣它們可以一起移動和修剪。內部佈局容器仍然可以提供對齊、在較小區域內裁切或動畫群組;只填充場景的容器通常是不必要的。卡片、文字框和具有自身邊界的 SVG 可以保留預設的 layout: 'none',並將 style 轉發給它們的視覺根元素。

import {Video} from '@remotion/media';
import type React from 'react';
import {Interactive, useVideoConfig, type InteractivitySchema} from 'remotion';

type SceneProps = {
  readonly title: string;
};

const SceneInner: React.FC<SceneProps> = ({title}) => {
  const {fps} = useVideoConfig();

  return (
    <>
      <Video
        name="背景素材"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{width: '100%', height: '100%'}}
      />
      <Interactive.Div
        name="場景標題"
        style={{
          position: 'absolute',
          left: 80,
          bottom: 80,
          color: 'white',
          fontFamily: 'Helvetica, Arial, sans-serif',
          fontSize: 80,
        }}
      >
        {title}
      </Interactive.Div>
    </>
  );
};

const sceneSchema = {
  title: {type: 'string', default: '', description: '標題'},
} as const satisfies InteractivitySchema;

export const Scene = Interactive.withSchema({
  Component: SceneInner,
  componentName: 'Scene',
  schema: sceneSchema,
  wrapInSequence: true,
  layout: 'absolute-fill',
});

選擇 Schema 欄位

Schema 中的每個鍵都是一個屬性 (prop),Studio 可以在調用點讀取和編輯它。 不在 schema 中的屬性仍然可以運作,但在 Studio 中無法編輯。 鍵可以使用點記法,例如 style.color。

類型用途可關鍵影格化
string字串,例如標題、提示或程式碼片段否
number數字,可選 min、max、step、integer是
boolean開/關開關是 (保持)
colorCSS 顏色字串是
enum在 variants 之間選擇,每個都有其自己的巢狀 schema可選 (保持)
array數字、顏色、列舉等列表否
asset媒體來源,可選 assetType否
font-familyCSS 字型家族否
font-weight字重是
translate、scale、rotation-css、rotation-degrees、transform-origin變換是
uv-coordinate元素上一個標準化的 [x, y] 點是
svg-pathSVG 路徑資料是
remotion-captionsCaption[] 資料否
hidden一個保持在控制項之外的值—

每個欄位都需要一個 default 值,並且應該有一個 description,Studio 會將其顯示為標籤。 設置 keyframable: false 可顯示靜態控制項。

公開內容和每個實例的外觀,例如文字和強調色。 不要添加 style.translate、style.scale、style.rotate、style.transformOrigin 和 style.opacity 這些欄位:wrapInSequence: true 已經添加了它們。

要讓根元素的 CSS 屬性在每個實例中可編輯,請展開內建的 schema 片段:

const cardSchema = {
  ...Interactive.textSchema, // style.color, style.fontSize, style.fontWeight, ...
  ...Interactive.backgroundSchema, // style.backgroundColor
  ...Interactive.borderRadiusSchema, // style.borderRadius, ...
} as const satisfies InteractivitySchema;

同樣可用的還有:Interactive.borderSchema,以及用於接受字幕的元件的 Interactive.captionsSchema。 對於接受 React.ReactNode children 的元件,添加 ...Interactive.childrenSchema。 它為 children 內容提供控制項,並且未來可能發展為支援更豐富的內容。 目前,它使用一個標籤為「文字」的字串欄位,預設值為 ''。 如果一個元件只需要字串類型的 children,請改用 type: 'string' 宣告一個明確的 children 欄位。 Interactive.textSchema 控制排版樣式;childrenSchema 控制內容。 children 片段不包含在 baseSchema 中,也不會自動添加到自訂元件中。 巢狀標記和計算後的 children 目前是唯讀的。 text-content 是 string 欄位類型的已棄用別名。 請參閱 InteractivitySchema 了解所有選項。

將可復用元件註冊為連接的組合 (Connected Compositions)

將具有自己圖層或動畫的重要場景和可復用元件註冊為獨立的組合 (Compositions)。Studio 將這些稱為連接的組合 (connected compositions):它們可以在自己的時間軸中被打開,同時與父級影片共享相同的元件實作。

在元件內部定義所需的樣式並載入字型,使其無需依賴父級即可運作。請參閱 父級獨立性 (parent independence)。

註冊父級渲染的相同匯出元件參考。對於上面的範例,使用 component={Scene},而不是 SceneInner 或內聯包裝器。 使用 wrapInSequence: true,則無需額外的 <Sequence> 來建立連接。

import {Composition} from 'remotion';
import {Scene} from './Scene';

export const RemotionRoot = () => (
  <Composition
    id="Scene"
    component={Scene}
    width={1280}
    height={720}
    fps={30}
    durationInFrames={30}
    defaultProps={{
      title: '一個新視角',
    }}
  />
);

選擇一個唯一的 ID、有代表性的內聯預設值,以及適合預覽元件的尺寸、fps 和 持續時間。註冊預設值不會自動從父級實例複製屬性。請參閱 連接的組合 (connected compositions) 以了解註冊和提取的詳細資訊。

將計時直接放在元件上

避免在一個已經接受計時屬性的元件周圍使用單獨的 <Sequence>。 內建的互動式元件以及使用 wrapInSequence: true 製作的元件可以直接接收 name、from、durationInFrames、trimBefore、playbackRate 和 premountFor。

<Scene
  name="開場場景"
  from={30}
  durationInFrames={90}
  premountFor={fps}
  style={{opacity: 0.9}}
  title="一個新視角"
/>

移除冗餘的 sequence 時,將其計時和名稱移至子元件。 保留任何現有的子元件計時;不要覆蓋或重複偏移。 元件內部的 useCurrentFrame() 調用讀取其本地影格,而調用點的樣式表達式仍使用調用者的影格。在移入動畫樣式時保留這種區別。

在以下情況下保留 <Sequence>:它為多個兄弟元素提供共享計時, 包裝了一個不處理計時的元件,或者提供了子元件無法直接接受的尺寸。受管理的根節點也接受 useVideoConfig() 使用的 width 和 height。對於連續佈局(有或沒有過渡),保留 <TransitionSeries.Sequence>;直接的 from 屬性無法取代這些行為。

為每個可獨立編輯的項目賦予其自己的 JSX 節點

Studio 編輯建立項目的 JSX 源節點。如果多個執行時期項目來自同一個 JSX 節點,它們共享一個編輯源目標。

對於每個應該可以獨立編輯的組合註冊、片段、場景、圖層或 sequence,請編寫一個單獨的 JSX 節點,並將其可編輯屬性保留在該節點上。這適用於 <Composition>、<Still>、內建媒體元件、<Sequence>、<TransitionSeries.Sequence> 和自訂元件。

例如,像這樣編寫一個可編輯的時間軸:

import { TransitionSeries } from "@remotion/transitions";

<TransitionSeries>
  <TransitionSeries.Sequence name="介紹" durationInFrames={90}>
    <Introduction />
  </TransitionSeries.Sequence>
  <TransitionSeries.Sequence name="展示" durationInFrames={150}>
    <Demo />
  </TransitionSeries.Sequence>
</TransitionSeries>

一個 .map() 或其他程式化循環會從一個 JSX 源節點建立多個執行時期項目。這適用於有意作為一個模板控制的重複輸出,例如視覺化條或 粒子。當實例需要獨立的 ID 或 名稱、屬性、後設資料、計時、排序、刪除或在 Studio 中複製時,則不適用。

使用 Interactive 讓 HTML 元素變得可互動

每個 HTML 和 SVG 元素(除了 <Img>,它已經是互動式的)例如 <div> 都可以使用 Interactive 變成互動式:

<Interactive.Div
  name="問候卡"
  style={{fontSize: 80, padding: 24}}
>
  哈囉
</Interactive.Div>

這允許在 Studio 中設定樣式和關鍵影格。要明智使用,如果一個元件有太多元素,時間軸可能會變得凌亂。

偏好內聯文字

如果文字是固定的且僅使用一次,請直接將其寫在互動元素內部,而不是將其提取到常數中。

// 👍 固定的副本保持可編輯
<Interactive.Div name="標題">
  Remotion 最佳實踐
</Interactive.Div>

僅在文字是動態的或被重複使用時,才使用屬性或變數。

為互動元素賦予描述性名稱

為元素添加 name 屬性,使其易於識別。 避免使用計算名稱,請對其進行硬編碼。

<>
  <Interactive.Div name="英雄標題" style={{fontSize: 80}}>
    發布日
  </Interactive.Div>
  <Img name="頭像" src="https://remotion.media/image.jpeg" />
  <Video name="背景" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="標題">
    發布日
  </Interactive.Div>
</>

將所有 CSS 樣式保持內聯

最好的方法是直接將一個純物件傳遞給 style——不要引用常數、不要展開物件、不要使用數學運算。

<Interactive.Div
  style={{
    fontSize: 80,
    color: 'red',
  }}
>
  哈囉,世界!
</Interactive.Div>
const baseStyle = useMemo(() => {
  return {
    fontSize: 12 // ❌ 非內聯樣式不受支援
  }
}, []);

<Interactive.Div
  style={{
    ...baseStyle, // ❌ 展開不受支援
    color: RED, // ❌ 引用常數不受支援
    scale: frame * 10 // ❌ 數學運算不受支援
  }}
>
  哈囉,世界!
</Interactive.Div>

使用 interpolate() 製作動畫

將動畫編寫為對變化屬性進行的內聯 interpolate() 調用。
輸出範圍、緩動、外推和 output 屬性應使用硬編碼值。

輸入範圍還可以使用直接從 useVideoConfig() 解構的 durationInFrames、fps、width 和 height。支援僅標識符,例如 durationInFrames、乘以數字,例如 2 * fps 或 fps * 2,以及減去數字,例如 durationInFrames - 1。

const {fps, durationInFrames} = useVideoConfig();

// 👍 內聯值可以被標準化和關鍵影格化
<Interactive.Div
  name="產品卡片"
  style={{
    color: 'white',
    fontSize: 80,
    scale: interpolate(frame, [0, fps], [0, 1], {
      easing: Easing.spring({damping: 200}),
      output: 'perceptual-scale',
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    }),
    rotate: interpolate(frame, [0, 1 * fps], ['0deg', '20deg'], {
      easing: Easing.spring({damping: 200}),
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp'
    }),
    translate: interpolate(
      frame,
      [durationInFrames - 30, durationInFrames],
      ['0px 0px', '0px 120px'],
      {
        easing: Easing.spring({damping: 200}),
        extrapolateLeft: 'clamp',
        extrapolateRight: 'clamp'
      }
    )
  }}
>
  感謝您的觀看!
</Interactive.Div>

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相關技能