text-to-lottie
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAuthoring Renderable Lottie Files
创作可渲染的Lottie文件
This app renders Lottie with Skia's Skottie module (via ),
not the JS runtime. Follow the rules below and
verify the result.
canvaskit-wasmlottie-webThis skill covers the mechanics — the JSON shape Skottie needs. For the craft (timing, easing, choreography, Disney animation principles), see LottieFiles' motion-design skill. Its guidance is in milliseconds; convert to frames with.frames = ms / 1000 * fr
本应用通过,使用Skia的Skottie模块渲染Lottie动画,而非JS的运行时。请遵循以下规则并验证结果。
canvaskit-wasmlottie-web本技能涵盖的是技术实现——即Skottie所需的JSON结构。至于创作技巧(时序、缓动、编排、迪士尼动画原则),请参阅LottieFiles的运动设计技能。其指导内容以毫秒为单位,可通过转换为帧数。frames = ms / 1000 * fr
Setting up the project
项目设置
The deliverable is not just : the viewer should be set up
and the animation should be previewable in the browser. If the player project is
missing, create it; if it exists, install/update dependencies as needed, start
the dev server, and open the local preview URL for verification.
public/lottie.jsonAlways use the official GitHub player project — never hand-roll a custom
viewer. This skill's JSON rules (slots, the properties panel, the
URL controls, the Skottie wasm wiring) only hold inside that exact project. Do
not build your own HTML page, swap in , or scaffold a bespoke
canvas setup — any of those will silently diverge from how this player renders
and the verification steps below won't apply. If the player project isn't
already on this machine, scaffold a fresh copy of the repo with degit:
?frame=lottie-webbash
npx degit diffusionstudio/lottie my-animation
cd my-animation
npm install # postinstall copies the CanvasKit wasm into /public
npm run devThen open the printed local URL. If you already have the project, just
.
npm install && npm run dev交付成果不只是:需要搭建好查看器,确保动画可在浏览器中预览。如果播放器项目缺失,请创建它;如果已存在,请按需安装/更新依赖,启动开发服务器,并打开本地预览URL进行验证。
public/lottie.json请始终使用官方GitHub播放器项目——切勿自行编写自定义查看器。本技能的JSON规则(插槽、属性面板、 URL控制、Skottie wasm连接)仅在该特定项目中生效。请勿构建自己的HTML页面、替换为或搭建定制化canvas环境——任何此类操作都会导致与本播放器的渲染方式产生隐性差异,且下文的验证步骤将不再适用。如果本机上没有该播放器项目,请使用degit克隆仓库的全新副本:
?frame=lottie-webbash
npx degit diffusionstudio/lottie my-animation
cd my-animation
npm install # postinstall会将CanvasKit wasm复制到/public目录
npm run dev然后打开打印出的本地URL。如果已有该项目,只需执行即可。
npm install && npm run devWhere to write the file (and how it loads)
文件编写位置(及加载方式)
- Write the animation JSON to . That is the only file you need to touch to change what the app shows —
public/lottie.jsonfetchessrc/App.tsxat startup./lottie.json - With the dev server running (), a Vite plugin watches that file and full-reloads the page on save, so your edit appears immediately. No other wiring is required.
npm run dev - If parsing fails, the app shows the error on screen ("CanvasKit could not parse the Lottie file.").
- 将动画JSON写入****。这是唯一需要修改的文件,用于更改应用显示内容——
public/lottie.json会在启动时获取src/App.tsx。/lottie.json - 开发服务器运行时(),Vite插件会监听该文件,保存后自动重载页面,因此你的修改会立即生效。无需其他配置。
npm run dev - 如果解析失败,应用会在屏幕上显示错误信息(“CanvasKit无法解析Lottie文件。”)。
Required top-level shape
必需的顶层结构
Every Lottie document is one JSON object with at least these fields:
jsonc
{
"v": "5.7.0", // bodymovin version string
"fr": 60, // frame rate (fps)
"ip": 0, // in point (start frame)
"op": 120, // out point (end frame) — duration = (op - ip) / fr seconds
"w": 512, // composition width (px)
"h": 512, // composition height (px)
"assets": [], // images / precomps; [] if none
"layers": [ /* ... */ ]
}The app letterboxes the × composition to fit the canvas, so pick a square
or sensible aspect ratio. controls the total frame count shown in the UI.
whop每个Lottie文档都是一个JSON对象,至少包含以下字段:
jsonc
{
"v": "5.7.0", // bodymovin版本字符串
"fr": 60, // 帧率(fps)
"ip": 0, // 入点(起始帧)
"op": 120, // 出点(结束帧)——时长 = (op - ip) / fr 秒
"w": 512, // 合成宽度(像素)
"h": 512, // 合成高度(像素)
"assets": [], // 图片/预合成;无则为[]
"layers": [ /* ... */ ]
}应用会将×的合成内容添加黑边以适配画布,因此请选择正方形或合理的宽高比。控制UI中显示的总帧数。
whopLayers
图层
layersjsonc
{
"ty": 4, // layer type: 4 = shape layer (the common case)
"nm": "circle", // name (optional but helpful)
"ip": 0, // layer in point
"op": 120, // layer out point — must cover the frames you want it visible
"st": 0, // start time
"ks": { /* transform — see below */ },
"shapes": [ /* ... */ ] // for shape layers
}Common layer types: shape, image, solid, precomp, text.
Prefer shape layers () for LLM-authored animations — no external
assets needed.
42105ty: 4layersjsonc
{
"ty": 4, // 图层类型:4 = 形状图层(常见情况)
"nm": "circle", // 名称(可选但有助于识别)
"ip": 0, // 图层入点
"op": 120, // 图层出点——必须覆盖你希望其可见的帧范围
"st": 0, // 起始时间
"ks": { /* 变换——见下文 */ },
"shapes": [ /* ... */ ] // 形状图层专用
}常见图层类型:形状、图片、纯色、预合成、文本。对于LLM创作的动画,优先选择形状图层()——无需外部资源。
42105ty: 4The transform block (ks
)
ks变换块(ks
)
ksEvery layer has a transform. Each property is either static ()
or animated ().
{ "a": 0, "k": value }{ "a": 1, "k": [ ...keyframes ] }jsonc
"ks": {
"o": { "a": 0, "k": 100 }, // opacity 0–100
"r": { "a": 0, "k": 0 }, // rotation (degrees)
"p": { "a": 0, "k": [256, 256, 0] }, // position [x, y, z]
"a": { "a": 0, "k": [0, 0, 0] }, // anchor point [x, y, z]
"s": { "a": 0, "k": [100, 100, 100] } // scale (percent, per axis)
}Anchor matters: rotation and scale pivot around the anchor , expressed in
the layer's own coordinate space. To rotate a shape around its own center, set
the shape's geometry around the anchor (e.g. center the ellipse on ).
aa每个图层都有变换属性。每个属性要么是静态的(),要么是动态的()。
{ "a": 0, "k": value }{ "a": 1, "k": [ ...关键帧 ] }jsonc
"ks": {
"o": { "a": 0, "k": 100 }, // 透明度 0–100
"r": { "a": 0, "k": 0 }, // 旋转(度数)
"p": { "a": 0, "k": [256, 256, 0] }, // 位置 [x, y, z]
"a": { "a": 0, "k": [0, 0, 0] }, // 锚点 [x, y, z]
"s": { "a": 0, "k": [100, 100, 100] } // 缩放(百分比,按轴)
}锚点很重要:旋转和缩放围绕锚点进行,锚点以图层自身的坐标空间表示。要让形状围绕自身中心旋转,请将形状的几何中心与锚点对齐(例如,将椭圆中心设置在点)。
aaShapes — the #1 Skottie gotcha
形状——Skottie最容易出错的点
Skottie requires shape elements to be wrapped in a Group (). A flat
list of shapes + fills directly in renders blank. Always nest the
geometry, fill/stroke, and a group transform inside a group's array:
ty: "gr"shapesitjsonc
"shapes": [
{
"ty": "gr", // GROUP — required wrapper
"nm": "ball",
"it": [
{
"ty": "el", // ellipse
"p": { "a": 0, "k": [0, 0] },
"s": { "a": 0, "k": [120, 120] }
},
{
"ty": "fl", // fill
"c": { "a": 0, "k": [0.2, 0.6, 1, 1] }, // RGBA, each 0–1
"o": { "a": 0, "k": 100 }
},
{
"ty": "tr", // GROUP TRANSFORM — include even if identity
"p": { "a": 0, "k": [0, 0] },
"a": { "a": 0, "k": [0, 0] },
"s": { "a": 0, "k": [100, 100] },
"r": { "a": 0, "k": 0 },
"o": { "a": 0, "k": 100 }
}
]
}
]Shape primitives inside :
it- ellipse —
"el"center,p[width, height]s - rectangle —
"rc"center,p[w, h],scorner radiusr - custom path —
"sh"is a bezierks.k{ "c": closed?, "v": verts, "i": inTangents, "o": outTangents } - stroke —
"st"color,cwidth,wopacityo - fill —
"fl"color (RGBA 0–1),copacityo - the group's transform (always include it last)
"tr"
Colors are normalized 0–1 RGBA, not 0–255. is opaque red.
[1, 0, 0, 1]Skottie要求形状元素必须包裹在Group()中。直接在中放置形状+填充的扁平列表会导致空白渲染。请始终将几何图形、填充/描边和组变换嵌套在组的数组中:
ty: "gr"shapesitjsonc
"shapes": [
{
"ty": "gr", // GROUP——必需的包裹容器
"nm": "ball",
"it": [
{
"ty": "el", // 椭圆
"p": { "a": 0, "k": [0, 0] },
"s": { "a": 0, "k": [120, 120] }
},
{
"ty": "fl", // 填充
"c": { "a": 0, "k": [0.2, 0.6, 1, 1] }, // RGBA,每个值0–1
"o": { "a": 0, "k": 100 }
},
{
"ty": "tr", // 组变换——即使是默认值也请包含
"p": { "a": 0, "k": [0, 0] },
"a": { "a": 0, "k": [0, 0] },
"s": { "a": 0, "k": [100, 100] },
"r": { "a": 0, "k": 0 },
"o": { "a": 0, "k": 100 }
}
]
}
]it- 椭圆——
"el"为中心,p为[宽度, 高度]s - 矩形——
"rc"为中心,p为[宽, 高],s为圆角半径r - 自定义路径——
"sh"为贝塞尔曲线ks.k{ "c": 是否闭合?, "v": 顶点, "i": 入切线, "o": 出切线 } - 描边——
"st"为颜色,c为宽度,w为透明度o - 填充——
"fl"为颜色(RGBA 0–1),c为透明度o - 组的变换(请始终放在最后)
"tr"
颜色为归一化的0–1 RGBA值,而非0–255。表示不透明红色。
[1, 0, 0, 1]Animating a property (keyframes)
为属性添加动画(关键帧)
Set and make an array of keyframe objects. Each keyframe has a
time (frame), a value (start value for that segment, as an array), and
easing handles /:
"a": 1ktsiojsonc
"p": {
"a": 1,
"k": [
{ "t": 0, "s": [256, 120], "i": { "x": [0.5], "y": [1] }, "o": { "x": [0.5], "y": [0] } },
{ "t": 60, "s": [256, 400], "i": { "x": [0.5], "y": [1] }, "o": { "x": [0.5], "y": [0] } },
{ "t": 120, "s": [256, 120] }
]
}- is the frame number; the last keyframe usually has no
t/i/easing pair beyondo(it's the end).s - is always an array, even for scalars like rotation:
s."s": [360] - /
iare the bezier ease handles (incoming / outgoing).o/xarrays iny. For a smooth ease use[0..1](in) andx:[0.5], y:[1](out); for linear usex:[0.5], y:[0]/x:[0], y:[0]. Multi-dimensional values may use per-axis arrays.x:[1], y:[1] - To loop seamlessly, make the last keyframe's value equal the first.
设置并将设为关键帧对象数组。每个关键帧包含时间(帧数)、值(该片段的起始值,为数组)和缓动控制/:
"a": 1ktsiojsonc
"p": {
"a": 1,
"k": [
{ "t": 0, "s": [256, 120], "i": { "x": [0.5], "y": [1] }, "o": { "x": [0.5], "y": [0] } },
{ "t": 60, "s": [256, 400], "i": { "x": [0.5], "y": [1] }, "o": { "x": [0.5], "y": [0] } },
{ "t": 120, "s": [256, 120] }
]
}- 为帧编号;最后一个关键帧通常除了
t之外没有s/i/缓动参数(它是结束帧)。o - 始终是数组,即使是旋转这样的标量:
s。"s": [360] - /
i是贝塞尔缓动控制(入/出)。o/x数组的值在y范围内。平滑缓动请使用[0..1](入)和x:[0.5], y:[1](出);线性缓动请使用x:[0.5], y:[0]/x:[0], y:[0]。多维值可使用按轴的数组。x:[1], y:[1] - 要实现无缝循环,请让最后一个关键帧的值与第一个关键帧的值相同。
Exposing editable properties (slots + the properties panel)
暴露可编辑属性(插槽+属性面板)
The app can render a live properties panel (text inputs and sliders) that
edit chosen values of the animation in real time. This rides on Skottie's
native slot feature — no re-parse, the change shows on the next frame.
To make a property editable, do two things:
1. Declare a slot in the Lottie JSON. Add a top-level object whose
keys are slot IDs, and point a property at one with instead of (or
alongside) an inline value. The slot's holds the default, in the same
shape the property would normally take.
"slots""sid""p"jsonc
{
"v": "5.7.0", "fr": 60, "ip": 0, "op": 90, "w": 512, "h": 512, "assets": [],
"slots": {
"ballColor": { "p": { "a": 0, "k": [0.231, 0.6, 1, 1] } }, // color: RGBA 0–1
"ballSize": { "p": { "a": 0, "k": 120 } } // scalar
},
"layers": [ /* ... */
// in the fill: "c": { "sid": "ballColor" }
// in a scalar: "s": { "sid": "ballSize" }
]
}Slot types map to controls like this:
| Slot value | Control rendered |
|---|---|
| scalar (a single number) | slider |
| color (RGBA 0–1) | color picker |
vec2 ( | two number inputs |
| text (a string) | text input |
The app discovers slots automatically via Skottie's — you do
not list them anywhere else for them to work. The panel appears as soon as
the animation declares at least one slot.
getSlotInfo()应用可渲染实时属性面板(文本输入框和滑块),用于实时编辑动画的选定值。这基于Skottie的原生插槽功能——无需重新解析,更改会在下一帧显示。
要让属性可编辑,请执行两步操作:
**1. 在Lottie JSON中声明插槽。**添加顶层对象,其键为插槽ID,并通过而非(或同时)内联值指向某个属性。插槽的保存默认值,格式与属性通常的格式一致。
"slots""sid""p"jsonc
{
"v": "5.7.0", "fr": 60, "ip": 0, "op": 90, "w": 512, "h": 512, "assets": [],
"slots": {
"ballColor": { "p": { "a": 0, "k": [0.231, 0.6, 1, 1] } }, // 颜色:RGBA 0–1
"ballSize": { "p": { "a": 0, "k": 120 } } // 标量
},
"layers": [ /* ... */
// 在填充中: "c": { "sid": "ballColor" }
// 在标量中: "s": { "sid": "ballSize" }
]
}插槽类型对应的控件如下:
| 插槽值类型 | 渲染的控件 |
|---|---|
| 标量(单个数字) | 滑块 |
| 颜色(RGBA 0–1) | 颜色选择器 |
二维向量( | 两个数字输入框 |
| 文本(字符串) | 文本输入框 |
应用通过Skottie的自动发现插槽——你无需在其他地方列出它们即可生效。只要动画声明了至少一个插槽,面板就会显示。
getSlotInfo()Required: a background-color control on every animation
必需:每个动画都要有背景颜色控件
Every animation you produce must expose at least one control for the
background color. The player does not paint a composition background of its
own, so add a full-composition background layer as the last entry in
(so it renders underneath everything), fill it with a slotted color,
and label that slot in . Use a rectangle the size of the
composition:
layerscontrols.jsonjsonc
// last layer in `layers`:
{
"ty": 4, "nm": "background", "ip": 0, "op": 120, "st": 0,
"ks": { "o": { "a": 0, "k": 100 }, "p": { "a": 0, "k": [256, 256, 0] },
"a": { "a": 0, "k": [0, 0, 0] }, "s": { "a": 0, "k": [100, 100, 100] },
"r": { "a": 0, "k": 0 } },
"shapes": [
{ "ty": "gr", "it": [
{ "ty": "rc", "p": { "a": 0, "k": [256, 256] },
"s": { "a": 0, "k": [512, 512] }, "r": { "a": 0, "k": 0 } },
{ "ty": "fl", "c": { "sid": "bgColor" }, "o": { "a": 0, "k": 100 } },
{ "ty": "tr", "p": { "a": 0, "k": [0, 0] }, "a": { "a": 0, "k": [0, 0] },
"s": { "a": 0, "k": [100, 100] }, "r": { "a": 0, "k": 0 },
"o": { "a": 0, "k": 100 } }
] }
}jsonc
// slots: "bgColor": { "p": { "a": 0, "k": [1, 1, 1, 1] } } // default white
// controls: { "sid": "bgColor", "label": "Background color" }Match the rectangle's / to your composition's ×. This is in addition
to whatever other controls the animation exposes.
pswh2. (Optional) Describe presentation in . Slots only
expose an ID and type, not a label or a sensible slider range. The sidecar file
adds that. It is optional — missing entries fall back to the slot ID and a
generic 0–100 range. Like , it hot-reloads on save.
public/controls.jsonlottie.jsonjsonc
{
"controls": [
{ "sid": "ballColor", "label": "Ball color" },
{ "sid": "ballSize", "label": "Ball size", "min": 40, "max": 240, "step": 1 }
]
}- must match a slot ID exactly.
sid - is the display name;
label/min/maxshape scalar sliders and vec2 inputs (ignored for color/text).step - An entry whose matches no slot is simply ignored; a slot with no entry still renders with defaults.
sid
你制作的每个动画都必须至少暴露一个背景颜色控件。播放器本身不会绘制合成背景,因此请添加一个覆盖整个合成的背景图层作为中的最后一个条目(使其在所有内容下方渲染),用带插槽的颜色填充它,并在中为该插槽添加标签。使用与合成尺寸相同的矩形:
layerscontrols.jsonjsonc
// `layers`中的最后一个图层:
{
"ty": 4, "nm": "background", "ip": 0, "op": 120, "st": 0,
"ks": { "o": { "a": 0, "k": 100 }, "p": { "a": 0, "k": [256, 256, 0] },
"a": { "a": 0, "k": [0, 0, 0] }, "s": { "a": 0, "k": [100, 100, 100] },
"r": { "a": 0, "k": 0 } },
"shapes": [
{ "ty": "gr", "it": [
{ "ty": "rc", "p": { "a": 0, "k": [256, 256] },
"s": { "a": 0, "k": [512, 512] }, "r": { "a": 0, "k": 0 } },
{ "ty": "fl", "c": { "sid": "bgColor" }, "o": { "a": 0, "k": 100 } },
{ "ty": "tr", "p": { "a": 0, "k": [0, 0] }, "a": { "a": 0, "k": [0, 0] },
"s": { "a": 0, "k": [100, 100] }, "r": { "a": 0, "k": 0 },
"o": { "a": 0, "k": 100 } }
] }
}jsonc
// 插槽: "bgColor": { "p": { "a": 0, "k": [1, 1, 1, 1] } } // 默认白色
// 控件: { "sid": "bgColor", "label": "Background color" }请将矩形的/与合成的×匹配。这是除动画暴露的其他控件之外必须添加的内容。
pswh**2.(可选)在中描述展示信息。**插槽仅暴露ID和类型,不包含标签或合理的滑块范围。这个附属文件可添加这些信息。它是可选的——缺失的条目会回退到插槽ID和通用的0–100范围。与一样,保存后会自动重载。
public/controls.jsonlottie.jsonjsonc
{
"controls": [
{ "sid": "ballColor", "label": "Ball color" },
{ "sid": "ballSize", "label": "Ball size", "min": 40, "max": 240, "step": 1 }
]
}- 必须与插槽ID完全匹配。
sid - 是显示名称;
label/min/max用于设置标量滑块和二维向量输入框的范围(对颜色/文本无效)。step - 与任何插槽不匹配的条目会被忽略;没有对应条目的插槽仍会使用默认值渲染。
sid
Controlling playback from a browser agent
通过浏览器代理控制播放
When you drive the page through a browser tool, do not pixel-drag the slider or
hunt for the play button — it's unreliable and you can't land on an exact
frame. Instead, pin the frame in the URL and read the canvas by its test id:
http://localhost:5173/?frame=60&paused=1- seeks to frame
?frame=Non load and holds it paused, so the moment sits still for a screenshot. This is the right way to inspect a specific frame (e.g. "is the ball at the bottom at frame 60?"): openN, then screenshot.?frame=60 - starts paused (at frame 0, or at
?paused=1if also given);frameforces autoplay even with a frame pinned.?paused=0 - With no query params the animation autoplays as usual.
To change the inspected frame, navigate to a new URL (or just edit the query
string and reload). The canvas carries , so a
browser tool can target it directly for screenshots. If the canvas is blank,
the page hasn't finished loading or the Lottie failed to parse (check the
on-screen error).
data-testid="lottie-canvas"当你通过浏览器工具操作页面时,不要拖动滑块或寻找播放按钮——这不可靠且无法精确定位到某一帧。相反,请在URL中固定帧并通过测试ID读取画布:
http://localhost:5173/?frame=60&paused=1- 会在加载时跳转到第
?frame=N帧并保持暂停状态,以便截图查看该帧。这是检查特定帧的正确方式(例如“第60帧时球是否在底部?”):打开N,然后截图。?frame=60 - 启动时处于暂停状态(在第0帧,或同时指定
?paused=1时在该帧);frame强制自动播放,即使已固定帧。?paused=0 - 无查询参数时,动画会正常自动播放。
要更改检查的帧,请导航到新URL(或直接编辑查询字符串并重新加载)。画布带有,因此浏览器工具可直接定位它进行截图。如果画布为空,说明页面尚未加载完成或Lottie解析失败(请检查屏幕上的错误信息)。
data-testid="lottie-canvas"Before you finish — checklist
完成前的检查清单
- The file is valid JSON (no comments, no trailing commas). Validate with
.
node -e "JSON.parse(require('fs').readFileSync('public/lottie.json','utf8'))" - Every shape primitive/fill is inside a group's
"ty": "gr"array, and each group ends with aittransform."tr" - Top-level and each layer's
opcover the frames you animate.op - Colors are 0–1 RGBA; positions/sizes are within the ×
wcomposition.h - Keyframe values are arrays; loops repeat the first value at the end.
s - A background-color control is present: a full-composition background layer
(last in ) with a slotted fill (e.g.
layers) and a matchingbgColorlabel.controls.json - The project is the official GitHub player (scaffolded via degit), not a custom/hand-rolled viewer.
- If the dev server is running, just save — it hot-reloads. Otherwise start it
with . A blank canvas (no error) → re-check the group wrapping.
npm run dev - The player is running and the preview URL has been opened or reported. When a
browser tool is available, verify the page shows a nonblank rendered
animation before finalizing — pin a key frame via the URL (see "Controlling
playback from a browser agent"), e.g. open and screenshot, rather than dragging the on-screen slider.
?frame=60&paused=1
- 文件是有效的JSON(无注释,无尾随逗号)。可使用验证。
node -e "JSON.parse(require('fs').readFileSync('public/lottie.json','utf8'))" - 每个形状原语/填充都在组的
"ty": "gr"数组内,且每个组都以it变换结尾。"tr" - 顶层和每个图层的
op覆盖了你制作动画的帧范围。op - 颜色为0–1 RGBA值;位置/尺寸在×
w的合成范围内。h - 关键帧的值是数组;循环动画的最后一个值与第一个值相同。
s - 存在背景颜色控件:一个覆盖整个合成的背景图层(位于最后),带有带插槽的填充(例如
layers),并在bgColor中有匹配的标签。controls.json - 项目是官方GitHub播放器(通过degit搭建),而非自定义/自行编写的查看器。
- 如果开发服务器正在运行,只需保存即可——它会自动重载。否则请用启动。空白画布(无错误)→重新检查组包裹情况。
npm run dev - 播放器正在运行,且已打开或报告预览URL。当有浏览器工具可用时,请在最终确定前验证页面显示非空白的渲染动画——通过URL固定关键帧(请参阅“通过浏览器代理控制播放”),例如打开并截图,而非拖动屏幕上的滑块。
?frame=60&paused=1