Remotion LabRemotion Lab
建構應用在 Svelte 中使用 Remotion

在 Svelte 中使用 Remotion

學習如何將 Remotion Player 整合到 Svelte 應用程式中,包括建立 React 包裝器和雙向數據綁定

本指南說明如何將 Remotion 整合到 Svelte 專案中。

安裝必要套件

安裝 Remotion 和必要的依賴項:

使用 npm
npm i remotion @remotion/player @remotion/cli react react-dom
npm i --save-dev @types/react @types/react-dom
使用 pnpm
pnpm i remotion @remotion/player @remotion/cli react react-dom
pnpm i --dev @types/react @types/react-dom
使用 Yarn
yarn add remotion @remotion/player @remotion/cli react react-dom
yarn add --dev @types/react @types/react-dom
使用 Bun
bun i remotion @remotion/player @remotion/cli react react-dom
bun --dev @types/react @types/react-dom

注意:Bun 作為執行時大部分受到支援。在此閱讀更多

建立 Remotion 資料夾

為了更好地分離關注點,建立一個資料夾來存放你的 Remotion 檔案:

src/remotion

將你的 Remotion 專案或入門範本(例如 HelloWorld)的內容複製到這個新資料夾中。

複製 remotion.config.ts

remotion.config.ts 檔案複製到 Svelte 專案的根目錄,放置在與 package.json 相同的層級。

remotion.config.ts
import { Config } from '@remotion/cli/config';
 
Config.setVideoImageFormat('jpeg');
Config.setOverwriteOutput(true);

為 JSX 配置 TypeScript

要在 Svelte 中啟用 JSX 支援,請在 tsconfig.app.jsoncompilerOptions 下設定 "jsx": "react"

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react"
  }
}

為 Svelte 建立 React 包裝器組件

要在 Svelte 中嵌入 Remotion 組件,需要建立一個包裝器組件

在你的 remotion 資料夾中,建立一個名為 PlayerViewWrapper.svelte 的檔案:

PlayerViewWrapper.svelte
<script lang="ts">
  import { onDestroy, onMount } from 'svelte';
  import React from 'react';
  import { createRoot, type Root } from 'react-dom/client';
  import { type PlayerSchema, PlayerView } from './PlayerView';
  import { type PlayerRef } from '@remotion/player';
 
  let { data, player = $bindable(), onPaused } = $props<{
    data: PlayerSchema;
    player?: PlayerRef | null;
    onPaused?: () => void;
  }>();
 
  let containerRef: HTMLDivElement;
  let root: Root;
 
  // 當 data 改變時重新渲染播放器
  $effect(() => {
    // 需要在 $effect 中存取屬性,因為 Svelte 不會自動偵測深層變更
    console.log(data.titleText);
    render();
  });
 
  function render() {
    if (!containerRef || !root) return;
 
    root.render(
      React.createElement(PlayerView, {
        ref: (ref: any) => {
          player = ref?.playerRef;
        },
        data,
        onPaused,
      })
    );
  }
 
  onMount(() => {
    root = createRoot(containerRef);
    render();
  });
 
  onDestroy(() => {
    root?.unmount();
  });
</script>
 
<div bind:this={containerRef}></div>

此包裝器組件將作為 Svelte 和 Remotion React 組件之間的橋樑。

為 Remotion Player 建立包裝器

  1. 在你的 remotion 資料夾中,建立一個名為 PlayerView.tsx 的檔案。
  2. 確保每個 .tsx 檔案在頂部明確匯入 React:

此檔案使用 createRef 獲取播放器的參考:

PlayerView.tsx
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
import { Player, type PlayerRef } from '@remotion/player';
import { HelloWorld } from './HelloWorld';
 
export interface PlayerSchema {
  titleText: string;
  titleColor: string;
  logoColor1: string;
  logoColor2: string;
}
 
export const PlayerView = forwardRef<
  { playerRef: PlayerRef | null },
  {
    data: PlayerSchema;
    onPaused?: () => void;
  }
>((props, ref) => {
  const playerRef: React.RefObject<PlayerRef> = React.createRef();
 
  useEffect(() => {
    if (playerRef.current) {
      // 當播放器暫停時新增回調
      playerRef.current.addEventListener('pause', () => {
        props.onPaused?.();
      });
    }
  }, []);
 
  useImperativeHandle(ref, () => ({
    get playerRef() {
      return playerRef.current;
    },
  }));
 
  return (
    <Player
      ref={playerRef}
      component={HelloWorld}
      inputProps={props.data}
      durationInFrames={150}
      fps={30}
      compositionWidth={1920}
      compositionHeight={1080}
      style={{ width: '100%' }}
    />
  );
});

在 Svelte 中使用組件

在你的 Svelte 應用程式中使用包裝器組件:

+page.svelte
<script lang="ts">
  import PlayerViewWrapper from '../remotion/PlayerViewWrapper.svelte';
  import { type PlayerRef } from '@remotion/player';
  import type { PlayerSchema } from '../remotion/PlayerView';
 
  let player: PlayerRef | null = $state(null);
 
  let videoData: PlayerSchema = $state({
    titleText: '你好,Svelte + Remotion!',
    titleColor: '#ffffff',
    logoColor1: '#91EAE4',
    logoColor2: '#86A8E7',
  });
 
  function handlePaused() {
    console.log('播放器已暫停');
  }
 
  function updateTitle() {
    videoData.titleText = '更新後的標題';
  }
 
  function playVideo() {
    player?.play();
  }
 
  function pauseVideo() {
    player?.pause();
  }
</script>
 
<main>
  <h1>我的影片應用</h1>
 
  <PlayerViewWrapper
    data={videoData}
    bind:player
    onPaused={handlePaused}
  />
 
  <div class="controls">
    <button onclick={playVideo}>播放</button>
    <button onclick={pauseVideo}>暫停</button>
    <button onclick={updateTitle}>更新標題</button>
  </div>
</main>
 
<style>
  .controls {
    margin-top: 16px;
    display: flex;
    gap: 8px;
  }
 
  button {
    padding: 8px 16px;
    border-radius: 4px;
    border: none;
    background: #4CAF50;
    color: white;
    cursor: pointer;
  }
</style>

使用 SvelteKit 的伺服器端渲染

如果你使用 SvelteKit,需要防止在伺服器端渲染時載入 Remotion 組件:

+page.svelte(SvelteKit)
<script lang="ts">
  import { browser } from '$app/environment';
  import { onMount } from 'svelte';
 
  let PlayerViewWrapper: any = $state(null);
 
  onMount(async () => {
    // 只在瀏覽器端載入 Remotion 組件
    const module = await import('../remotion/PlayerViewWrapper.svelte');
    PlayerViewWrapper = module.default;
  });
</script>
 
{#if browser && PlayerViewWrapper}
  <svelte:component
    this={PlayerViewWrapper}
    data={{
      titleText: '你好,Remotion!',
      titleColor: '#ffffff',
      logoColor1: '#91EAE4',
      logoColor2: '#86A8E7',
    }}
  />
{:else}
  <div>載入中...</div>
{/if}

啟動 Remotion Studio

在你的 Svelte 專案根目錄中執行:

npx remotion studio

另請參閱