mcpField
W
webflow-mcp:interactions

webflow-mcp:interactions

github.com/webflow设计与 UI133231 次安装1 天前更新
代码仓库

关于 webflow-mcp:interactions

此技能指导 Agent 通过 Webflow MCP 创建和管理 IX3(GSAP)交互,面向处理 Webflow 页面动画的工作流。它强调先查阅实时指南、规划并确认写入,再验证结果。

主要功能

  1. 01创建、更新和删除 IX3 交互
  2. 02管理点击、悬停和滚动触发器
  3. 03支持组件及变体作用域
  4. 04写入前解析页面与样式目标
  5. 05验证交互并排查拒绝原因

适用场景

  1. 01为页面元素设置点击淡入效果
  2. 02制作滚动揭示或视差动画
  3. 03为组件变体配置交互
  4. 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.md
capabilities.generated.md
conditional-playback.md
envelope-and-targets.md
gated-capabilities.md
index.md
limits-and-budgets.md
panel-traps.md
rejects-index.md
timelines-and-groups.md
trigger-click.md
trigger-custom.md
trigger-hover.md
trigger-load.md
trigger-mouse-move.md
trigger-scroll.md
updating-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:发现
  1. 首先调用 webflow_guide_tool——始终将其作为第一个 MCP 工具调用。它涵盖通用 MCP 约定,并包含一个简短的交互部分 (data_interactions_tool、其操作、顶层 siteId 和 pageId)。 该部分不是载荷约定。请从 data_interactions_tool → guide 获取合法格式。
  2. 获取站点:使用 list_sites 调用 data_sites_tool。如果只有一个站点,就使用该站点。
  3. 获取页面:使用 list_pages 调用 data_pages_tool。每次调用 data_interactions_tool 时,都需要将该页面的 ID 作为顶层 pageId。
  4. 确认工具已注册:如果 data_interactions_tool 未注册,请停止并告知用户,不要尝试绕过。不要将缺少工具视为缺少 Bridge 会话,也不要让用户打开 Designer 或 MCP Bridge。如果会话中包含 site/interactions/interactions.ix3.json,请改为编辑该文件。
  5. 列表为空并不能证明站点没有交互。 data_interactions_tool 只读取 IX3。使用 IX2 的站点会返回 {items: [], total: 0},且不会报错,因此无法区分该站点没有交互的情况。读取结果为空时,绝不要断定站点没有动态效果:告诉用户读取到了什么,并让他们检查 Interactions 面板底部的版本选择器。在 IX2 站点上仍可成功创建交互,此后站点会同时运行两种运行时。
阶段 2:读取所构建内容的约定
  1. 调用 data_interactions_tool → guide 获取合法载荷格式。 如果要构建的触发器所需的特定功能不在指南中,请阅读 references/ 中对应的文件。请参阅下方的参考资料映射。在本文件中五个内联示例之外的任何内容进行首次写入之前,务必完成此步骤。
  2. 不要自行编造 += / < / > 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 Rivedata_interactions_tool action guide
信封、ID、作用域、目标、筛选条件references/envelope-and-targets.md
属性、值、tt、时序、splitTextreferences/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 阶段:规划(任何写入操作之前)
  1. 解析类目标:优先使用 data_style_tool 中的样式块 ID 数组;也接受类名字符串。使用 query_styles 确认该类解析后恰好对应一个样式块——如果某个名称被多个组合链重复用作末级类名,就会返回多个结果,且每个结果都指向不同元素。如果元素需要自己的类,请使用 create_style 创建,并为其指定你打算应用的完整 parent_style_names 链,然后使用 data_element_tool 的 set_style 应用同一条链;如果该链尚不存在对应的样式块,则会被拒绝
  2. 根据第 2 阶段阅读的参考资料规划负载:对象格式、合法的触发器/目标、新的操作 ID
  3. 在创建/更新/删除之前,明确征求确认:
    • “要我创建这个点击淡入效果吗?”
    • “写入此交互之前:[计划]。请确认后继续。”
阶段 4:写入(仅在确认后)
  1. 在 data_interactions_tool 上调用 create_interaction 或 update_interaction
  2. update_interaction 是部分更新:省略的字段保持不变,提供的值会替换原值,null 会清除 timelineDefaults / conditionalPlayback。发送 timelines 时会完全替换原有内容。
阶段 5:验证
  1. 使用返回的 id 调用 get_interaction
  2. 报告创建或更新的内容
  3. **如果被拒绝:**阅读错误信息,在 references/rejects-index.md 中查找并修正 payload。不要臆造 GSAP 位置运算符,也不要抱着侥幸心理用相同结构重试,所有此类拒绝都是确定性的。
  4. 写入成功并不能证明动画会运行。 有几种合法的 payload 虽然能保存、读回时字节完全相同,却不会产生任何效果:To 补间上的 [from, to] 对、并非同一组合链的类名数组、没有目标的鼠标移动触发器、首次点击时的 control: "reverse"。get_interaction 无法检测这些情况。报告成功前,请根据下方的指南检查 payload;如果用户表示没有任何效果,请先查看 references/rejects-index.md 中的“没有内容可解码时”,而不是重写 interaction。

示例

将 STYLE_BLOCK_ID 替换为样式块 id。每次操作都要生成一个全新的唯一 id。

示例 1:点击淡入

用户:“添加点击淡入 interaction”

  1. 调用 webflow_guide_tool
  2. list_sites → list_pages → 通过 data_style_tool 解析 STYLE_BLOCK_ID
  3. 调用 data_interactions_tool → guide,了解 payload 结构。仅当指南未包含所需的固定模式时,才阅读 references/trigger-click.md。
  4. 提出计划并等待确认
  5. 确认后,调用 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"] }
          ]
        }
      ]
    }
  ]
}
  1. 调用 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 事件

用户:“当我的脚本触发事件时播放这个动画”

有三个不明显但都必须满足的要求:

  1. 触发器目标必须是 wf:body(value: "")。任何其他键都会被拒绝。
  2. 事件名称位于 config.pluginConfig.eventName。如果放在 config 层级,它会被静默丢弃,触发器也永远不会触发。
  3. 它不是 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。

索引名称索引名称索引名称
0none(线性)11power4.out22elastic.in
1power1.in12power4.inOut23elastic.out
2power1.out13back.in24elastic.inOut
3power1.inOut14back.out25expo.in
4power2.in15back.inOut26expo.out
5power2.out16bounce.in27expo.inOut
6power2.inOut17bounce.out28sine.in
7power3.in18bounce.inOut29sine.out
8power3.out19circ.in30sine.inOut
9power3.inOut20circ.out
10power4.in21circ.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'、数字或 null
  • grid — '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:selectorCSS 选择器字符串,例如 "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:class ID 数组会被 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-card Block 生成了两个 gsap_split_word span)。会静默失败的是内部完全没有文本的目标——保存后不会发生任何动画。
  • 角色位于 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 权限范围。

评论

支持 Markdown