Communitygithub.com

ahmadzulfi/video

以特定方式編寫 Remotion 標記,使 Remotion Studio 能夠識別程式碼結構並提供互動功能。

video 是什麼?

這個技能提供了一套最佳實踐,指導開發者如何撰寫 Remotion 標記,以便 Remotion Studio 能夠將其識別為可互動的元件。透過遵循這些規範,使用者可以在 Studio 中直接點選選取項目、拖曳、調整大小、旋轉,並編輯 CSS 樣式、關鍵影格和緩動函數。它支援 Remotion Studio 平台,並且透過使用 `Interactive` API 和 `Interactive.withSchema()` 來實現互動性。此技能詳細說明了如何選擇結構化的屬性(Schema)、註冊可重複使用的元件為「連接組合」(Connected Compositions)、將計時直接放在元件上、以及讓每個可獨立編輯的項目擁有自己的 JSX 節點。最終目標是讓開發者創建出 Studio 能完全理解的程式碼,從而獲得流暢的視覺化編輯體驗。

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

在你喜歡的 AI 中提問

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

預覽

來自技能 README

video 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="Background footage"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{width: '100%', height: '100%'}}
      />
      <Interactive.Div
        name="Scene title"
        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: 'Title'},
} as const satisfies InteractivitySchema;

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

選擇結構化屬性欄位

結構化屬性中的每個鍵都是一個 prop,Studio 可以在呼叫端讀取和編輯。 不在結構化屬性中的 props 仍然有效,但在 Studio 中不可編輯。 鍵可以使用點記號,例如 style.color。

類型用途可設關鍵影格
string字串,例如標題、提示或程式碼片段否
number數字,可選用 min、max、step、integer是
boolean開關是 (保持)
colorCSS 顏色字串是
enum在 variants 之間選擇,每個選項都有自己的巢狀結構化屬性選擇性加入 (保持)
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 以顯示靜態控制項。

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

要使根元素的 CSS 屬性可針對每個實例進行編輯,請展開內建的結構化屬性片段:

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 子元素的元件,請添加 ...Interactive.childrenSchema。 它為子元素內容提供控制項,並可能隨時間演進以支援更豐富的內容。 目前,它使用一個標籤為 "Text" 且預設值為 '' 的字串欄位。 如果某個元件僅接受字串子元素,請改為使用 type: 'string' 聲明一個明確的 children 欄位。 Interactive.textSchema 控制排版樣式;childrenSchema 控制內容。 子元素片段不包含在 baseSchema 中,也不會自動添加到自訂元件中。 巢狀標記和計算出的子元素目前是唯讀的。 text-content 是 string 欄位類型的棄用別名。 請參閱 InteractivitySchema 以了解所有選項。

將可重複使用的元件註冊為連接組合

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

在元件內部定義所需的樣式並載入字型,以便它在沒有父元件的情況下也能運作。請參閱 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: 'A new perspective',
    }}
  />
);

選擇一個唯一的 ID、具有代表性的默認值,以及適合預覽該元件的尺寸、fps 和持續時間。註冊默認值不會自動從父實例複製 props。請參閱 connected compositions 以了解註冊和提取的詳細資訊。

直接將計時放在元件上

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

<Scene
  name="Opening scene"
  from={30}
  durationInFrames={90}
  premountFor={fps}
  style={{opacity: 0.9}}
  title="A new perspective"
/>

當移除多餘的序列時,將其計時和名稱移動到子元件。 保留任何現有的子元件計時;不要覆蓋或雙重套用偏移量。 元件內部的 useCurrentFrame() 呼叫讀取其本地影格,而呼叫端的樣式表達式仍使用呼叫者的影格。在移動動畫樣式時要保留這種區別。

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

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

Studio 編輯的是創建該項目的 JSX 原始節點。如果多個執行時期項目來自同一個 JSX 節點,則它們共享一個原始編輯目標。

對於每個組合註冊、剪輯、場景、圖層或序列,如果它們應該能夠獨立編輯,請撰寫一個單獨的 JSX 節點,並將可編輯的 props 保留在該節點上。這適用於 <Composition>、<Still>、內建媒體元件、<Sequence>、<TransitionSeries.Sequence> 和自訂元件。

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

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

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

一個 .map() 或其他程式化的迴圈會從一個 JSX 原始節點建立多個執行時期項目。這適用於需要作為一個模板進行統一控制的重複輸出,例如視覺化長條圖或粒子。但當這些實例需要獨立的 ID 或名稱、props、元資料、計時、排序、刪除或複製時,就不適合使用。

使用 Interactive 使 HTML 元素具有互動性

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

<Interactive.Div
  name="Greeting card"
  style={{fontSize: 80, padding: 24}}
>
  Hello
</Interactive.Div>

這允許在 Studio 中設定樣式和關鍵影格。請保持理智,如果一個元件有許多元素,時間軸可能會變得雜亂。

偏好使用內聯文字

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

// 👍 固定的文字保持可編輯
<Interactive.Div name="Title">
  Remotion Best Practices
</Interactive.Div>

僅當文字是動態的或可重複使用時,才使用 prop 或變數。

為互動元素提供描述性名稱

為元素添加 name prop,使其易於識別。 避免使用計算出來的名稱,應將其硬編碼。

<>
  <Interactive.Div name="Hero title" style={{fontSize: 80}}>
    Launch day
  </Interactive.Div>
  <Img name="Avatar" src="https://remotion.media/image.jpeg" />
  <Video name="Background" src="https://remotion.media/video.mp4" />
  <Interactive.Div name="Title">
    Launch day
  </Interactive.Div>
</>

保持所有 CSS 樣式內聯

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

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

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

使用 interpolate() 進行動畫

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

輸入範圍還可以額外使用直接從 useVideoConfig() 解構的 durationInFrames、fps、width 和 height。支援諸如 durationInFrames 之類的裸識別碼、與數字相乘如 2 * fps 或 fps * 2,以及減去一個數字如 durationInFrames - 1。

const {fps, durationInFrames} = useVideoConfig();

// 👍 內聯數值可以被標準化和設定關鍵影格
<Interactive.Div
  name="Product card"
  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({d

…

[已截斷以進行翻譯]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相關技能