Communitygithub.com

Guilhermeslvg/video-portfolio

内容、动画和效果的最佳实践指南

video-portfolio 是什么?

该技能是针对使用 Remotion React Markup 构建视频作品的开发者设计的综合指南。它详细阐述了在 Studio 中编辑时的最佳实践,包括使用 `useVideoConfig()` 获取 fps 进行时间计算,通过 `useCurrentFrame()` 和 `interpolate()` 驱动动画,并强调 CSS 过渡和动画无法正确渲染,必须移除。技能还指导如何使用 `Interactive.withSchema()` 创建可编辑的交互式组件,通过 `premountFor` 提前挂载组件以确保流畅播放,以及如何正确放置和管理资产(视频、音频、图片、GIF)。针对多场景视频、连接合成、文本高亮、转场、动画模糊、视觉特效(如颜色校正、模糊、色调映射)等高级主题提供了详细的分步指导,并引用了相关的独立文档。该技能支持 Remotion Studio 和类似平台,帮助开发人员创建可编辑、高性能且结构清晰的视频作品。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Guilhermeslvg/video-portfolio/tree/HEAD/.agents/skills/remotion-markup

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

预览

来自技能 README

文档

这是编写 Remotion React 标记的指南。 如果与此无关,请加载 Remotion 最佳实践。

保留用户更改

用户可能在对话之外对代码进行编辑。

如果您发现中间发生了意外的更改,请不要覆盖它,假设它是有意为之或请求确认。

通用规则

在合成或场景组件内部,使用 useVideoConfig() 获取 fps,用于所有秒到帧的计算,包括 from、trimBefore、durationInFrames 和 premountFor 等时间属性。将表达式保留在 JSX 节点上,例如 trimBefore={4 * fps},可以让 Studio 编辑时间,并在合成帧速率更改时保持正确。

使用 useCurrentFrame() 和 interpolate() 驱动动画。 CSS transition 或 animation 无法正确渲染,需要重构。 Tailwind 动画类无法正确渲染,需要重构。

使用 Easing.bezier() 和 Easing.spring() 自定义动画时间曲线。

根据 Remotion 交互性最佳实践构建您的标记。 对于具有可编辑属性的场景,更喜欢使用 Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})。它们内部的标记可以返回一个片段;包装器处理布局和实例样式。像 lower third 这样尺寸固定的元素可以保留默认布局,并将 style 转发给它们自己的根元素。将可重用的场景注册为连接合成。

将时间属性直接放在支持它的组件上;避免冗余的 <Sequence> 包装器。 为每个支持 premountFor 的定时组件提供一秒的预挂载时间:premountFor={fps},其中 fps 来自 useVideoConfig()。将此应用于媒体、交互式组件、<Sequence>、<TransitionSeries.Sequence> 和 <TransitionSeries.Overlay>,包括嵌套在场景内部的定时组件。当嵌套项目需要在父项目启动之前挂载时,也要预挂载父时间轴项目。不支持 premountFor 的组件,例如 <TransitionSeries.Transition>,则无需替代。

Studio 编辑创建项目的 JSX 源代码节点。将每个应独立编辑的合成注册、剪辑、场景、图层和序列编写为自己的 JSX 节点,并内联其可编辑属性。 当生成的实例作为单个源模板有意控制时,程序化循环是合适的,当用户需要单独编辑实例时则不合适。

import { useCurrentFrame, useVideoConfig, Easing, interpolate, Interactive } from "remotion";

export const FadeIn = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  return (
    <Interactive.Div
      name="Title"
      premountFor={fps}
      style={{
        opacity: interpolate(frame, [0, 2 * fps], [0, 1], {
          extrapolateRight: "clamp",
          extrapolateLeft: "clamp",
          easing: Easing.bezier(0.16, 1, 0.3, 1),
        }),
      }}
    >
      Hello World!
    </Interactive.Div>
  );
};

将 interpolate() 调用内联在 style 属性中。 优先使用 scale、translate、rotate CSS 属性而不是 transform。

// 👍 内联可编辑关键帧和变换简写
style={{
  scale: interpolate(frame, [0, 100], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
    output: 'perceptual-scale' // 对于 `scale` 动画,请使用 "output: 'perceptual-scale'"
  }),
  translate: interpolate(frame, [0, 100], ["0px 0px", "100px 100px"], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
  }),
  rotate: interpolate(frame, [0, 100], ["20deg", "90deg"], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.spring({damping: 200}),
  }),
}}

// 👎 非内联值和 transform 字符串在 Studio 中变得更难编辑
const scale = interpolate(frame, [0, 100], [0, 1]);

style={{
  transform: `scale(${scale})`,
}}

资产

将资产放置在项目根目录的 public/ 文件夹中。 使用 staticFile() 引用 public/ 文件夹中的文件。

媒体组件

使用 @remotion/media 中的 <Video> 和 <Audio> 添加视频和音频。 使用 <CanvasImage> 组件添加图像。 使用 <AnimatedImage> 添加动画 GIF、APNG、WebP 或 AVIF 图像,如果不是使用 Chrome,请使用 @remotion/gif。 对 public/ 中的文件使用 staticFile(),或直接传递远程 URL:

import { Audio, Video } from "@remotion/media";
import { staticFile, CanvasImage, AnimatedImage, useVideoConfig } from "remotion";

export const MyComposition = () => {
  const {fps} = useVideoConfig();

  return (
    <>
      <Video src={staticFile("video.mp4")} premountFor={fps} style={{ opacity: 0.5 }} />
      <Audio src={staticFile("audio.mp3")} premountFor={fps} />
      <CanvasImage
        src={staticFile("logo.png")}
        premountFor={fps}
        style={{ width: 100, height: 100 }}
      />
      <Video src="https://remotion.media/video.mp4" premountFor={fps} />
      <AnimatedImage src={staticFile('nyancat.gif')} premountFor={fps} />
    </>
  );
};

如果合成主要是视频或音频剪辑的时间轴,请在选择其源结构之前阅读 video-editing.md。

示例场景

一个带有 lower third 的背景视频。 lower third 是一个带有自己时间轴的交互式组件,注册为连接合成。 它的文本作为 children 传递,accentColor 是一个可编辑属性。 淡入效果在调用点内联进行关键帧控制。

// MyScene.tsx
import { Video } from "@remotion/media";
import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
import { LowerThird } from "./LowerThird";

export const MyScene = () => {
  const { fps } = useVideoConfig();
  const frame = useCurrentFrame();

  return (
    <>
      <Video
        name="Background"
        src="https://remotion.media/video.mp4"
        premountFor={fps}
        objectFit="cover"
        style={{
          position: "absolute",
          width: "100%",
          height: "100%",
        }}
      />
      <LowerThird
        name="Lower third"
        from={1 * fps}
        premountFor={fps}
        accentColor="#0b84f3"
        style={{
          opacity: interpolate(frame, [1 * fps, 2 * fps], [0, 1], {
            extrapolateRight: "clamp",
            extrapolateLeft: "clamp",
            easing: Easing.bezier(0.16, 1, 0.3, 1),
          }),
        }}
      >
        Jane Doe, Product Designer
      </LowerThird>
    </>
  );
};
// LowerThird.tsx
import type React from "react";
import { Interactive, type InteractivitySchema } from "remotion";

type LowerThirdProps = {
  readonly children: React.ReactNode;
  readonly accentColor: string;
  readonly style?: React.CSSProperties;
};

const LowerThirdInner: React.FC<LowerThirdProps> = ({
  children,
  accentColor,
  style,
}) => {
  return (
    <Interactive.Div
      style={{
        position: "absolute",
        left: 80,
        bottom: 80,
        display: "flex",
        alignItems: "center",
        gap: 20,
        backgroundColor: "white",
        borderRadius: 16,
        padding: "20px 32px",
        color: "black",
        fontFamily: "Helvetica, Arial, sans-serif",
        fontSize: 48,
        fontWeight: 600,
        ...style,
      }}
    >
      <div
        style={{
          width: 8,
          alignSelf: "stretch",
          borderRadius: 4,
          backgroundColor: accentColor,
        }}
      />
      {children}
    </Interactive.Div>
  );
};

const lowerThirdSchema = {
  ...Interactive.childrenSchema,
  accentColor: {
    type: "color",
    default: "#0b84f3",
    description: "Accent color",
  },
} as const satisfies InteractivitySchema;

export const LowerThird = Interactive.withSchema({
  Component: LowerThirdInner,
  componentName: "LowerThird",
  schema: lowerThirdSchema,
  wrapInSequence: true,
});
import { Composition } from "remotion";
import { LowerThird } from "./LowerThird";
import { MyScene } from "./MyScene";

export const RemotionRoot: React.FC = () => {
  return (
    <>
      <Composition
        id="MyScene"
        component={MyScene}
        durationInFrames={60}
        fps={30}
        width={1280}
        height={720}
      />
      <Composition
        id="LowerThird"
        component={LowerThird}
        durationInFrames={30}
        fps={30}
        width={1280}
        height={720}
        defaultProps={{
          children: "Jane Doe, Product Designer",
          accentColor: "#0b84f3",
        }}
      />
    </>
  );
};

延迟与修剪

将时间属性直接放在支持它的组件上。有关支持的属性、示例和操作顺序,请参阅时间属性。

地图

如果希望在视频中包含地图,请参阅 Remotion 地图。

文本高亮与注释

有关文本高亮(高亮标记)、圆圈、下划线、删除线、划掉文本、方框,请参阅 text-highlights.md。

多场景视频

如果计划制作包含多个后续场景的视频,请参阅 multi-scene-video.md。

连接合成

当一个场景或一组图层值得拥有自己的可编辑时间轴时,请遵循 connected-compositions.md。在多场景视频中,对于重要的场景,优先使用这种结构。

预合成操作

对于诸如 Pre-compose Ambient glow (src/BarChart.tsx:134) 这样的 Studio 请求,在给定位置找到选定的序列标记。创建一个连接合成,遵循 connected-compositions.md:将标记提取到一个命名组件中,最好使用 Interactive.withSchema({wrapInSequence: true}) 使其具有交互性,并在根目录中使用唯一的 <Composition> 注册相同的导出组件引用。直接使用其时间属性渲染交互式组件,或者当该包装器有特定用途时,将其作为序列的唯一子元素渲染。如果选定的节点已经是一个序列,则保留其属性并提取其子元素。注册需要尺寸、fps、持续时间和与其父级使用方式等效的 defaultProps。没有注册合成的组件提取无法完成预合成请求。

将继承的样式(如 fontFamily 和字体加载)带入提取的组件中;请参阅父级独立性。

画外音

有关使用 ElevenLabs TTS 向 Remotion 合成添加 AI 生成的画外音,请参阅 voiceover.md。

嵌入视频

有关嵌入视频的高级知识(修剪、音量、速度、循环、音调),请参阅 embedding-videos.md。

嵌入音频

有关高级音频功能(修剪、音量、速度、音调),请参阅 audio.md。

裁剪

如果需要裁剪组件的可见矩形,请参阅 cropping.md。

转场

有关场景转换模式,请参阅 transitions.md。

运动模糊

当添加运动模糊或移动轨迹时,请阅读 motion-blur.md,了解首选的 HTML-in-canvas 方法、预览要求和替代方案。

视觉与像素效果

添加视觉特效时,首先确定应影响的元素或组。 如果目标组件支持 effects 并且下面的内置效果与请求匹配,则优先使用该效果而不是 CSS 近似值。例如,当晕影应该影响该视频时,可以直接将 vignette() 应用于 <Video> 的 effects 属性。

这些组件直接接受 effects:

导入来源组件
remotion<Img>, <CanvasImage>, <AnimatedImage>, <Solid>, <HtmlInCanvas>
@remotion/media<Video>
@remotion/gif<Gif>
@remotion/rive<RemotionRiveCanvas>
@remotion/shapes<Arrow>, <Callout>, <Circle>, <Ellipse>, <Heart>, <Pie>, <Polygon>, <Rect>, <Spark>, <Star>, <Triangle>

普通 HTML 元素、<Interactive.*> DOM 包装器、<AbsoluteFill> 和 <Sequence> 不直接接受 effects。要对其内容应用效果,请将它们包装在 <HtmlInCanvas> 中,并将 effects 属性放在包装器上。

对于其他元素,在合适的情况下优先使用常规 HTML 和 CSS。当对整个组进行后处理是合适的并且环境支持时,考虑将一组元素包装在 <HtmlInCanvas> 中。

在应用效果之前,请阅读 effects.md 以了解导入、参数和设置。当没有匹配的内置效果时,它还涵盖了使用 createEffect() 创建可重用的自定义效果。

可用效果

来自 @remotion/effects 的内置效果:

  • 颜色和色调:brightness(), colorCorrection(), colorKey(), contrast(), duotone(), exposure(), grayscale(), hue(), invert(), levels(), linearGradient(), linearGradientTint(), lut(), saturation(), shadowsHighlights(), thermalVision(), tint(), vibrance(), whiteBalance()。
  • 模糊和轨迹:blur(), lightTrail(), linearProgressiveBlur(), radialProgressiveBlur(), regionBlur(), sceneBlur()。
  • 位移和变形:displacement()。
  • 像素化:pixelate(), edgeWork()。
  • 样条曲线和遮罩:用 spline() 函数绘制贝塞尔形状,用 mask() 通过亮度或其他通道遮罩内容。
  • 合成和混合:composite(), useMerge(), multiplyBlend(), screenBlend(), overlayBlend()。
  • 眩光:lensGlare(), anamorphicLensFlare()。
  • 特殊效果:chromaKey(), customMatrix(), dropShadow(), glitch(), noise(), pageCurl(), polaroid(), scanlines() 等。

当没有完全匹配时,首先检查 effects.md 的目录,看看是否需要。如果没有任何效果符合目的,可以使用 CSS 效果对普通元素进行近似处理,或者将 HTML-in-canvas 与来自 @remotion/effects 的效果结合使用。不要手动实现 chromaKey() 或类似的内置效果。如果 @remotion/effects 中没有合适的效果,请参考 @remotion/lottie、@remotion/three、@remotion/renderers 或 @remotion/shapes。

管理效果列表

请参阅 effects-manager.md 以获取介绍。

Individual skills in this repo

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

相关技能