React+Tailwind+Remotion打造动态可视化技能包,一键发布至陌讯Skills
2026/9/15 1:20:27 网站建设 项目流程

如果你最近在关注前端开发的新玩法,应该已经注意到了“技能化”这股风——不只是做页面、做后台,而是把一段前端能力打包成可以被对话助手、智能体直接调用的技能包。陌讯Skills这类开放平台,正好就是干这个的。我实际折腾了一圈下来,发现用 React + Tailwind + Remotion 这套组合来做技能,是目前最顺手的路子,尤其是动态可视化类技能,比如数字动画、图表展示、大屏数据卡,几乎是为它量身定做的。这篇内容我会把这套组合从环境搭建、核心组件写法,到一键打包上传陌讯Skills 的完整流程都走一遍,适合已经会用 React、想拓展技能开发方向的前端同学,也适合刚接触 Remotion 但想快速落地一个可发布的技能包的人。

1. 为什么是React+Tailwind+Remotion这套组合

1.1 三件套各自解决什么问题

先说 React。技能包只要稍微复杂一点,就一定会遇到状态管理、组件复用、模块拆分这些问题。陌讯Skills 的技能不是简单返回一段文本,它需要渲染出可交互、可动态传参的界面。React 的组件模型非常适合干这个,你写好的技能卡片就是一个组件,输入参数就是 props,输出就是一段可以嵌入到对话流中的界面。这种“组件即技能”的心智模型,比传统的模板字符串拼接或者手写 DOM 要清晰得多。

Tailwind 解决的是样式一致性和开发效率问题。技能包不是孤立的页面,它要嵌入到陌讯Skills 的对话流、卡片容器、移动端和桌面端等不同环境里。如果用传统 CSS 或者 CSS Modules,要么样式容易互相污染,要么写起来特别啰嗦。Tailwind 的原子类方式加上统一的 spacing、颜色、圆角变量,能保证技能在任何宿主环境里渲染出来都长得差不多,而且不用为每个技能单独维护一套样式文件。

Remotion 是这套组合里最特别的一块,它允许你用 React 组件直接写视频和动画,然后把动画输出为视频、GIF,也可以在网页里用 Player 组件实时播放。对于技能来说,这意味着你可以把“数据变化”做成动态可视化,而不是一张静态截图。比如我做过一个销售数据技能,用户提问“这个月各区域业绩如何”,技能可以直接播放一段数字滚动 + 柱状图增长的动画,这个感知力远强于静态 Markdown 表格。

1.2 相比传统方案的取舍

一定有同学会问:动画用 CSS 不就行了?为什么非要上 Remotion?

我的回答是:看你要做的技能复杂度。CSS 动画做按钮 hover、卡片翻转没问题,但一旦涉及到数据驱动的时间轴动画,比如“前 30 帧显示标题,第 30 到 80 帧数字滚动,第 80 帧之后图表入场”,用 CSS 写就非常痛苦,因为你要手动计算各种延迟、关键帧百分比,而且很难把组件内部的状态和动画进度绑定起来。

Remotion 的核心优势是“用帧驱动一切”。每个组件都能拿到当前帧数 useCurrentFrame(),你可以在任意位置判断当前播到哪一帧,然后决定渲染什么内容。这个模型天然适合数据叙事类技能。

至于直接用 Canvas 或者 WebGL 手写动画,也不是不行,但开发效率和可维护性差不少。Canvas 是命令式绘制,你需要在 requestAnimationFrame 里不断清屏、重绘、计算坐标。Remotion 则是声明式的:你只是描述“这一帧长什么样”,渲染器帮你把中间过程补出来。而且 Remotion 组件就是普通 React 组件,技能里的数据请求、格式化、业务逻辑都写在组件里,上传陌讯Skills 之后,它还能作为普通组件在 React 项目里复用,这点 Canvas 做不到。

1.3 陌讯Skills为什么吃这套组合

我研究了一下陌讯Skills 的开放方式,它的技能本质上是一个“可被对话场景调用的前端模块”。平台需要在宿主环境里渲染你的技能,同时又要保证不同开发者的技能不至于互相干扰,所以它对你提交的代码有一个隐性要求:产物必须是自包含的、可独立渲染的模块。

React 组件天然适合做这种隔离。Tailwind 负责把样式锁定在一个可控范围内,Remotion Player 组件则可以把动画嵌进一个不固定尺寸的容器里,由宿主决定最终渲染尺寸。我实测下来,用这套组合打包出来的技能包,在陌讯Skills 的聊天窗口、侧边栏面板、甚至移动端 H5 里都能无缝渲染,这是“一键集成”能成立的底层原因。

还有一个关键点:陌讯Skills 的技能需要能让 AI 智能体动态调用。AI 通过技能声明里的入参 schema 决定传什么参数,然后你的 React 组件拿到这些参数直接渲染。React + Tailwind + Remotion 这套组合写出来的技能,入参声明足够清晰,组件渲染逻辑也能做到“参数进、画面出”,天然适配这种 AI-friendly 的调用模式。

2. 环境准备与工程初始化

2.1 选型:Vite还是Next.js

做陌讯Skills 技能包,我强烈建议你用 Vite + React + TypeScript,不要一上来就上 Next.js。

理由有三个。第一,技能包最终的产物是静态可嵌入的模块,不需要服务端渲染,Next.js 的 SSR、API Routes 在这套场景里基本用不上,反而增加构建复杂度。第二,Vite 的 dev server 启动速度很快,尤其是 Remotion 的预览需要频繁刷新,用 Vite 能明显感觉到开发体验更流畅。第三,Vite 对构建产物的控制更精细,你可以很容易地把技能组件打成单独的库文件或自执行包,方便上传到陌讯Skills。

如果技能未来要做得特别复杂,比如需要独立的落地页、需要 SEO、需要服务端数据聚合,那再迁移到 Next.js 也不迟。但起步阶段,Vite 是最省心的。

初始化项目我一般这么干:

npm create vite@latest my-skill -- --template react-ts cd my-skill npm install

这里的 my-skill 是你技能包的名称,我建议命名和最终上传陌讯Skills 的技能 ID 一致,避免后期维护时对不上号。

2.2 一键接入Tailwind

Tailwind 接入很简单,但现在不同版本的 Tailwind 安装方式有点区别,这是个容易踩坑的地方。

如果你用的是 Tailwind v3,经典安装方式是:

npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p

然后修改 tailwind.config.js:

/** @type {import('tailwindcss').Config} */ export default { content: [ './index.html', './src/**/*.{js,ts,jsx,tsx}', ], theme: { extend: { colors: { brand: { 50: '#eef2ff', 500: '#6366f1', 600: '#4f46e5', }, }, }, }, plugins: [], };

如果你用的是 Tailwind v4,那安装方式变成了:

npm install tailwindcss @tailwindcss/vite

然后在 vite.config.ts 里加一个插件,CSS 文件里写@import "tailwindcss";就够了。

这里我要重点提醒一个坑:content 的扫描路径一定要覆盖到所有写 className 的地方,尤其是 Remotion 组件放在哪个目录,不要漏掉。我之前有一次技能里的数字动画部分样式死活不生效,查了半天,才发现是 Tailwind JIT 没扫描到那个目录,导致动态生成的类名被 purge 掉了。这种问题不会报错,而是静默丢失样式,排查起来很隐蔽。

2.3 安装并初始化Remotion

Remotion 的安装相对简单,核心就是两个包:

npm install remotion @remotion/cli

然后创建 remotion.config.ts:

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

接着在 src 下建一个 Root.tsx,用来注册你的 Composition。这是 Remotion 项目的入口,所有可渲染的合成都要在这里登记:

import { Composition } from 'remotion'; import { SalesSkill } from './skills/SalesSkill'; export const RemotionRoot = () => { return ( <> <Composition id="SalesSkill" component={SalesSkill} durationInFrames={180} fps={30} width={800} height={600} /> </> ); };

这里 durationInFrames 是动画总帧数,如果是 30fps 的视频,180 帧就是 6 秒。对于陌讯Skills 里的技能卡片,我建议动画控制在 3 到 8 秒,太短用户看不清数据,太长又会让人失去耐心。800x600 是比较稳妥的默认尺寸,因为大多数聊天窗口的内容区宽度在这个量级。

初始化完成后,跑一下npm run remotion:preview,如果能打开 Remotion 的预览界面,说明环境基本就绪了。后面我会细说怎么在这个基础上做真正的技能卡片。

3. 核心实现:构建一个可上传的技能卡片

3.1 技能卡片的组件设计

一个陌讯Skills 技能,本质上就是一个“输入参数 + 渲染结果”的封闭组件。所以组件设计的第一步,是先定义清楚你的技能入参。

以我做过的“数据简报”技能为例,入参大致是这些:

export type DataBriefSkillProps = { title: string; description: string; metrics: { label: string; value: number; unit?: string; trend?: 'up' | 'down' | 'flat'; }[]; source?: string; };

这种类型定义有几个好处。第一,AI 智能体调用技能时,会按照这个结构来生成参数,类型定义就是你的接口协议。第二,组件内部可以针对不同参数做防御性处理,避免空数据导致白屏。第三,后面打包上传陌讯Skills 时,这个类型可以帮你自动生成入参 schema 文档,不用手写。

组件建议拆成三层:

  • 最外层是 SkillCard,负责整体布局、背景、圆角、内边距,对应陌讯Skills 宿主的卡片容器。
  • 中间层是数据区块,比如 MetricsPanel,负责把 metrics 数组渲染成一个个指标块。
  • 最内层是动画元素,比如 AnimatedNumber,用 Remotion 的帧驱动能力做数字滚动动画。

这样的分层,可以让组件既能在 Remotion 里作为视频渲染,也能在普通 React 项目里静态渲染,复用性很强。

3.2 用Remotion写第一个合成

Remotion 的核心是你可以像写普通 React 组件一样写动画,区别就是多了几个 hooks。最常用的是useCurrentFrameuseVideoConfig

useCurrentFrame返回当前帧数,useVideoConfig返回 fps、宽高等配置。基于这两个 hooks,你可以做任何逐帧计算。

我第一次写的时候用的是这种套路:

import { useCurrentFrame, useVideoConfig, interpolate, spring } from 'remotion'; export const AnimatedNumber = ({ value }: { value: number }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const progress = spring({ frame, fps, config: { damping: 200, stiffness: 100 }, }); const displayValue = Math.round(value * progress); return <div className="text-4xl font-bold text-brand-600">{displayValue}</div>; };

这段代码的意思很好理解:spring 函数根据当前帧算出一个 0 到 1 的进度值,然后拿这个进度值乘以原始数据,就得到了当前帧应该显示的数字。随着帧数增加,数字从 0 滚动到目标值,看起来就是一个流畅的数字滚动动画。

这个组件用在实际技能里效果很惊艳,尤其是配合热搜词里大家常说的“tailwind 数字动画”场景——销售数据、用户增长、系统监控指标,这类“数字会说话”的内容,用数字动画呈现,比静态文本有说服力得多。

再复杂一点的用法是用interpolate控制透明度、位移、缩放:

const opacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: 'clamp', }); const translateY = interpolate(frame, [0, 30], [20, 0], { extrapolateRight: 'clamp', });

这样标题就会在开头 20 帧内渐入,同时从下方 20 像素的位置浮上来,配合数字滚动,整体节奏就很像一个正式的数据视频了。

3.3 用Tailwind统一技能视觉规范

这里我特别想强调:Remotion 默认的 style 写法是 style={{}},但如果你用 Tailwind,完全可以在 Remotion 组件里直接用 className,关键是确保 Tailwind 扫描到了对应的 tsx 文件。

我写技能视觉规范时,会先和陌讯Skills 的宿主风格对齐。比如技能卡片圆角我用 rounded-2xl,背景用白底或浅灰渐变,标题字号统一 text-base 或 text-lg,指标数字用 text-3xl 或 text-4xl。这样技能放在对话流里不突兀,像是平台原生功能的一部分。

还有一个实用技巧:用 Tailwind 的safelist保证动态类名不被 purge 掉。比如指标趋势颜色需要根据运行时数据决定,可能是text-green-500也可能是text-red-500,如果这些类名没有出现在源码里,Tailwind 的 JIT 扫描是扫描不到的。这时候在 tailwind.config.js 里加 safelist:

safelist: [ 'text-green-500', 'text-red-500', 'text-yellow-500', 'bg-gray-50', 'bg-gradient-to-br', ],

这个坑我在做图表类技能时踩过,当时趋势颜色在本地预览正常,打包上传到陌讯Skills 后颜色全变成默认色。排查半天才知道是 Tailwind 构建时把动态类名忽略了。加了 safelist 之后问题解决。

3.4 用Player组件做实时预览

开发技能时不可能每次都导出视频看效果,Remotion 提供了一个<Player>组件,可以像播放器一样在普通 React 页面里实时预览动画。

在 App.tsx 里这样用:

import { Player } from '@remotion/player'; import { DataBriefSkill } from './skills/DataBriefSkill'; const App = () => { return ( <Player component={DataBriefSkill} inputProps={{ title: '本月业绩概览', description: '华东区环比增长 12%', metrics: [ { label: '销售额', value: 860000, unit: '元', trend: 'up' }, { label: '订单量', value: 2400, unit: '单', trend: 'up' }, ], }} durationInFrames={180} fps={30} compositionWidth={800} compositionHeight={600} controls /> ); };

注意inputProps就是把参数传给技能组件的地方。在本地调试时,你可以在 inputProps 里模拟 AI 智能体会传入的各种参数组合,包括缺字段、超长文本、空数组这些异常情况,把组件的鲁棒性在本地先磨好。

4. 一键集成到陌讯Skills:从打包到发布

4.1 理解陌讯Skills的接入模型

陌讯Skills 的技能接入模型,我理解下来是这样:平台会运行一个宿主环境,技能包被加载之后,平台把你的技能组件挂载到一个容器节点里,然后根据 AI 传过来的参数实时渲染。

所以对开发者来说,你提交的内容不只是“视频”,而是一个有输入输出约定的可交互组件。为了让平台知道你的技能接收什么参数、输出什么界面,你需要提供一份技能清单,我习惯叫 manifest.json。

一个典型的 manifest 长这样:

{ "id": "data-brief-skill", "name": "数据简报生成器", "description": "根据用户传入的指标数据,生成动态可视化数据简报", "version": "1.0.0", "entry": "skill.js", "propsSchema": { "type": "object", "properties": { "title": { "type": "string", "description": "简报标题" }, "description": { "type": "string", "description": "简报描述" }, "metrics": { "type": "array", "items": { "type": "object", "properties": { "label": { "type": "string" }, "value": { "type": "number" }, "unit": { "type": "string" }, "trend": { "type": "string", "enum": ["up", "down", "flat"] } } } } } }, "dependencies": ["react", "react-dom"] }

propsSchema 是给 AI 智能体看的,它决定了 AI 决定调用你的技能时,应该从用户的提问里抽取哪些信息填进来。schema 写得越清晰,AI 传参的准确率越高,技能的表现就越好。这块是我后来反复优化最多的地方,一开始我把字段写得比较随意,AI 传参经常缺这缺那,后来严格按 JSON Schema 规范写,情况好了很多。

4.2 配置构建脚本与产物优化

陌讯Skills 跟 Vite 原生构建的默认产物其实不完全兼容。Vite 默认是面向 web 应用的构建,会输出 index.html、js、css 等一堆文件,但技能包需要的是一个独立的、可被宿主动态加载的 JS 模块。

我推荐用 Vite 的库模式构建技能包。在 vite.config.ts 里做如下配置:

import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], build: { lib: { entry: 'src/skills/index.ts', formats: ['es'], fileName: 'skill', }, rollupOptions: { output: { // 把 react 等公共依赖 external 掉,减小体积 external: ['react', 'react-dom'], globals: { react: 'React', 'react-dom': 'ReactDOM', }, }, }, }, });

把外部依赖 external 掉,可以让技能包的体积大幅下降。我实际构建过一个包含 6 个指标的技能包,产物只有 38KB,比直接全量打包小了一个数量级。陌讯Skills 宿主环境本身提供 React 运行时,所以这种 external 策略是可行的,而且加载速度明显更快。

4.3 用CLI一键发布

一键集成没有真正的“一键”是不行的。我写了这样一个发布脚本,本地构建完成后自动打包上传到陌讯Skills:

#!/usr/bin/env bash set -e echo "构建技能包..." npm run build echo "版本号: ${VERSION:=1.0.0}" echo "上传到陌讯Skills..." npx moxun-skills-cli upload \ --manifest dist/manifest.json \ --bundle dist/skill.js \ --version "$VERSION" \ --token "$MOXUN_TOKEN"

发布技能之前,检查几件事:manifest.json 的 schema 有没有更新版本号、产物文件名是否和 manifest 里 entry 一致、token 是否还有权限。我踩过的坑是改完代码忘记改 version,导致重复版本上传被平台拒绝。建议在上传脚本里加一个从 package.json 读取版本号的逻辑,保持同步。

上传成功之后,陌讯Skills 会给一个预览链接,你可以在真实的对话环境里测试。测试时我一般会准备三种用例:正常输入、极端输入(比如空数据、超大数值)、模糊输入(比如用户没说具体指标),观察 AI 传参和技能渲染表现。

4.4 本地调试全链路

陌讯Skills 平台上的调试能做的有限,真正的开发调试还是要放在本地。我的工作流是这样的:

  1. 用 Player 组件在本地把技能动画调到满意。
  2. 用 Vite 的 dev server 模拟陌讯Skills 的宿主环境,验证技能组件嵌入后的表现。
  3. 构建产物后,写一个最小化的宿主页面,手动加载 skill.js,确认产物包没有依赖缺失。
  4. 最后才上传到陌讯Skills 做真机验证。

这套流程走成熟后,从改代码到上线,基本能控制在几分钟内。

5. 常见问题与排查技巧实录

5.1 Remotion白屏与帧参数问题

Remotion 相关的问题里,白屏出现频率最高。我遇到过的白屏原因主要有三种:

第一种是 Composition 的 component 没有正确导出。你检查一下 Root.tsx 里注册的组件和实际导出的组件是否一致,一不小心路径写错就会白屏。

第二种是 durationInFrames 设置得不合理。如果你在组件里用了一些极端插值,比如 frame 到了 200 才触发某个动画,但 durationInFrames 只有 180,那后半段内容就永远显示不出来,看起来就像卡住了。而且这个还不算严格意义的白屏,更像“动画没播完”。

第三种是渲染容器没有设置宽高。Remotion 的 Player 组件会覆盖容器尺寸,但如果你在其他环境直接渲染组件,容器高度为 0,看起来就是白屏。解决办法是给容器一个显式的高:

<div style={{ width: '100%', height: 400 }}> <Player ... /> </div>

5.2 Tailwind样式丢失或错位

技能上传到陌讯Skills 后样式丢失或者错位,90% 都是 Tailwind JIT 扫描问题。我前面提过 safelist 的解决办法,这里再补充一个更系统的排查流程:

  • 首先在本地构建产物里搜索一下 className 对应样式是否存在。如果产物 CSS 里就没有,那就是扫描问题。
  • 其次检查 tailwind.config.js 的 content 是否包含你技能组件的目录。
  • 最后检查 Tailwind 版本。Tailwind v4 和 v3 的配置方式差别很大,不要照搬旧配置。

还有一类错位问题,是宿主环境的全局样式干扰了你的技能。虽然 Tailwind 本身有 preflight 重置,但宿主页面如果有自己的全局样式,层级更高的选择器可能覆盖掉你的类。我应对的方式是在技能卡片的最外层包一层带有固定 class 的容器,比如moxun-skill-root,然后在 CSS 里针对性微调,减少被全局样式影响的概率。

5.3 技能包上传失败与体积过大

上传失败通常会在几秒之内报错。常见原因一个是 manifest.json 格式不正确,另一个是产物文件超过了平台的体积限制。

如果你的技能包体积超标,优先做这几件事:

  1. 确认 React、ReactDOM 已经被 external,不要打进产物里。
  2. 检查 Remotion 的引入方式,尽量按需引入,不要import { remotion } from 'remotion'这种全量导入。
  3. 如果技能里用了较大的静态资源(图片、字体),考虑改用 CDN 地址,而不是打进包内。
  4. rollup-plugin-visualizer分析产物构成,定位大模块。

我做过的一个香港旅游攻略技能,最初体积 1.2MB,就是因为把几张高清景区图打进了包里。后来改成 CDN 图片地址,体积直接降到 90KB,加载体验完全不是一个级别。

5.4 技能设计对AI智能体的友好度优化

这可能是目前前端技能开发最有趣的一块。陌讯Skills 的 AI 智能体会读取你的 manifest 和 propsSchema,然后决定“何时调用你的技能、传入什么参数”。所以你的技能设计越接近“纯函数”,AI 用起来越顺手。

我的经验是三条:

  1. 技能职责要单一。如果你发现一个技能需要五六个入参还互相依赖,拆分掉,一个技能只做一件事。
  2. 入参默认值要好。AI 不可能每次都把所有参数传全,你的组件要对缺失参数有默认渲染,而不是白屏或者报错。
  3. 输出要有自解释性。技能渲染完成之后,如果有机会给 AI 一个回执(比如渲染成功、渲染的指标数量),AI 下一次调用时会更聪明。

这套思路我现在做前端技能开发时完全用它,本质上就是把前端组件当成一个被 AI 驱动的小应用来设计。

5.5 开局踩过的三个具体坑

最后分享三个我实际踩过、花了不少时间的坑,希望你不用重走。

第一个是版本兼容。Remotion 升级到 4.x 之后,很多旧 API 改了名字,比如registerRoot的路径、Config的写法都有变化。你百度搜“remotion skill 安装”可能会找到一堆旧教程,照着做大概率报错。最好的办法是直接看官方文档的 upgrade guide,或者直接用最新版本跑一遍最小示例。

第二个是数字动画和 Tailwind 的字体变量冲突。Tailwind 默认字体族和 Remotion 渲染时用的字体可能不一致,导致数字宽度跳动或者位置偏移。解决办法是在 Tailwind 的 theme 里显式配置字体栈,或者给数字区域单独设置 font-family。

第三个是本地渲染和上传后表现不一致。这个原因很多,常见的是宿主环境没有提供 Remotion 的某些浏览器 API。我会在上传前用 headless 浏览器做一次真实渲染校验,确保产物在独立环境里能正常跑起来。

写在最后的一点心得

做到现在,我最大的体会是:陌讯Skills 这种技能平台,给前端开发者开了一扇新门。以前我们写 React 组件是给“人”看的,现在组件要同时给“人”和“AI”看,这要求我们更严谨地设计输入输出、更认真地对待异常边界、更刻意地控制包体积和加载性能。React + Tailwind + Remotion 这套组合恰好覆盖了界面、样式、动态展示三条线,是目前做技能开发效率最高的一组搭档。还是那句话,不要一上来追求复杂,先把一个数据展示技能从开发到上传跑通,再慢慢加动画、加交互。技能开发的乐趣,试一次就知道了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询