news 2026/10/1 2:06:17

Motion 中 `display: “none“` 动画失效问题的根因分析与 v11.2.0 修复方案(issue-2563 复盘)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Motion 中 `display: “none“` 动画失效问题的根因分析与 v11.2.0 修复方案(issue-2563 复盘)
  • 前端
  • UI组件

【免费下载链接】motion

A modern animation library for React and JavaScript

项目地址:https://gitcode.com/GitHub_Trending/mo/motion
点击查看免费下载

本文以仓库内的 plans/issues/issue-2563.md 计划文档为主体,深入复盘 Motion 动画库中animate(el, { opacity: 0, display: "none" })在useAnimate/独立animate()路径下动画结束后display停留在旧值的问题:包括其被定位为与 issue-2656 同根因的回归、为何只有"none"关键字受影响、mixVisibility的修复实现,以及如何通过回归测试与 GitHub CLI 完成验证和关闭的完整流程。读者读完可以掌握 Motion 对离散值(display/visibility)动画的底层处理机制、修复后的预期行为,以及仓库中这套"问题计划(plan)"的落地执行方法。

一、问题现象:useAnimate中display: "none"无法被设置

该问题于 2024-03-16 报告,针对当时的 11.0.14 版本。复现代码非常简洁:

animate(el, { opacity: 0, display: "none" }, { duration: 0.7 })

实际表现是:opacity正常按 0.7s 淡出,但display始终停留在动画开始前的旧值,元素在视觉淡出后依然占据布局。而同一动画中如果使用其他display关键字(如inline、contents、flex),则一切正常。issue 下共有 8 条评论确认了同样的症状,并验证了transitionEnd: { display: "none" }这个 workaround 可以绕开问题。其中一位评论者(modulareverything,2024-05-03)通过二分定位,将问题锁定到v10 → v11.1.x 的升级区间——这与 issue-2656 所指向的 11.0.11 回归版本完全吻合,为后续合并处理提供了直接证据。

二、根因分析:为什么偏偏只有"none"失效

2.1 同一回归的两个入口

  • issue-2563:useAnimate/ 独立animate()命令式 API 路径;
  • issue-2656(详见 plans/issues/issue-2656.md):React 组件的variants/animate属性路径,即{ opacity: 1, display: "block" }与{ opacity: 0, display: "none" }之间切换时,隐藏动画结束后display没有回到none。

两个问题在 11.0.10 都正常,在 11.0.11 同时被破坏。issue-2656 计划文档明确指出根因是11.0.11 的异步关键帧解析重写(async-keyframe-resolution,CHANGELOG 中对应 "Keyframes now resolved asynchronously"),该改动改变了离散字符串关键帧的混合(mix)方式,导致none终点状态丢失。mattgperry 于 2024-05-13 确认了回归(原话大意:"这个确实被无意间破坏了,不过此前的行为也不理想——瞬间切到 'none'"),并在当天合入修复。

2.2isNone():"none"被当作特殊关键帧

为什么其他关键字不受影响?关键在关键帧解析器对"none"的特殊对待。packages/motion-dom/src/animation/keyframes/utils/is-none.ts 中的isNone()把"none"(以及数字0、字符串"0"、各种零值字符串)判定为特殊值:

export function isNone(value: AnyResolvedKeyframe | null) { if (typeof value === "number") { return value === 0 } else if (value !== null) { return value === "none" || value === "0" || isZeroValueString(value) } else { return true } }

"inline"、"contents"等普通关键字不会命中该判断,走的是常规的mixImmediate即时混合路径,因此不受回归影响;而"none"由于在解析器和 mixers 中都被当作特殊关键帧处理,走进了被破坏的离散混合(discrete-mix)路径,最终丢掉了终点值。这就是"只有none坏了"的完整解释。

三、修复实现:mixVisibility与二值可见性插值

3.1 核心函数

修复提交9dc6e6aa1(2024-05-13,随v11.2.0于 2024-05-14 发布)新增了mixVisibility,位于 packages/motion-dom/src/utils/mix/visibility.ts:

export const invisibleValues = new Set(["none", "hidden"]) export function mixVisibility(origin: string, target: string) { if (invisibleValues.has(origin)) { return (p: number) => (p <= 0 ? origin : target) } else { return (p: number) => (p >= 1 ? target : origin) } }

其语义可以拆成两条规则:

  • 从不可见 → 可见(如none→block):p <= 0时保持origin,其余进度立即输出target,即显示方向瞬间应用可见值;
  • 从可见 → 不可见(如block→none):p >= 1时才输出target,其余进度保持origin,即隐藏方向在整个动画期间保持可见,动画结束瞬间才切到none/hidden。

这正是 CHANGELOG 中该版本记录的措辞:"Binary visibility interpolation i.edisplay: ["block", "none"]now maintains the visible state throughout the animation"(见 CHANGELOG.md 对应 v11.2.0 条目)。

3.2 路由与调度

mixVisibility并非孤立生效,它被嵌入到混合管线中:

  1. 路由:packages/motion-dom/src/utils/mix/complex.ts 的mixComplex检测到invisibleValues("none"、"hidden")且对端没有可插值数值时,直接返回mixVisibility(origin, target),不再走数值混合。
  2. 保留 JS 路径:packages/motion-dom/src/animation/utils/can-animate.ts 中if (name === "display" || name === "visibility") return true,确保这两个属性始终进入 JS 动画路径(代码注释明确说明"这些传统上不可动画,但我们支持它们")。
  3. 不启用 WAAPI 加速:packages/motion-dom/src/animation/waapi/supports/waapi.ts 依据acceleratedValues判断,display不在加速列表中,因此display动画永远不会被 Web Animations API 加速执行,全部由 JS 帧循环驱动。
  4. 终点落盘:packages/motion-dom/src/animation/JSAnimation.ts 的tick在动画完成时通过getFinalKeyframe(...)写入真实的最终关键帧(即"none"),保证动画结束后样式值精确落在目标值上。

3.3 回归测试与演示用例

仓库已包含完整的回归测试,位于 packages/framer-motion/src/motion/tests/animate-prop.test.tsx:

  • "animate display none => block immediately switches to block":验证显示方向在动画开始后立即输出"block";
  • "animate display block => none switches to none on animation end":验证隐藏方向在动画结束时才输出"none";
  • 还有"animate visibility hidden => visible immediately switches to visible"等visibility用例,以及none/block切换触发onAnimationComplete的用例。

修复提交同时新增了开发演示 fixture:dev/react/src/examples/Animation-display-visibility.tsx,可直接在 dev 环境观察display/visibility动画的完整时序。

四、修复后的预期行为:none在动画完成时生效,而非开始时

需要特别强调的是,修复后display: "none"在动画完成时才被应用,而不是动画一开始就生效——这是设计行为,不是 bug。对报告者的 0.7s 场景,即:元素先淡出 0.7s(期间保持可见),0.7s 结束后display才切换为none。这样元素在淡出过程中始终可见可交互,视觉上自然;若要在动画开始时立即隐藏,需要改用其他手段(例如opacity配合pointer-events或延迟应用display)。这也是原文档在 Verdict 末尾特意标注"not at the start — that is the designed behaviour, not a bug"的原因。

因此,transitionEnd: { display: "none" }这个 workaround 在 v11.2.0 之后不再需要,官方在关闭评论中也明确建议:若在 motion@12 上仍可复现,请附上最小复现(reproduction)新建 issue。

五、useAnimate路径为何同样被覆盖

issue-2563 走的是useAnimate命令式路径,issue-2656 走的是 React 声明式路径,两条路径最终汇入同一条渲染管线。从 packages/framer-motion/src/animation/animate/subject.ts 可以看到,元素动画会为 DOM 元素创建视觉元素(createDOMVisualElement,见 subject.ts),随后与组件动画一样经过AsyncMotionValueAnimation → JSAnimation → mix这条管线。由于display永远不会被 WAAPI 加速(见 3.2 节),两条入口在 JS 混合层完全汇合,因此同一个mixVisibility修复天然覆盖useAnimate与独立animate()。

六、计划文档的执行流程:从验证到关闭

issue-2563 计划文档本身还包含一套可复现的落地步骤,这也展示了仓库中 plan 类文档的标准作业方式:

Step 1:用 issue-2656 的回归测试做验证(gate)

npx jest --config packages/framer-motion/jest.config.json --testPathPattern="animate-prop" -t "display"

验证标准:所有display相关测试全部通过(≥3 个,0 失败);若有任何失败,则说明回归仍然存在,必须停止并改用 FIX 计划。

Step 2:审批门禁(Approval gate)

打开 plans/issues/README.md,找到 issue-2563 对应状态行;若该计划未被标记为 APPROVED,则将该行置为 BLOCKED 并停止。

Step 3:评论并关闭 issue

gh api repos/motiondivision/motion/issues/2563/comments -f body="This was the same regression as #2656 and was fixed in v11.2.0 (2024-05-14). Since then, animating to display: 'none' holds the visible value for the duration of the animation and applies 'none' when the animation completes — so with your 0.7s duration, display switches to none after 0.7s (by design, so the element stays visible while it fades out). The transitionEnd workaround is no longer required. If you can still reproduce on motion@12, please open a new issue with a reproduction." gh api -X PATCH repos/motiondivision/motion/issues/2563 -f state=closed -f state_reason=completed

验证:gh api repos/motiondivision/motion/issues/2563 --jq .state返回"closed"。

完成标准与停止条件

  • Done criteria:Step 1 测试通过;issue 在 APPROVED 后以completed原因注释并关闭;不修改任何源文件;更新 plans/issues/README.md 状态行。
  • STOP conditions:Step 1 失败 → 报告并不得关闭;README 行未 APPROVED → 置为 BLOCKED。
  • Drift check:每次执行前先跑gh api repos/motiondivision/motion/issues/2563 --jq .state,若 issue 已关闭则直接标记 DONE 停止,避免重复劳动。

七、小结:从"奇怪 bug"到可复用的修复范式

issue-2563 的价值在于它揭示了一个典型回归的三层结构:症状(display: "none"不生效)、根因(异步关键帧解析重写破坏离散值混合,且只有被isNone()特殊化的"none"走受损路径)、修复(mixVisibility把display/visibility定义为"二值可见性插值":显示立即生效、隐藏动画结束生效)。它同时演示了与 issue-2656 合并关闭的跨 issue 协同方式:同根因问题共享修复与回归测试,通过-t "display"定向测试 + GitHub CLI 完成闭环。

对使用 Motion 的开发者,本文可以提炼出两条实践结论:

  1. 对display/visibility这类离散属性,动画期间的值变化是"阶梯式"的:显示方向立即切换,隐藏方向在动画完成时切换——这正是淡出后自动隐藏元素的标准模式,无需再借助transitionEnd。
  2. 遇到"某个特殊值失效、其他值正常"的动画 bug 时,可以顺着"特殊值在解析器/mixer 中的特殊分支"这条线索排查(如isNone()对"none"的特殊处理),往往能快速定位到回归点。

进一步阅读:根因与修复细节见 plans/issues/issue-2656.md;核心实现见 visibility.ts、complex.ts、is-none.ts、can-animate.ts;回归测试见 animate-prop.test.tsx。

  • 前端
  • UI组件

【免费下载链接】motion

A modern animation library for React and JavaScript

项目地址:https://gitcode.com/GitHub_Trending/mo/motion
点击查看免费下载

相关推荐

上一篇:Rebound高级技巧:创建复杂动画链和交互效果
下一篇:ncc源码中的代码复用策略:工具库与辅助函数

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 2:05:52

GPTsdex 提示词拆解:基于 GPT Actions 构建万级自定义 GPT 推荐引擎

提示工程 【免费下载链接】GPTs leaked prompts of GPTs 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/gp/GPTs 点击查看 免费下载 GPTsdex 是收录于本仓库 prompts/GPTsdex.md 的一个推荐型 GPT 系统提示词&#xff0c;其定位是"探索超过 10,000 个自定义…

作者头像 李华
网站建设 2026/10/1 2:05:42

Madeira兼容层:Wine+FEX-Emu+DXMT跨平台运行原理

1. 项目概述&#xff1a;从“Madeira”到跨平台兼容层的技术溯源“Madeira”这个词在当前技术语境下&#xff0c;绝非仅指葡萄牙的马德拉群岛或同名葡萄酒——它正悄然成为国内Linux桌面生态中一个高频出现、却极少被系统性解读的技术代号。结合热搜词中反复出现的Wine、FEX-Em…

作者头像 李华
网站建设 2026/10/1 2:05:23

基于 Rube MCP 的 Diffbot 自动化实战:Awesome Claude Skills 应用指南

AI 技能AI 插件人工智能工作流自动化 【免费下载链接】awesome-claude-skills A curated list of awesome Claude Skills, resources, and tools for customizing Claude AI workflows 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/aw/awesome-claude-skills 点击…

作者头像 李华
网站建设 2026/10/1 2:05:13

沁恒微 RISC-V 蓝牙 CH5xx GPIO使用说明

CH5xx 芯片的 GPIO 使用说明以及注意事项 ...... 矜辰所致 ...... 增加晶振引脚的说明 2025/12/5 ...... 增加中断标志寄存器的读取说明 2026/3/2 ...... 增加GPIO上电默认状态说明 2026/9/30前言 官方并没有单独为 GPIO 写一…

作者头像 李华