在 Angular 中使用 Remotion
學習如何將 Remotion Player 整合到 Angular 應用程式中,包括 React 包裝器組件和 TypeScript 設定
本指南說明如何將 Remotion 整合到 Angular 專案中。
安裝必要套件
安裝 Remotion 和必要的依賴項:
npm i remotion @remotion/player @remotion/cli @remotion/zod-types react react-dom zod
npm i --save-dev @types/react @types/react-dompnpm i remotion @remotion/player @remotion/cli @remotion/zod-types react react-dom zod
pnpm i --dev @types/react @types/react-domyarn add remotion @remotion/player @remotion/cli @remotion/zod-types react react-dom zod
yarn add --dev @types/react @types/react-dombun i remotion @remotion/player @remotion/cli @remotion/zod-types react react-dom zod
bun --dev @types/react @types/react-dom注意:Bun 作為執行時大部分受到支援。在此閱讀更多。
建立 Remotion 資料夾
為了更好地分離關注點,建立一個資料夾來存放你的 Remotion 檔案:
src/app/remotion
將你的 Remotion 專案或入門範本(例如 HelloWorld)的內容複製到這個新資料夾中。這有助於將 Remotion 相關檔案與其他 Angular 程式碼分開。
複製 remotion.config.ts
將 remotion.config.ts 檔案複製到 Angular 專案的根目錄,放置在與 package.json 相同的層級。
import { Config } from '@remotion/cli/config';
Config.setVideoImageFormat('jpeg');
Config.setOverwriteOutput(true);此設定檔案對於 Remotion 識別和編譯你的專案設定是必要的。
為 JSX 配置 TypeScript
要在 Angular 中啟用 JSX 支援,請在 tsconfig.json 的 compilerOptions 下設定 "jsx": "react"。
{
"compilerOptions": {
"jsx": "react",
"skipLibCheck": true
}
}注意:同時建議設定
"skipLibCheck": true以防止與某些函式庫類型的相容性問題。
為 Angular 建立 React 包裝器組件
要在 Angular 中嵌入 Remotion 組件,需要建立一個包裝器組件:
- 在你的
remotion資料夾中,建立一個名為PlayerViewWrapper.tsx的檔案。 - 確保每個
.tsx檔案在頂部明確匯入 React:
import {
AfterViewInit,
Component,
effect,
ElementRef,
EventEmitter,
Input,
OnDestroy,
Output,
signal,
Signal,
ViewChild,
WritableSignal,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { PlayerRef } from '@remotion/player';
import { myCompSchema, PlayerView } from './PlayerView';
import { z } from 'zod';
const rootDomID: string = 'reactCounterWrapperId';
@Component({
selector: 'app-player-view',
standalone: true,
imports: [CommonModule, RouterOutlet],
template: `<div id="${rootDomID}" #reactWrapper></div>`,
})
export class PlayerViewWrapper implements AfterViewInit, OnDestroy {
@ViewChild(rootDomID, { static: false })
containerRef: ElementRef | undefined;
@Input({ required: true })
data: Signal<z.infer<typeof myCompSchema>> = signal({
titleText: '歡迎使用 Remotion',
titleColor: '#000000',
logoColor1: '#91EAE4',
logoColor2: '#86A8E7',
});
@Output() onPaused = new EventEmitter<void>();
playerRef: WritableSignal<PlayerRef | undefined> = signal(undefined);
private root?: Root;
constructor() {
effect(() => {
this.render();
});
}
ngAfterViewInit() {
this.root = createRoot(this.getRootDomNode());
this.render();
this.playerRef()?.play();
}
ngOnDestroy(): void {
this.root?.unmount();
}
private getRootDomNode() {
if (!this.containerRef || !this.containerRef.nativeElement) {
throw new Error('Cannot get root element. This should not happen.');
}
return this.containerRef.nativeElement;
}
protected render() {
if (!this.containerRef || !this.containerRef.nativeElement) {
return;
}
this.root?.render(
React.createElement(PlayerView, {
ref: (ref: any) => {
this.playerRef.set(ref?.playerRef);
},
data: this.data(),
onPaused: () => this.onPaused.emit(),
})
);
}
}此包裝器組件將作為 Angular 和 Remotion React 組件之間的橋樑。
你也可以傳遞 EventEmitter 而不是 Signal。
為 Remotion Player 建立包裝器
- 在你的
remotion資料夾中,建立一個名為PlayerView.tsx的檔案。 - 確保每個
.tsx檔案在頂部明確匯入 React:
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
import { Player, type PlayerRef } from '@remotion/player';
import { HelloWorld } from './HelloWorld';
import { z } from 'zod';
export const myCompSchema = z.object({
titleText: z.string(),
titleColor: z.string(),
logoColor1: z.string(),
logoColor2: z.string(),
});
export const PlayerView = forwardRef<
{ playerRef: PlayerRef | null },
{
data: z.infer<typeof myCompSchema>;
onPaused?: () => void;
}
>((props, ref) => {
const playerRef = useRef<PlayerRef>(null);
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%' }}
/>
);
});在 Angular 模板中使用組件
在你的 Angular 模板中使用包裝器組件:
import { Component, signal } from '@angular/core';
import { PlayerViewWrapper } from './remotion/PlayerViewWrapper';
import { z } from 'zod';
import { myCompSchema } from './remotion/PlayerView';
@Component({
selector: 'app-root',
standalone: true,
imports: [PlayerViewWrapper],
template: `
<div>
<h1>我的影片應用</h1>
<app-player-view
[data]="videoData"
(onPaused)="handlePaused()"
/>
</div>
`,
})
export class AppComponent {
videoData = signal<z.infer<typeof myCompSchema>>({
titleText: '你好,Angular + Remotion!',
titleColor: '#ffffff',
logoColor1: '#91EAE4',
logoColor2: '#86A8E7',
});
handlePaused() {
console.log('播放器已暫停');
}
// 更新影片屬性
updateTitle(newTitle: string) {
this.videoData.set({
...this.videoData(),
titleText: newTitle,
});
}
}啟動 Remotion Studio
在你的 Angular 專案根目錄中執行:
npx remotion studio此命令會啟動 Remotion Studio,讓你可以預覽和調試你的影片組件。