关于 webflow-mcp:interactions
此技能指导 Agent 通过 Webflow MCP 创建和管理 IX3(GSAP)交互,面向处理 Webflow 页面动画的工作流。它强调先查阅实时指南、规划并确认写入,再验证结果。
主要功能
- 01创建、更新和删除 IX3 交互
- 02管理点击、悬停和滚动触发器
- 03支持组件及变体作用域
- 04写入前解析页面与样式目标
- 05验证交互并排查拒绝原因
适用场景
- 01为页面元素设置点击淡入效果
- 02制作滚动揭示或视差动画
- 03为组件变体配置交互
- 04检查并更新已有交互
MCP 相关
通过 Webflow MCP 的 `data_interactions_tool` 读取和写入交互,并配合站点、页面及样式工具解析目标。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ webflow/ webflow-skills --skill webflow-mcp:interactions安装后在 Claude Code 中输入/webflow-mcp:interactions确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/actions-and-properties.mdcapabilities.generated.mdconditional-playback.mdenvelope-and-targets.mdgated-capabilities.mdindex.mdlimits-and-budgets.mdpanel-traps.mdrejects-index.mdtimelines-and-groups.mdtrigger-click.mdtrigger-custom.mdtrigger-hover.mdtrigger-load.mdtrigger-mouse-move.mdtrigger-scroll.mdupdating-interactions.md详细介绍
交互
通过 Webflow MCP 创建和编辑 IX3 交互(GSAP 动画)。
重要提示
所有操作始终使用 Webflow MCP 工具:
- 在调用任何其他工具之前,使用 Webflow MCP 的
webflow_guide_tool获取最佳实践。该工具包含一段简短的 Interactions 说明,其中提到data_interactions_tool,并提示你在写入前调用guide操作。参数结构来自guide操作,而非这段简短说明。 - 使用 Webflow MCP 的
data_sites_tool,通过list_sites操作确定目标站点 - 使用 Webflow MCP 的
data_pages_tool,通过list_pages操作按名称或 slug 查找目标页面 - 使用 Webflow MCP 的
data_interactions_tool执行 guide / list / get / create / update / delete 操作 - 使用 Webflow MCP 的
data_style_tool,在定位wf:class前解析 class style-block id - 不要使用任何其他工具或方法执行 Webflow 交互的 CRUD 操作
- 所有工具调用都必须包含必填的
context参数(15–25 个词,使用第三人称视角) - 无需 Designer 或 MCP Bridge。
data_interactions_tool是无头工具,与data_element_tool相同。之后可以使用 Designer 检查 Interactions 面板或预览。不要要求用户打开 Bridge 应用来列出或写入交互。 - 如果工具列表中没有
data_interactions_tool,且会话中包含site/interactions/interactions.ix3.json,则编辑该文件。否则使用data_interactions_tool。此技能使用 MCP 路径。
工具接口
- 权限范围:
pages:read/pages:write - 复合工具:
data_interactions_tool - 操作:
guide、list_interactions、get_interaction、create_interaction、update_interaction、delete_interaction siteId和pageId是顶层工具参数(页面上下文 / 创建时记录信息)。它们不在create_interaction参数中。guide是实时的。 调用data_interactions_tool并指定guide操作,或读取webflow://guides/interactions。两种方式获取的 Markdown 相同。参数结构以其为准。本技能介绍围绕这些结构的工作流;references/中的内容是 guide 未涵盖的特殊情况。create_interaction参数:name(必填)、scope(可选,默认为 site)、triggers(必填数组)、timelines(必填数组)、timelineDefaults(可选)、conditionalPlayback(可选)- 支持组件和变体范围。 创建和更新时可使用
{type:"component", componentId, variants?}。variants包含来自data_component_variants_tool的变体选项 id——省略该参数或传入[]都表示适用于所有变体。不要设置libraryProfileId;设置后会将交互标记为库所有。不存在的组件或变体 id 会被拒绝。请参阅 references/envelope-and-targets.md。
说明
阶段 1:发现
- 首先调用
webflow_guide_tool——始终将其作为第一个 MCP 工具调用。它涵盖通用 MCP 约定,并包含一个简短的交互部分 (data_interactions_tool、其操作、顶层siteId和pageId)。 该部分不是载荷约定。请从data_interactions_tool→guide获取合法格式。 - 获取站点:使用
list_sites调用data_sites_tool。如果只有一个站点,就使用该站点。 - 获取页面:使用
list_pages调用data_pages_tool。每次调用data_interactions_tool时,都需要将该页面的 ID 作为顶层pageId。 - 确认工具已注册:如果
data_interactions_tool未注册,请停止并告知用户,不要尝试绕过。不要将缺少工具视为缺少 Bridge 会话,也不要让用户打开 Designer 或 MCP Bridge。如果会话中包含site/interactions/interactions.ix3.json,请改为编辑该文件。 - 列表为空并不能证明站点没有交互。
data_interactions_tool只读取 IX3。使用 IX2 的站点会返回{items: [], total: 0},且不会报错,因此无法区分该站点没有交互的情况。读取结果为空时,绝不要断定站点没有动态效果:告诉用户读取到了什么,并让他们检查 Interactions 面板底部的版本选择器。在 IX2 站点上仍可成功创建交互,此后站点会同时运行两种运行时。
阶段 2:读取所构建内容的约定
- 调用
data_interactions_tool→guide获取合法载荷格式。 如果要构建的触发器所需的特定功能不在指南中,请阅读references/中对应的文件。请参阅下方的参考资料映射。在本文件中五个内联示例之外的任何内容进行首次写入之前,务必完成此步骤。 - 不要自行编造
+=/</>timing.position字符串或{reducedMotion:"skip"}。timing.duration使用纯数字时,单位为秒(0.4,而非400)。
参考索引
| 用户询问内容 | 阅读 |
|---|---|
| 点击 | references/trigger-click.md |
| 悬停、鼠标进入/离开 | references/trigger-hover.md |
| 页面加载 | references/trigger-load.md |
| 滚动、擦动、视差 | references/trigger-scroll.md |
| 鼠标移动、光标跟随 | references/trigger-mouse-move.md |
| 自定义 JS 事件 | references/trigger-custom.md |
| 导航栏、下拉菜单、条件 | references/gated-capabilities.md |
| Variable、Rive、Animate Rive | data_interactions_tool action guide |
| 信封、ID、作用域、目标、筛选条件 | references/envelope-and-targets.md |
属性、值、tt、时序、splitText | references/actions-and-properties.md |
| 角色、组、百分比画布 | references/timelines-and-groups.md |
| 减少动态效果、断点播放 | references/conditional-playback.md |
| 编辑现有交互 | references/updating-interactions.md |
| 大小和数量限制 | references/limits-and-budgets.md |
| 写入成功,但用户无法编辑 | references/panel-traps.md |
| 写入成功,但没有任何动画 | references/rejects-index.md → "nothing to decode" |
| 解读拒绝消息 | references/rejects-index.md |
| 所有可用触发器和属性 | references/capabilities.generated.md |
如果不确定,请从 references/index.md 开始。该文档还会
说明强制执行标签,尤其是 [PANEL-TRAP],它表示写入会成功,但用户将无法在
Designer 中编辑内容;以及 [LEGACY-OK-ON-UPDATE],它表示必须原样传递已存储的值,
而不是“修正”它。
第 3 阶段:规划(任何写入操作之前)
- 解析类目标:优先使用
data_style_tool中的样式块 ID 数组;也接受类名字符串。使用query_styles确认该类解析后恰好对应一个样式块——如果某个名称被多个组合链重复用作末级类名,就会返回多个结果,且每个结果都指向不同元素。如果元素需要自己的类,请使用create_style创建,并为其指定你打算应用的完整parent_style_names链,然后使用data_element_tool的set_style应用同一条链;如果该链尚不存在对应的样式块,则会被拒绝 - 根据第 2 阶段阅读的参考资料规划负载:对象格式、合法的触发器/目标、新的操作 ID
- 在创建/更新/删除之前,明确征求确认:
- “要我创建这个点击淡入效果吗?”
- “写入此交互之前:[计划]。请确认后继续。”
阶段 4:写入(仅在确认后)
- 在
data_interactions_tool上调用create_interaction或update_interaction update_interaction是部分更新:省略的字段保持不变,提供的值会替换原值,null会清除timelineDefaults/conditionalPlayback。发送timelines时会完全替换原有内容。
阶段 5:验证
- 使用返回的 id 调用
get_interaction - 报告创建或更新的内容
- **如果被拒绝:**阅读错误信息,在 references/rejects-index.md 中查找并修正 payload。不要臆造 GSAP 位置运算符,也不要抱着侥幸心理用相同结构重试,所有此类拒绝都是确定性的。
- 写入成功并不能证明动画会运行。 有几种合法的 payload 虽然能保存、读回时字节完全相同,却不会产生任何效果:To 补间上的
[from, to]对、并非同一组合链的类名数组、没有目标的鼠标移动触发器、首次点击时的control: "reverse"。get_interaction无法检测这些情况。报告成功前,请根据下方的指南检查 payload;如果用户表示没有任何效果,请先查看 references/rejects-index.md 中的“没有内容可解码时”,而不是重写 interaction。
示例
将 STYLE_BLOCK_ID 替换为样式块 id。每次操作都要生成一个全新的唯一 id。
示例 1:点击淡入
用户:“添加点击淡入 interaction”
- 调用
webflow_guide_tool list_sites→list_pages→ 通过data_style_tool解析STYLE_BLOCK_ID- 调用
data_interactions_tool→guide,了解 payload 结构。仅当指南未包含所需的固定模式时,才阅读 references/trigger-click.md。 - 提出计划并等待确认
- 确认后,调用
create_interaction:
{
"name": "Click fade",
"triggers": [
{
"extensionKey": "wf:click",
"config": { "control": "play" },
"target": { "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
}
],
"timelines": [
{
"actions": [
{
"id": "act-click-fade",
"name": "Fade",
"tt": 2,
"timing": { "duration": 0.4 },
"properties": { "wf:transform": { "opacity": ["0%", "100%"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
}
]
}
- 调用
get_interaction进行验证
FromTo 会在静止状态下保持起始状态,因此该元素会呈现为**opacity: 0,直到首次点击**——这一点已在已发布的页面上测得。这就是淡入的含义,但请告知用户,因为看不见的元素会让人觉得写入失败,而不是 interaction 正常工作。如果元素在点击前就应该可见,请为静止值本身可见的属性添加动画(例如 x 偏移或颜色),或者使用 To。
示例 2:页面加载时淡入
用户:“页面加载时让此部分淡入”
发现流程、参考文档阅读和确认步骤相同。Load 不包含触发器目标。操作目标不得为 wf:trigger-only。
{
"name": "Load fade",
"triggers": [{ "extensionKey": "wf:load", "config": { "control": "play" } }],
"timelines": [
{
"actions": [
{
"id": "act-load-fade",
"name": "Fade",
"tt": 2,
"timing": { "duration": 0.4 },
"properties": { "wf:transform": { "opacity": ["0%", "100%"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
}
]
}
示例 3:滚动擦除
用户:“用户滚动时擦除淡入效果”
Scroll 是独立的。擦除需要数值型 scrub(不能是 true)、带 canvasDuration: 1 且不带角色的时间线,以及与该 canvas 时长相等的操作 timing.duration,使补间贯穿整个过程。省略播放设置中的 control / delay / jump / speed。省略 scrub 会在越过触发范围时播放一次,而不是进行擦除。
{
"name": "Scroll scrub fade",
"triggers": [
{
"extensionKey": "wf:scroll",
"config": {
"scrollTriggerConfig": {
"start": "top bottom",
"end": "top 10%",
"scrub": 0.3
}
},
"target": { "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
}
],
"timelines": [
{
"canvasDuration": 1,
"actions": [
{
"id": "act-scroll-scrub",
"name": "Fade",
"tt": 2,
"timing": { "duration": 1 },
"properties": {
"wf:transform": { "opacity": ["0%", "100%"], "xPercent": [-40, 0] }
},
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
}
]
}
仅播放一次的变体(这是显示效果,不是擦除)。移除 scrub 和 canvasDuration,将 timing.duration 改回秒数,并且发送 enter: "play"。主机在创建时以及替换触发器时,会为缺失的切换键写入默认值(enter: "play",其余三个为 "none"),并保留显式的 "none",因此缺失的值会被修正,而不会保持无效。仍然要发送 enter:这是面板写入的值,在发布流程的两端都正确。
{
"extensionKey": "wf:scroll",
"config": {
"scrollTriggerConfig": {
"start": "top 90%",
"end": "bottom 15%",
"enter": "play",
"leaveBack": "reset"
}
},
"target": { "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
}
leaveBack: "reset" 是可选的;用户向上滚动时,它会重新播放显示效果。显示效果本身请使用 From(tt: 1),这样元素初始时会隐藏。
示例 4:鼠标悬停进入/离开
用户:“鼠标悬停时淡入,移开时淡出”
使用角色形式:一个 wf:hover 触发器,设置 multiTimeline: true,并通过 triggerMetadata 将两个时间线标记为 mouseEnter 和 mouseLeave。角色必须与这些字符串完全一致,且每个时间线的角色必须唯一。不要将其与旧版悬停 type / hover / custom 混用。
{
"name": "Hover fade",
"triggers": [
{
"extensionKey": "wf:hover",
"config": { "pluginConfig": { "multiTimeline": true } },
"target": { "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
}
],
"timelines": [
{
"triggerMetadata": { "role": "mouseEnter" },
"actions": [
{
"id": "act-hover-in",
"name": "Fade in",
"tt": 2,
"timing": { "duration": 0.3 },
"properties": { "wf:transform": { "opacity": ["0%", "100%"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
},
{
"triggerMetadata": { "role": "mouseLeave" },
"actions": [
{
"id": "act-hover-out",
"name": "Fade out",
"tt": 2,
"timing": { "duration": 0.3 },
"properties": { "wf:transform": { "opacity": ["100%", "0%"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
}
]
}
现在不再需要任何注意事项。 此技能的早期版本曾提醒,面板不会为任一操作组提供移除按钮。编写该版本时确实如此,而 webflow#117817 已于 2026-08-19 修复此问题——当存在 Hover 所属项时,这种结构现在会显示 Remove。之后已在 Designer 中确认:通过 MCP 编写的角色形式悬停动画不仅能正常播放,两个 Actions 组也都显示删除控件,与拆分形式相同。
面板更倾向的双触发器拆分形式可以编写,但判别字段是 config.pluginConfig.eventMode,其取值为 'enter' / 'leave'——不是 mouseEnter/mouseLeave,也不在 config 层级,否则会被静默丢弃(参见第一节 Guidelines)。eventMode 旁边还必须有布尔值 multiTimeline,且交互包含两个或更多操作组时,需要设置 control: "play"。
拆分形式使用 multiTimeline: false,而不是 true。 运行时会根据该标记选择分支:true 是双组角色模式,会触发 mouseEnter / mouseLeave 角色回调,也就是上面的角色形式;false 是单组模式,每个触发器驱动自己的组,并由 eventMode 决定绑定哪个事件(bindEnter = eventMode !== 'leave')。拆分形式通过 groupId 路由,因此需要使用 false。
"triggers": [
{
"extensionKey": "wf:hover",
"config": {
"control": "play",
"assignedGroupId": "grp-in",
"pluginConfig": { "multiTimeline": false, "eventMode": "enter" }
},
"target": { "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
},
{
"extensionKey": "wf:hover",
"config": {
"control": "play",
"assignedGroupId": "grp-out",
"pluginConfig": { "multiTimeline": false, "eventMode": "leave" }
},
"target": { "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
}
]
两个时间线分别设置 groupId: "grp-in" / "grp-out"。groupId 会保留到宿主;如果 assignedGroupId 与任何时间线组都不匹配,则会被拒绝,而不是作为无效值存储。请注意,多时间线悬停会先按时间线角色路由,再按组路由。
两种形式都能正常工作,而且都不会带来可衡量的用户成本。 在 Designer 中并排验证过,两种形式都通过 MCP 针对同一元素编写:播放效果完全相同(进入时 y 从 0 → -24,离开时从 -24 → 0),且两种形式的两个操作组都显示删除控件。
因此,这只是一个较弱的偏好:优先使用上面的拆分形式,因为这是面板自身写入的结构,用户看到的也正是他们手动创建的结构。角色形式(示例 4)同样有效——面板不会写入这种形式,但可以编辑它。
此技能的早期版本曾告诉 Agent,需要在播放保真度和缺少移除按钮之间权衡。实际上不存在这种权衡。参见 references/trigger-hover.md。
仅需进入动画时,发送一个触发器,设置 pluginConfig: { "multiTimeline": false },并附带单个时间线。
如果用户只需要进入动画,发送一个触发器,设置 pluginConfig: { "multiTimeline": false }——省略此键会退回旧版编辑器。无论哪种情况,面板都可能不会在单时间线悬停设置中提供“Add separate hover out”,因此只要用户需要两个方向,就应编写上面的完整拆分形式。
示例 5:鼠标移动
用户:“用光标移动此元素”
鼠标移动是独立的。即使没有目标也能通过验证,但没有目标时永远不会触发,因此务必设置目标——页面范围跟踪使用 wf:viewport(value: ""),或使用 wf:inst / wf:class 限定范围。每个时间线都需要一个来自 mouseX / mouseY / interval 的唯一角色。省略播放控制项 control / delay / jump / speed。参见 references/trigger-mouse-move.md。
{
"name": "Mouse follow",
"triggers": [
{
"extensionKey": "wf:mouse-move",
"config": {},
"target": { "extensionKey": "wf:viewport", "value": "" }
}
],
"timelines": [
{
"triggerMetadata": { "role": "mouseX" },
"actions": [
{
"id": "act-mouse-x",
"name": "Follow X",
"tt": 2,
"timing": { "duration": 0.4 },
"properties": { "wf:transform": { "x": ["0px", "40px"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
},
{
"triggerMetadata": { "role": "mouseY" },
"actions": [
{
"id": "act-mouse-y",
"name": "Follow Y",
"tt": 2,
"timing": { "duration": 0.4 },
"properties": { "wf:transform": { "y": ["0px", "40px"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
}
]
}
示例 6:自定义 JS 事件
用户:“当我的脚本触发事件时播放这个动画”
有三个不明显但都必须满足的要求:
- 触发器目标必须是
wf:body(value: "")。任何其他键都会被拒绝。 - 事件名称位于
config.pluginConfig.eventName。如果放在config层级,它会被静默丢弃,触发器也永远不会触发。 - 它不是 DOM
CustomEvent。分发这种事件不会产生任何效果。网站通过 IX3 模块触发它。
{
"name": "Custom event flash",
"triggers": [
{
"extensionKey": "wf:custom",
"config": {
"control": "play",
"pluginConfig": { "eventName": "my-event" }
},
"target": { "extensionKey": "wf:body", "value": "" }
}
],
"timelines": [
{
"actions": [
{
"id": "act-custom-flash",
"name": "Flash",
"tt": 2,
"timing": { "duration": 0.4 },
"properties": { "wf:transform": { "opacity": ["100%", "20%"] } },
"targets": [
{ "extensionKey": "wf:class", "value": ["STYLE_BLOCK_ID"] }
]
}
]
}
]
}
告诉用户如何触发它。 否则这部分看起来就像是坏掉了:
const wfIx = Webflow.require('ix3');
wfIx.emit('my-event');
emit 是唯一的触发方法 — 模块公开了 {getInstance, emit, destroy, ready, instance}。没有 trigger、dispatch 或 fire。非字符串或仅包含空白字符的 eventName 会在写入边界被拒绝;为了向后兼容,缺失的 eventName 会被接受,但会产生一个永远不会触发的触发器。参见 references/trigger-custom.md。
指南
首先阅读这一条:未知的 config 键会被静默丢弃
triggerConfigSchema 是一个非严格的 Zod 对象,因此,放在 trigger.config 上的任何未声明字段都会被丢弃,且不会报错。 写入会成功,响应看起来也正常,get_interaction 往返后字节完全一致,但你的字段已经不见了。
已声明的 config 字段:control、delay、jump、speed、controlType、scrollTriggerConfig、pluginConfig、assignedGroupId、assignedTimelineRole、conditionalLogic。
所有插件专属内容都必须放在 config.pluginConfig 中 — eventName、eventMode、multiTimeline、smoothness 以及插件定义的其他内容。把其中任何一项放在 config 层级,是这个界面上代价最高的错误,因为没有任何提示会告诉你:
// WRONG — silently discarded, trigger never fires
"config": { "eventName": "my-event" }
// RIGHT
"config": { "pluginConfig": { "eventName": "my-event" } }
一次内部实测因此丢失了三个字段(eventName、eventMode 和拼写错误的 easing),并把它们全都误报为缺失的 API 功能。如果你发送的字段没有出现在响应中,在断定它不受支持之前,先假设你放错了位置。
缓动:timing.ease 是数字,不是字符串
ease 可以是内置缓动表中的非负整数索引,也可以是高级缓动对象。字符串会被拒绝,并只返回一个无法说明具体原因的 Invalid input。
| 索引 | 名称 | 索引 | 名称 | 索引 | 名称 | ||
|---|---|---|---|---|---|---|---|
| 0 | none(线性) | 11 | power4.out | 22 | elastic.in | ||
| 1 | power1.in | 12 | power4.inOut | 23 | elastic.out | ||
| 2 | power1.out | 13 | back.in | 24 | elastic.inOut | ||
| 3 | power1.inOut | 14 | back.out | 25 | expo.in | ||
| 4 | power2.in | 15 | back.inOut | 26 | expo.out | ||
| 5 | power2.out | 16 | bounce.in | 27 | expo.inOut | ||
| 6 | power2.inOut | 17 | bounce.out | 28 | sine.in | ||
| 7 | power3.in | 18 | bounce.inOut | 29 | sine.out | ||
| 8 | power3.out | 19 | circ.in | 30 | sine.inOut | ||
| 9 | power3.inOut | 20 | circ.out | ||||
| 10 | power4.in | 21 | circ.inOut |
面板上的“Power 1 out”对应 2;“Linear”对应 0。
"timing": { "duration": 0.4, "ease": 2 }
高级缓动是由 type 区分的对象。直接写入即可 — 写入边界没有任何防护会拒绝高级缓动:
back {curve,power}、elastic {curve,amplitude,period}、steps {stepCount}、
rough {templateCurve,points,strength,taper,randomizePoints,clampPoints}、
slowMo {linearRatio,power,yoyoMode}、expoScale {startingScale,endingScale,templateCurve}、
customWiggle {wiggles,wiggleType}、customBounce {strength,squash,endAtStart}、
customEase {bezierCurve}。curve 可取 in / out / inOut。
"timing": { "duration": 0.4, "ease": { "type": "back", "curve": "out", "power": 1.7 } }
timing.stagger.ease 采用相同的结构。不接受包含 4 个数字的贝塞尔数组 — 请使用 {type: "customEase", bezierCurve: "..."}。
类名变更:wf:class
一个名为 class 的属性,仅支持设置(tt: 3):
"tt": 3,
"properties": {
"wf:class": {
"class": { "operation": "addClass", "selectors": ["STYLE_BLOCK_ID"] }
}
}
operation 可以是 addClass / removeClass / toggleClass。裸数组、裸字符串或 {add: [...]} 均会被拒绝。
stagger 是一个对象
{amount?, axis?, each?, ease?, from?, grid?}。裸数字会被拒绝。
each/amount— 秒,或"250ms"字符串axis—'x'/'y'ease— 与timing.ease相同的索引或对象形式from—'start' | 'center' | 'end' | 'edges' | 'random'、数字或nullgrid—'auto'、[columns, rows]数字对或null。'none'会被拒绝。
"timing": { "duration": 0.5, "stagger": { "each": 0.05, "from": "start", "grid": [2, 2] } }
filterContext.relationship 枚举
none · within · direct-child-of · contains · direct-parent-of ·
next-to · next-sibling-of · prev-sibling-of。类似 CSS 的猜测值(如
descendants)会被拒绝。
目标值形式
| 键 | 值 |
|---|---|
wf:class | 样式块 ID 数组,或类名字符串 |
wf:inst | [componentId, elementId] — 对于页面级元素,componentId 槽位是页面 ID。哪种形式合法取决于 scope:组件范围使用组件定义 ID,站点和页面范围要求页面 ID |
wf:selector | CSS 选择器字符串,例如 "body"。这是在操作中定位 body 的方式;wf:body 仅用于触发器上下文 |
wf:body, wf:viewport | "" — 仅限触发器目标 |
wf:any-element | "*" — 不能是 ""。仅限操作目标 |
wf:trigger-only, wf:trigger-only-parent | ""。仅限操作目标 |
wf:attribute | 属性名称或完整选择器。"data-thing" 会存储为 [data-thing],并匹配所有带有该属性的元素;如果多个元素共享该属性,请传入 '[data-thing="x"]' |
wf:id 不是目标键。 在所有上下文中都会被拒绝,并返回
target type "wf:id" is not offered by the Designer (shouldShow: false in all contexts). Use wf:selector, wf:class, or wf:inst instead.
要按 DOM ID 定位,请将 wf:selector 与 '#the-id' 配合使用。可编写的目标键有七个,而不是八个。
wf:any-element 是唯一值为通配符而非占位符的键。
另外三个仅限操作的键看似可以互换,实际上并非如此:在
wf:any-element 上使用 "" 会被拒绝,并返回 "wf:any-element" value must be "*"。
由于这些目标通常按每个 filterContext 关系各编写一行,填错会导致整批内容失效,而不只是一行。
wf:inst 和 wf:trigger-only 不接受有效的 filterContext;带有
relationship: 'none' 的占位值则可以。
上限
每个交互的触发器 20 · 每个交互的时间线 5 · 每条时间线的操作 200 · 每个操作的目标 20 · canvasDuration ≤ 12s · groupId 1–64 个字符 · 随机数组集合 2–12 个值 · IX3 值总计 65,536 字节。
MCP 在创建和更新时都会通过 Zod .max() 应用这些上限。 对于已存储且超出上限的交互,Designer 主机会提高上限;MCP 不会。因此,读取一条有 200 多个操作的时间线并重新提交,仅修改其中一个操作,通过 MCP 提交会失败。
工具架构说明勘误
data_interactions_tool JSON Schema 目前将
conditionalPlayback 描述为 {"type": "object"},示例则为
{reducedMotion:"skip"}。两者都不正确。 服务端验证器要求它是一个数组;请参阅下文的 conditionalPlayback 条目。对于该字段,请以此技能文档为准,而非工具说明。
另请注意,工具系列中 siteId/pageId 的位置各不相同:data_interactions_tool、data_style_tool 和 data_element_tool 使用顶层字段;data_pages_tool、data_assets_tool 和 data_agent_instructions_tool 则在操作内部嵌套使用 site_id。
其他所有内容
- 仅支持对象格式:
{ extensionKey, value, filterContext? }。嵌套的filterBy是一个 二元组["wf:class", ["STYLE_BLOCK_ID"]],而不是对象。 - ID: 创建时省略时间线
id(由宿主生成)。每个动作都需要一个全新的唯一id。不要发送触发器id。 - 不透明度位于
wf:transform下(例如["0%","100%"]),不能放在wf:style下。 - 类目标: 优先使用样式块 ID 数组;也接受类名字符串。
wf:classID 数组会被 AND 合并为一个复合选择器,而不是一组备选项:["a","b"]会解析为.a.b。宿主会为你展开组合类的父类,因此只需传入唯一的叶子 ID。来自两条不同组合类链的 ID 会生成没有元素匹配的选择器,目标将解析为空,且不会报错。一个目标只能对应一个类或一条组合类链——要为两组不同的元素添加动画,请使用两个动作。- 在多条链中重复使用的叶子类名(
.btn.lift和.btn.cta.lift是两个都名为lift的样式块)作为名称字符串传入时会被拒绝,作为 ID 传入时则会匹配错误的元素。请为每个元素指定各自的类。 - 只发送“交互”面板可以创建的内容。 Worker Zod 校验较宽松;不合法的结构会在 Designer 写入路径中失败。
conditionalPlayback是由{ type, behavior }组成的数组(也支持断点形式),不是{reducedMotion:"skip"}。示例:[{ "type": "prefers-reduced-motion", "behavior": "dont-animate" }]。- Load 省略触发器
target和pluginConfig。 - Scroll 和 mouse-move 省略播放配置中的
control/delay/jump/speed。 - 没有
scrub的 Scroll 必须设置enter: "play"。 持久化时,宿主会为缺失的切换键写入默认值(enter: "play",其余为"none"),并保留显式设置的"none";但请自行发送enter:面板始终会写入全部四个切换项,无论该默认值已推广到何处,显式设置都是正确的。scrollTriggerConfig本身不会自动生成——缺失或为 null 都会被拒绝。 - Scroll scrub: 发送数值型
scrub、canvasDuration: 1和动作timing.duration: 1。极短的时长只占滚动范围的约 1%,看起来就像什么也没发生。 control: "reverse"在第一次点击时不起作用(播放头从 0 开始)。如需让用户在 Preview 中尝试反向播放,请使用togglePlayReverse(pluginConfig.click省略或设为"each")。- To 补间只读取
to槽位。 在tt: 0时使用[from, to]会有一半被丢弃,GSAP 会从元素的实时值开始动画,因此未缩放元素上的scale: [0.55, 1.15]会从 1 变为 1.15,而不透明元素上的opacity: ["20%","100%"]则完全不起作用。动画需要起始值时,请使用tt: 2。 - 不要在自身 from 状态会收缩的元素上设置 click 或 hover 触发器(
scaleX: 0、width: 0)。这样会导致没有可点击的区域。请将触发器设在父元素上,并为子元素添加动画。 - 可以创建分组时间线。 MCP 时间线输入支持
groupId(1–64 个字符);click 或 hover 触发器上的config.assignedGroupId可将其路由到该分组。若assignedGroupId与任何时间线的groupId都不匹配,系统会拒绝该配置,而不是将其作为无效值存储,因此不匹配会显示为错误,而非无效交互。Load、scroll 和 continuous 触发器会忽略assignedGroupId。 wf:navbar和wf:dropdown不支持创建,wf:focus、wf:blur和wf:change也不支持。 守卫不接受标志或会话参数,因此无论调用者的 Statsig 状态如何,这一限制都适用——没有任何标志能为你启用这些功能。Navbar 和 dropdown 已在 Designer 中注册,但被排除在默认能力集合之外;focus、blur 和 change 则完全没有 Designer 架构。请告知用户该触发器不可用,而不要尝试写入。参见 references/gated-capabilities.md。wf:variable、wf:rive和wf:animate-rive支持创建;其结构请调用guide查询。timing.position不支持 GSAP 位置运算符(+=、<、>)。请使用有限数值(秒)或'500ms'。- 时长单位为秒。
timing.duration: 0.4表示 400ms。400表示 400 秒。如果习惯用毫秒,请使用"400ms"。 - From / FromTo(
tt: 1/2)会保持在 from 状态,直到触发器触发。 元素在静止时应可见的情况,优先使用 To(tt: 0或省略)。 splitText需要目标元素已包含文本——可以是目标自身,也可以是其后代。限制不在于元素类型:文本作为子节点的 Block / Div 可以正常拆分(已在发布页面上验证:包含 "Card one" 的.pg-cardBlock 生成了两个gsap_split_wordspan)。会静默失败的是内部完全没有文本的目标——保存后不会发生任何动画。- 角色位于
timelines[].triggerMetadata中,而不是触发器上。每条 timeline 中的 Mouse-move 都需要唯一的mouseX/mouseY/interval。 - 被拒绝时:读取错误,并在 references/rejects-index.md 中查找;不要臆造字段。
安装
npx -y skills add webflow/webflow-skills --skill 'webflow-mcp:interactions' --yes
可选参数 --agent claude-code(或 cursor)。调用方需要 pages:read / pages:write 权限范围。