open-slide 设计工程实战:基于 Emil Kowalski 动画决策框架打磨幻灯片细节体验
【免费下载链接】open-slideA slide framework built for agents.项目地址https://gitcode.com/gh_mirrors/op/open-slide点击查看免费下载open-slide 是一款为 Agent 而生的幻灯片框架它把演示体验本身当作一等公民页面过渡、元素入场、Morph 变换与手势反馈都依赖高质量的动画细节。本指南以仓库内 .agents/skills/emil-design-eng/SKILL.md 为核心骨架系统讲解设计工程Design Engineering的方法论——从动画决策框架、缓动曲线、弹簧动画到组件构建原则、性能规则与可访问性并结合 open-slide 源码中的实际实现过渡配置、缓动变量、reduced-motion 处理给出可验证的落地参考。读完你既能获得一套完整的 UI 打磨检查清单也能理解如何把这些原则写进一个以动画为核心的框架里。这份 Skill 是什么设计工程在 open-slide 中的定位.agents/skills/目录是仓库为 Agent 准备的技能包集合其中emil-design-eng在 skills-lock.json 中被登记为可加载技能其主体文件即 .agents/skills/emil-design-eng/SKILL.md。它的定位是编码了 Emil Kowalski 关于 UI 打磨、组件设计、动画决策以及那些让软件感觉很好的隐形细节的整套哲学。按照 SKILL.md 的约定该技能首次被调用且没有具体问题时只回复一段欢迎语提示可以进一步深入其设计工程课程不提供额外信息直到用户提出具体问题。也就是说它是一份按需激活的工程准则被激活后你应当以一名具有工艺敏感度的设计工程师身份行事让每个细节叠加成恰到好处的整体。对一个以动画和过渡为核心卖点的幻灯片框架而言这份准则恰好覆盖了从要不要动画到动画多快、多软的完整决策链。核心哲学品味是可训练的细节会叠加美是杠杆品味可以训练而非天生好的品味不是个人偏好而是一种被训练出来的本能能够越过显而易见的部分识别出什么让体验升华。训练方式是让自己沉浸在伟大的作品里、深入思考某个东西为什么感觉好并持续练习。在构建 UI 时不要只满足于能用要研究最好的界面为什么是这样的感觉——拆解动画、审查交互、保持好奇。看不见的细节会叠加大多数细节用户从不会刻意注意到——这正是要点。当一个功能的表现与用户预想完全一致时用户不会多看一眼。这才是目标。SKILL.md 引用 Paul Graham 的话所有这些看不见的细节组合在一起产生了令人惊叹的东西就像一千个几乎听不见的声音在一起齐唱。——Paul Graham下文每一条决策之所以存在正是因为隐形正确性的聚合会创造出用户不知道为什么但就是喜欢的界面。美是杠杆人们选择工具看的是整体体验而不只是功能。好的默认值和好的动画是真正的差异化因素。美在软件中常被低估——把它当作杠杆来突出自己。评审输出格式强制 Before/After 表格当评审 UI 代码时必须使用带 Before/After 两列的 Markdown 表格禁止用 Before: 和 After: 分行列表。标准格式如下BeforeAfterWhytransition: all 300mstransition: transform 200ms ease-out指定精确属性避免alltransform: scale(0)transform: scale(0.95); opacity: 0现实世界中没有东西会凭空出现下拉菜单用ease-in使用ease-out加自定义曲线ease-in感觉迟钝ease-out给出即时反馈按钮没有:active状态:active时transform: scale(0.97)按钮必须对按压有响应Popover 用transform-origin: centertransform-origin: var(--radix-popover-content-transform-origin)Popover 应从触发器处缩放Modal 除外——Modal 保持居中错误格式永远不要这样做Before: transition: all 300ms After: transition: transform 200ms ease-out ──────────────────────────── Before: scale(0) After: scale(0.95)正确格式一张带| Before | After | Why |三列的 Markdown 表格每个问题一行Why 列简要说明理由。动画决策框架写动画代码前先回答四个问题任何动画代码动笔之前按顺序回答以下问题。1. 这个动画是否应该存在先问用户多久会看到这个动画一次出现频率决策每天 100 次快捷键、命令面板开关不要动画。永远不要。每天十几次hover 效果、列表导航移除或大幅削减偶尔Modal、抽屉、Toast标准动画罕见/首次引导、反馈表单、庆祝效果可以增加愉悦感永远不要动画键盘触发的动作。这类动作每天被重复成百上千次动画会让它们显得缓慢、延迟、与用户的操作脱节。Raycast 没有开合动画——对每天使用数百次的东西来说这就是最优体验。2. 动画的目的是什么每个动画都必须能明确回答为什么这里要动画。合法目的包括空间一致性Toast 从同一方向进出让滑动关闭变得直观状态指示变形的反馈按钮展示状态变化解释说明营销动画演示某个功能如何工作反馈按钮按下时缩小确认界面听见了用户防止突兀变化元素无过渡地出现或消失会显得破碎如果目的只是看起来很酷而且用户会经常看到就不要做动画。3. 应该用哪种缓动决策树如下元素是进入还是退出 是 → ease-out快速起步感觉响应迅速 否 → 是在屏幕上移动/变形 是 → ease-in-out自然的加速/减速 是 hover/颜色变化 是 → ease 是匀速运动跑马灯、进度条 是 → linear 默认 → ease-out关键使用自定义缓动曲线。内置 CSS 缓动太弱缺少让动画显得有意图的冲击力/* 适合 UI 交互的强 ease-out */ --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* 适合屏幕上移动的强 ease-in-out */ --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* 类似 iOS 抽屉的曲线源自 Ionic Framework */ --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);永远不要在 UI 动画中使用 ease-in。它起步缓慢让界面显得迟钝、无响应。同样是 300ms带ease-in的下拉菜单感觉比ease-out的慢因为 ease-in 延迟了最初的运动——而那正是用户盯得最紧的时刻。不要从零手写曲线在缓动曲线资源站如 easing.dev、easings.co 这类缓动速查/生成器站点上寻找标准缓动的更强自定义变体。这一原则在 open-slide 中就是写死的实现事实核心样式的缓动令牌统一收敛在 packages/core/src/app/styles.css定义为--ease-swift: cubic-bezier(0.22, 1, 0.36, 1)——一个典型的快速起步、尾段轻微过冲的强 ease-out注释明确写着收敛到设计令牌而不是在每处散落 cubic-bezier 字面量。演示幻灯片同样遵循此道例如 apps/demo/slides/llm-fundamentals/index.tsx 将const ease cubic-bezier(0.16, 1, 0.3, 1)定义为全局缓动变量供所有l-fadeup等动画复用apps/demo/slides/morph-messages/index.tsx 则显式区分EASE_IN cubic-bezier(0.4, 0, 1, 1)、EASE_OUT cubic-bezier(0, 0, 0.2, 1)、EASE_STANDARD cubic-bezier(0.4, 0, 0.2, 1)三套 Material 曲线而 apps/demo/slides/material-design-2014/index.tsx 额外定义M_OVERSHOOT cubic-bezier(0.34, 1.56, 0.64, 1)用于需要弹性的装饰性场合——这正是把缓动当设计令牌、按组件气质选用的落地写法。4. 动画应该多快元素时长按钮按压反馈100-160msTooltip、小型 Popover125-200ms下拉菜单、Select150-250msModal、抽屉200-500ms营销/解释类可以更长规则UI 动画应控制在 300ms 以内。180ms 的下拉菜单比 400ms 的更跟手。转圈更快的 Spinner 会让应用感觉加载更快即便实际加载时间完全相同。感知性能动画速度直接改变体感动画的速度不只是爽快——它直接影响用户对应用性能的感知转得快的 Spinner让加载感觉更快加载时间相同感知不同180ms 的 Select动画比400ms的更有响应感第一个 Tooltip 打开之后后续 Tooltip 即时出现跳过延迟 跳过动画会让整个工具栏感觉更快速度感知与实际速度同样重要。缓动放大了这一点同样是 200msease-out比ease-in感觉更快因为用户立刻看到了运动。open-slide 的播放器控制条正是这一规则的活例子packages/core/src/app/components/present/control-bar.tsx 的工具栏容器只对translate, scale, opacity, filter做过渡且绑定ease-swift按钮则用transition-[background-color,color,opacity,scale] duration-150150ms 落在按钮按压反馈区间进度条 只动画transformmotion-safe:transition-transform motion-safe:duration-200配合origin-left与ease-swift——全程只触碰 GPU 友好的属性。弹簧动画用物理而不是时长弹簧比基于时长的动画更自然因为它们模拟真实物理没有固定时长而是根据物理参数安定下来。什么时候用弹簧带惯性的拖拽交互应该有生命感的元素如 Apple 的 Dynamic Island可能中途被打断的手势装饰性的鼠标跟随交互基于弹簧的鼠标交互把视觉变化直接绑定到鼠标位置会显得机械因为缺少运动。用 Motion原 Framer Motion的useSpring把数值变化插值成弹簧行为而不是即时更新import { useSpring } from framer-motion; // 不用弹簧机械、即时 const rotation mouseX * 0.1; // 用弹簧自然、有惯性 const springRotation useSpring(mouseX * 0.1, { stiffness: 100, damping: 10, });这成立的前提是动画是装饰性的——它不承担功能。如果是银行应用里的功能图表不做动画反而更好。要知道装饰何时有益、何时有害。弹簧配置Apple 式推荐——更容易推理{ type: spring, duration: 0.5, bounce: 0.2 }传统物理式更可控{ type: spring, mass: 1, stiffness: 100, damping: 10 }使用 bounce 时保持轻微0.1-0.3。大多数 UI 场景避免 bounce把它留给拖拽关闭和俏皮的交互。可打断性是弹簧的最大优势弹簧被打断时保持速度——CSS 动画和 keyframes 则从零重启。因此弹簧非常适合用户可能中途变向的手势当你展开一个条目又立刻按 Escape 时弹簧动画会从当前位置平滑反向。打断时平滑反向这是 keyframes 永远做不到的。组件构建原则按钮必须能听见按压在:active上加transform: scale(0.97)给出即时反馈让 UI 感觉真的在听用户说话.button { transition: transform 160ms ease-out; } .button:active { transform: scale(0.97); }适用于任何可按压元素缩放要轻微0.95-0.98。永远不要从 scale(0) 开始现实世界中没有东西会完全消失再出现。从scale(0)开始动画的元素看起来像是凭空冒出来的。从scale(0.9)或更高起步配合 opacity。即便是几乎不可见的初始缩放也会让入场更自然就像瘪掉的气球也有可见的形状/* 差 */ .entering { transform: scale(0); } /* 好 */ .entering { transform: scale(0.95); opacity: 0; }让 Popover 感知原点Popover 应该从触发器处缩放而不是从中心。默认的transform-origin: center对几乎所有的 Popover 都是错的。例外Modal。Modal 应该保持transform-origin: center因为它们不锚定于特定触发器——它们在视口居中显示/* Radix UI */ .popover { transform-origin: var(--radix-popover-content-transform-origin); } /* Base UI */ .popover { transform-origin: var(--transform-origin); }用户是否单独注意到这个差异并不重要。在聚合层面看不见的细节会变得可见它们会叠加。Tooltip后续悬停跳过延迟Tooltip 应延迟出现以防误触。但一旦某个 Tooltip 已打开悬停到相邻 Tooltip 应该即时出现、不做动画。这样既快又不破坏初始延迟的意义.tooltip { transition: transform 125ms ease-out, opacity 125ms ease-out; transform-origin: var(--transform-origin); } .tooltip[data-starting-style], .tooltip[data-ending-style] { opacity: 0; transform: scale(0.97); } /* 跳过后续 Tooltip 的动画 */ .tooltip[data-instant] { transition-duration: 0ms; }动态 UI 用 CSS transition 而不是 keyframesCSS transition 可以在动画中途被打断并重定向keyframes 则从零重启。任何可能被快速反复触发的交互连续加 Toast、切换状态transition 都更平滑/* 可打断——适合 UI */ .toast { transition: transform 400ms ease; } /* 不可打断——避免用于动态 UI */ keyframes slideIn { from { transform: translateY(100%); } to { transform: translateY(0); } }用模糊遮住不完美的过渡当两个状态之间的交叉淡入在换过各种缓动和时长后仍然别扭时在过渡期间加一点filter: blur(2px)。为什么 blur 有效没有 blur 时交叉淡入会看到两个不同的物体——旧状态和新状态重叠。这很不自然。Blur 通过把两个状态混在一起弥合视觉鸿沟骗过眼睛把它感知成一次平滑变换而不是两个物体在替换。配合按压缩放scale(0.97)可以得到打磨过的按钮状态过渡.button { transition: transform 160ms ease-out; } .button:active { transform: scale(0.97); } .button-content { transition: filter 200ms ease, opacity 200ms ease; } .button-content.transitioning { filter: blur(2px); opacity: 0.7; }Blur 保持在 20px 以内。过重的模糊开销很大尤其是在 Safari 上。用 starting-style 动画进入态现代 CSS 无需 JavaScript 就能动画元素进入.toast { opacity: 1; transform: translateY(0); transition: opacity 400ms ease, transform 400ms ease; starting-style { opacity: 0; transform: translateY(100%); } }这取代了常见的 React 模式——用useEffect在首次渲染后设置mounted: true。浏览器支持时用starting-style否则回退到data-mounted属性模式// 传统模式仍然到处可用 useEffect(() { setMounted(true); }, []); // div>/* 与抽屉高度无关 */ .drawer-hidden { transform: translateY(100%); } /* 与 Toast 高度无关 */ .toast-enter { transform: translateY(-100%); }优先使用百分比而非写死的像素值。它们更不容易出错还能适配内容。scale() 会连带缩放子元素与width/height不同scale()也会缩放元素的子元素。按压缩放按钮时字号、图标、内容按比例缩放。这是特性不是 bug。3D 变换带来纵深rotateX()、rotateY()配合transform-style: preserve-3d可以在 CSS 中创造真实的 3D 效果。轨道动画、硬币翻转、纵深效果都不需要 JavaScript.wrapper { transform-style: preserve-3d; } keyframes orbit { from { transform: translate(-50%, -50%) rotateY(0deg) translateZ(72px) rotateY(360deg); } to { transform: translate(-50%, -50%) rotateY(360deg) translateZ(72px) rotateY(0deg); } }transform-origin 决定锚点每个元素都有一个变换执行的锚点默认是中心。把它设置成触发器所在的位置以获得原点感知的交互。这在 open-slide 的演示幻灯片里有大量实证apps/demo/slides/harness-engineering/index.tsx 的条形增长动画全部带transform-origin: left center从左端生长而非中心生长apps/demo/slides/llm-fundamentals/index.tsx 的l-scale/l-line/l-bar同样如此——这正是transform-origin 匹配语义的教科书式应用。clip-path被低估的动画利器clip-path不只是做形状它是 CSS 里最强大的动画工具之一。inset 形状的语义clip-path: inset(top right bottom left)定义一个矩形裁剪区域每个值从对应方向吃掉元素/* 从右侧完全隐藏 */ .hidden { clip-path: inset(0 100% 0 0); } /* 完全可见 */ .visible { clip-path: inset(0 0 0 0); } /* 从左到右揭示 */ .overlay { clip-path: inset(0 100% 0 0); transition: clip-path 200ms ease-out; } .button:active .overlay { clip-path: inset(0 0 0 0); transition: clip-path 2s linear; }Tab 的完美颜色过渡复制一份 Tab 列表把副本样式化为激活态不同的背景、不同的文字颜色。裁剪副本让只有激活的 Tab 可见Tab 切换时动画 clip。这会创造出逐项过渡颜色永远无法实现的丝滑颜色切换。按住删除模式在彩色覆盖层上用clip-path: inset(0 100% 0 0)。:active时以 2s linear 过渡到inset(0 0 0 0)松开时以 200ms ease-out 弹回。给按钮加scale(0.97)做按压反馈。滚动揭示图片从clip-path: inset(0 0 100% 0)开始从底部隐藏。元素进入视口时动画到inset(0 0 0 0)。用IntersectionObserver或 Framer Motion 的useInView参数{ once: true, margin: -100px }。对比滑块叠加两张图用clip-path: inset(0 50% 0 0)裁剪上面的图。根据拖拽位置调整右侧 inset 值。不需要额外 DOM 元素完全硬件加速。手势与拖拽交互基于动量判定 dismissal不要要求拖过阈值。计算速度Math.abs(dragDistance) / elapsedTime。速度超过约 0.11 就 dismiss与距离无关。轻轻一甩就应当足够const timeTaken new Date().getTime() - dragStartTime.current.getTime(); const velocity Math.abs(swipeAmount) / timeTaken; if (Math.abs(swipeAmount) SWIPE_THRESHOLD || velocity 0.11) { dismiss(); }边界阻尼用户拖过自然边界时例如抽屉已经在顶部还往上拖应用阻尼。拖得越多元素动得越少。现实中的东西不会戛然而止它们先减速。指针捕获拖拽一旦开始让元素捕获所有指针事件确保指针离开元素边界后拖拽仍能继续。多点触控保护初始拖拽开始后忽略额外的触摸点。否则拖拽中途换手指会导致元素跳到新位置function onPress() { if (isDragging) return; // 开始拖拽... }用摩擦代替硬停与其完全禁止向上拖拽不如允许并施加递增摩擦。这比撞上一堵隐形墙自然得多。性能规则只动画 transform 和 opacity这两个属性跳过布局与绘制直接在 GPU 上运行。动画padding、margin、height、width会触发全部三个渲染阶段。CSS 变量会级联重算在父元素上改 CSS 变量会为所有子元素重算样式。在一个包含大量条目的抽屉里在容器上更新--swipe-amount会导致昂贵的样式重算。改为直接在该元素上更新transform// 差触发所有子元素重算 element.style.setProperty(--swipe-amount, ${distance}px); // 好只影响这个元素 element.style.transform translateY(${distance}px);Framer Motion 硬件加速的注意点Framer Motion 的简写属性x、y、scale不是硬件加速的它们用主线程上的requestAnimationFrame。要硬件加速用完整transform字符串// 非硬件加速方便但负载下会掉帧 motion.div animate{{ x: 100 }} / // 硬件加速主线程繁忙时依然流畅 motion.div animate{{ transform: translateX(100px) }} /这在浏览器同时加载内容、运行脚本或绘制时至关重要。在 VercelDashboard 的 Tab 动画用了 Shared Layout Animations页面加载期间掉帧切换到 CSS 动画离开主线程后修复。高负载下 CSS 动画优于 JSCSS 动画在主线程之外运行。浏览器忙于加载新页面时Framer Motion 动画用requestAnimationFrame会掉帧CSS 动画保持流畅。预先确定的动画用 CSS动态、可打断的动画用 JS。用 WAAPI 获得 JS 控制 CSS 性能Web Animations API 给你 JS 的控制力与 CSS 的性能硬件加速、可打断、无需库element.animate([{ clipPath: inset(0 0 100% 0) }, { clipPath: inset(0 0 0 0) }], { duration: 1000, fill: forwards, easing: cubic-bezier(0.77, 0, 0.175, 1), });性能规则在 open-slide 的过渡系统里是硬约束核心的 slide-transition-layer.tsx 定义DEFAULT_EASING cubic-bezier(.4, 0, .2, 1)Material 标准曲线并让 Morph 过渡只针对MORPH_VISUAL_PROPERTIES里列出的视觉属性opacity、color、background-color、border-*-color、fill、stroke 等做插值——颜色/透明度类属性是渲染成本最低的一档这正是只动画便宜属性的工程化取舍。同一文件还通过holdOpacity在过渡期保持透明度避免两帧重叠时出现两个物体的观感与用 blur 遮住不完美过渡是同一个思路的变体。可访问性prefers-reduced-motion动画可能导致晕动症。Reduced motion 意味着更少、更温和的动画而不是零动画。保留有助于理解的 opacity 和颜色过渡移除位移和位置动画media (prefers-reduced-motion: reduce) { .element { animation: fade 0.2s ease; /* 不要基于 transform 的运动 */ } }const shouldReduceMotion useReducedMotion(); const closedX shouldReduceMotion ? 0 : -100%;这一条在 open-slide 中被做成了基础能力工具函数 use-prefers-reduced-motion.ts 把(prefers-reduced-motion: reduce)封装成 React HookusePrefersReducedMotion()与命令式prefersReducedMotion()两个入口播放器、步进上下文、幻灯片路由等如 player.tsx、step-context.tsx、slide.tsx都会消费它样式侧 styles.css 则用media (prefers-reduced-motion: reduce)把网格入场动画等直接置为animation: none。此外播放器相关组件普遍使用 Tailwind 的motion-safe:前缀如 blackout-overlay.tsx、jump-input.tsx、laser-pointer.tsx在用户请求减少动态效果时自动降级——把可访问性当成默认行为而不是事后补救。触摸设备的 hover 状态触摸设备会在点按上触发 hover造成误判。把 hover 动画关进这条媒体查询后面media (hover: hover) and (pointer: fine) { .element:hover { transform: scale(1.05); } }Sonner 原则构建被喜爱的组件以下原则来自构建 Sonner每周超 1300 万次 npm 下载的 Toast 库的经验适用于任何组件开发者体验是关键。不要 Hook、不要 Context、不要复杂配置。插入Toaster /一次然后从任何地方调用toast()。采用摩擦越小使用的人越多。好的默认值比选项更重要。开箱即美。大多数用户从不自定义。默认的缓动、时长和视觉设计应该出色。命名创造身份。Sonner法语敲响比 react-toast 更优雅。在适当的时候用可记忆性换可发现性。隐形地处理边界情况。标签页隐藏时暂停 Toast 计时器。用伪元素填充堆叠 Toast 之间的空隙以保持 hover 状态。拖拽期间捕获指针事件。用户从不会注意到这些——这正是对的。动态 UI 用 transition 而非 keyframes。Toast 会被快速添加。keyframes 被打断时从零重启transition 平滑重定向。做一个优秀的文档站。让用户先接触产品、把玩它、理解它然后再使用它。带现成代码片段的交互式示例能降低采用门槛。有趣的是open-slide 自己就把 Sonner 作为基础 UI 组件内置packages/core/src/app/components/ui/sonner.tsx上述六条原则因此直接约束着框架内的通知体验——好的默认值隐形处理边界情况动态 UI 用 transition这三条在框架的 Toast 场景中逐条兑现。连贯性让动画风格匹配组件气质Sonner 的动画之所以令人满足部分是因为整体体验是连贯的缓动和时长符合库的气质。它比典型 UI 动画稍慢用ease而非ease-out以求更优雅。动画风格与 Toast 设计、页面设计、命名——一切都和谐一致。选择动画数值时要考虑组件的个性。俏皮的组件可以更有弹性专业仪表盘应该利落快速。让运动匹配情绪。opacity height 组合当条目进出列表时opacity 变化必须与高度动画配合得当。这常常是试错过程没有公式——调到感觉对为止。第二天再审一遍用新鲜的眼睛审查动画。你在第二天能发现开发时漏掉的瑕疵。用慢速或逐帧播放动画找出全速下看不见的时序问题。非对称的进出场时长按压过程需要刻意时应慢按住删除2s linear但释放永远要快200ms ease-out。这个模式广泛应用用户做决定时慢系统响应时快/* 释放快 */ .overlay { transition: clip-path 200ms ease-out; } /* 按压缓慢而刻意 */ .button:active .overlay { transition: clip-path 2s linear; }非对称进出场在 open-slide 的过渡配置模型中是第一等公民核心库 transition.ts 的SlideTransition类型允许为同一页面分别定义enter与exit两个TransitionPhase各自拥有独立的 keyframes、easing、duration、delay再经由resolveTransition按页解析。这意味着进入 800ms、退出 200ms这类非对称节奏可以直接声明在幻灯片配置里而不必写胶水代码。交错动画多个元素同时入场时错开它们的出现时间。每个元素在前一个之后延迟一小段时间进场形成比齐刷刷出现更自然的级联效果.item { opacity: 0; transform: translateY(8px); animation: fadeIn 300ms ease-out forwards; } .item:nth-child(1) { animation-delay: 0ms; } .item:nth-child(2) { animation-delay: 50ms; } .item:nth-child(3) { animation-delay: 100ms; } .item:nth-child(4) { animation-delay: 150ms; } keyframes fadeIn { to { opacity: 1; transform: translateY(0); } }交错延迟保持短条目间 30-80ms。长延迟会让界面感觉慢。交错是装饰性的——交错动画播放期间永远不要阻塞交互。open-slide 核心样式的网格入场动画就是克制交错的实现styles.css 定义osd-rise-in从opacity: 0; transform: translateY(4px)起步4px 的极小位移 350ms--ease-swift注释明确写着与每条目的小延迟配合形成有上限的交错——与30-80ms 短延迟、装饰性交错的原则一一对应。调试动画慢动作测试用降速播放动画找出全速下看不见的问题。临时把时长放大到正常值的 2-5 倍或使用浏览器 DevTools 的动画检查器减慢播放。慢动作下要检查颜色过渡是否顺滑还是看到两个不同状态重叠缓动是否合适还是启动/停止突兀transform-origin 是否正确还是元素从错误的位置缩放多个动画属性opacity、transform、color是否同步逐帧检查在 Chrome DevTools 的 Animations 面板里逐帧步进动画。这能暴露全速下看不见的、协调属性之间的时序问题。在真实设备上测试对触摸交互抽屉、滑动手势在物理设备上测试USB 连接手机通过 IP 访问本地开发服务器使用 Safari 的远程开发者工具。Xcode 模拟器是替代方案但手势测试真实硬件更好。评审清单评审 UI 代码时逐项核对问题修复transition: all指定精确属性transition: transform 200ms ease-outscale(0)入场动画从scale(0.95)加opacity: 0起步UI 元素用ease-in换成ease-out或自定义曲线Popover 用transform-origin: center设置为触发器位置或用 Radix/Base UI 的 CSS 变量Modal 豁免——保持居中键盘动作上有动画完全移除动画UI 元素时长 300ms降到 150-250mshover 动画没有媒体查询加media (hover: hover) and (pointer: fine)快速触发的元素用 keyframes改用 CSS transition 保证可打断性负载下用 Framer Motionx/y用transform: translateX()获得硬件加速进出场速度相同让出场快于进场如进场 2s、出场 200ms元素全部同时出现加交错延迟条目间 30-80ms把方法论落进框架open-slide 中的设计工程闭环把这份 Skill 的准则映射到 open-slide 上可以看到一条完整的落地闭环缓动是设计令牌核心样式收敛--ease-swiftstyles.css幻灯片按主题各自声明材质化曲线llm-fundamentals、morph-messages、material-design-2014让自定义缓动曲线成为可复用资产。过渡可配置且非对称SlideTransition的enter/exit独立相位transition.ts让进出场节奏、时长、缓动都能按页声明默认缓动则集中在 slide-transition-layer.tsx。只动画便宜属性Morph 只插值颜色与透明度属性控制条只过渡translate/scale/opacity/filtercontrol-bar.tsx。可访问性是默认值统一的prefers-reduced-motionHookuse-prefers-reduced-motion.ts 样式层animation: none降级 motion-safe:前缀三管齐下。通知体验直接继承 Sonner 原则内置 sonner.tsx 即六条原则的执行者。无论你是要为 open-slide 编写新幻灯片主题、评审既有过渡效果还是把这个框架当作 Agent 的界面品味参照系这份 Skill 的四个问题要不要动画 → 目的是什么 → 用什么缓动 → 多快 评审清单都是一份可以直接照着执行的决策模板。记住那句贯穿全文的判据动画的体感速度与实际速度同等重要而不可见细节的聚合最终会成为用户说不出理由的喜爱。赞分享【免费下载链接】open-slideA slide framework built for agents.项目地址https://gitcode.com/gh_mirrors/op/open-slide点击查看免费下载相关推荐SurfSense 前端设计工程指南基于 Emil Kowalski 设计哲学的 UI 打磨、动画决策与细节实现SurfSense 前端设计工程指南基于 Emil Kowalski 设计哲学的 UI 打磨、动画决策与细节实现 导读 本文围绕 .cursor/skills人工智能AI 应用后端AI Agent网页爬虫RAG深度研究MCP 服务前端Open Agents 的 emil-design-eng 设计工程技能UI 动画决策框架与细节打磨方法论Open Agents 的 emil design eng 设计工程技能UI 动画决策框架与细节打磨方法论 本文围绕 Open Agents 仓库中内置的 e人工智能AI Agent代码智能体Agent 工作流Agent 沙箱工具调用后端前端Kaneo 设计工程实战用 Emil Kowalski 的动效哲学打磨出感觉对了的界面Kaneo 设计工程实战用 Emil Kowalski 的动效哲学打磨出感觉对了的界面 本文是 Kaneo开源项目管理应用仓库内 设计工程技能文档企业应用后端前端上一篇TencentDB Agent Memory备份与恢复保障AI记忆数据安全的完整方案下一篇Remotion云部署终极指南CloudRun与AWS Lambda深度对比分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →