动效的四条边界:节奏、暂停、可见性与减少动态
卷中目录
动效是状态说明,不是持续的背景噪声
界面里的运动用来说明一件事刚刚发生:面板打开了,条目插入了,焦点换到了新区域,进度还在继续。运动一旦脱离这件事情,就会变成和正文抢注意力的噪声。噪声不只是难看。前庭觉敏感的人会在大面积位移、视差和缩放里感到眩晕;依赖屏幕阅读或键盘的人会在焦点被动画搬走时丢失位置;有人只是在阅读,自动轮播已经把他们正在看的段落换成了下一屏。

这四条边界一起用。节奏决定一次运动要花多久、走多远。暂停决定自动开始的运动能不能停下来。可见性决定用户看不见的时候还要不要动,以及动完之后内容是否真的能够被感知。减少动态决定系统已经声明不愿承受大幅运动时,界面如何退回到静止的状态变化。只做其中一条,另外三条仍会在真实环境里露出来。
节奏:时长跟着距离走
短反馈和长位移不要共用一个时长。颜色、不透明度这种小变化,大约落在一百到两百毫秒之间,人能感到响应,又不会觉得界面在等待动画结束才能继续。面板展开、列表插入这类涉及尺寸变化的过程,可以稍长,大致到三百毫秒上下。跨过大半个视口的位移,即使用很柔的缓动,也仍然是大幅运动,延长时长不会把它变成温和,只会让大幅运动在视野里停留更久。
进入和离开可以用不同的缓动。进入多用先快后慢,让元素尽快到达可读的位置,再轻轻停住。离开可以稍快,因为用户的注意力通常已经转到下一处,离开的过程不必占满同样的时间。缓动曲线用贝塞尔表达即可,避免又弹又晃的回弹,回弹会让同一段位移在终点附近重复移动,前庭刺激比单向滑动更强。
同一动作里的多个元素若要错开,错开的间隔保持在很短的范围内,并且总时长要有上限。一串卡片依次飞入,单张看起来不长,全部结束可能已经超过数秒。在这几秒里,如果自动运动和正文同时存在,用户无法把注意力放在已经显示的内容上。能合成一次运动就不要拆成一长串。与操作无关的循环动画,更不该在阅读时持续播放。
不要为了显得有层次,让元素从视口外很远的地方飞进来。飞入距离越大,布局和绘制要跨越的范围越大,敏感用户越容易感到视野在移动。入场可以是很短的淡入,或几个像素之内的轻微到位。状态变化即使完全没有位移,只要颜色或文本确实变了,信息也已经送达。
性能和节奏是连在一起的。优先过渡合成器友好的属性,例如变换和不透明度,避免每一帧都触发布局的宽度、高度和上偏移。布局动画在低端设备上掉帧时,节奏会从设计的两百毫秒变成一连串卡顿,体感比静止切换更差。若某一段动画必须影响布局,把它限制在很小的区域,并在减少动态时直接切到终态。
节奏还要让操作在动画结束前就可用
动画不该把界面锁住。用户打开面板之后,若必须等过渡结束才能把焦点放进面板里的输入框,节奏就从说明变成了阻碍。焦点可以在动作开始时就移到面板内的标题或控件上,过渡只改变外观。列表项删除时,若等收起动画结束才真正移除节点,重复操作和屏幕阅读都会碰到一个视觉上还在、交互上已经半失效的对象。更清楚的做法是立即更新文档结构和可访问名称,视觉上的收起作为增强;不能这样做时,也要在动画期间用 inert 等方式避免用户点到半路状态。
时长不要写死在每种组件的私有常量里却彼此矛盾。页面上按钮反馈极快、对话框极慢、通知又循环闪动,节奏就没有共同语言。可以约定三档:瞬时反馈、区域变化、必须保持可理解的持续进度。持续进度不属于装饰。上传和保存的忙闲状态即使用户关闭了大幅运动,也仍然要用文本或进度值说明,而不能只靠一条来回跑动的光带。
暂停:自动开始的运动必须能停
自动播放、自动滚动、自动轮播,只要持续时间长,又和别的内容同时出现,就需要一个让用户暂停、停止或隐藏的办法。这个办法必须是用户做得到的操作,而不是等动画自己放完。悬停时暂停对用鼠标的人有帮助,对触屏没有悬停,对键盘用户也不自然。应提供一个真正的按钮,放在轮播或滚动区域的可达顺序里,名称写明当前是暂停还是继续。
暂停之后要停住当前这一帧的内容,不要在指针移开或焦点离开时立刻跳到下一项。用户往往是因为想看清才把指针放上去,移开却换页,阅读就被打断。焦点进入轮播内部时也应暂停。人在用键盘阅读当前项的链接时,内容自行更换,焦点还留在已经离开视野的那一项上,这是暂停边界被打破的典型情形。
纯装饰的背景视频、视差跟随指针的大幅层、循环的整页滚动提示,都属于自动运动。若它们无法提供与正文等价的静止信息,就应能被关掉,而且关掉之后正文仍然完整。不要把唯一的说明放在一段循环动画里。标记、字幕和旁边的文本才是信息;动画只是重复这个信息。
脚本驱动的循环同样要暂停。用定时器改写样式、用动画帧请求画布、用滚动驱动的时间轴做进度,都要保存一个暂停状态。暂停不是把定时器间隔调到很大,那样仍然在跳。暂停是不再推进序号,画面停在当前项,按钮的可访问名称改成继续。继续时从当前项往后走,而不是从头再放一遍,免得用户重新寻找刚才的位置。
页面上有多处自动运动时,不要要求用户逐个寻找不同样式的暂停图标。同一产品里的暂停控件应有相同的名称模式和相同的键盘行为。隐藏也算一种停止:若用户选择隐藏装饰运动,被隐藏的区域不可再拦截指针和焦点。
可见性:看不见就不要动,动完必须能被感知
离开视野的动画仍然消耗电量,也会在用户滚回来时正好停在一个中间态。用交叉观察或同类机制,在区域远离视口时暂停循环动画和装饰视频,回到视口再按用户当前的暂停选择决定是否继续。页面隐藏到后台时也要暂停。用页面可见性接口监听隐藏与显示:文档不可见时,轮播、画布和视频装饰全部停止;重新可见时不要立刻连跳多项,补上后台这段时间里本该切换的次数,那样用户会看见内容猛地翻页。稳妥的做法是保持离开前的那一项,直到用户让它继续或间隔重新开始计时。
可见性还有另一层意思:运动结束之后,内容必须处于可感知的状态。只把不透明度动画到接近全透明,元素仍可能留在可访问树和 Tab 顺序里,屏幕阅读仍能读到,键盘仍能停在看不见的控件上。若目的是隐藏,使用隐藏属性、inert 或在不需要时从 Tab 顺序移除,而不是只靠透明。反过来,若目的是显示,终态必须可见,不能依赖动画填满模式把元素从透明变成可见。一旦动画没有运行,元素会停在透明的起始关键帧上,信息等于没有出现。
稳妥的写法是:样式表里的默认状态就是静止时的终态,元素本来就可读。动画作为增强,从偏离终态的地方回到默认状态。脚本没有加载、媒体查询要求减少动态、动画被关掉时,用户看见的仍是终态。不要把关键信息写成动画结束才插入的文本。
content-visibility 一类跳过屏外渲染的手段会改变测量和滚动时间轴。屏外区域不被渲染时,依赖尺寸的入场动画和滚动驱动动画会在进入视野的瞬间补跳。若这里面包含标题和链接,补跳还会影响键盘用户对位置的判断。大段内容使用跳过渲染时,要确认可访问树里的标题仍然能够被跳转,而不是连同绘制一起被丢掉。
焦点可见性与运动是同一条边界。过渡期间不要把焦点留在即将被设置成不可用的控件上,也不要在动画中改变滚动位置把焦点滚出视口却不跟随。打开对话框时,若同时有背景缩放或视差,视野移动和焦点移动叠在一起,用户更难确认焦点落点。对话框的出现可以是很短的淡入,背景保持静止,焦点移到对话框内的可命名元素上,关闭后焦点回到打开它的那个控件。
闪烁要单独限制。短时间内大面积、高对比的反复明暗变化,可能诱发光敏性反应。界面反馈不应使用快速闪烁来强调错误或新消息。需要提醒时,用文本、图标和一处稳定的颜色变化,并保证颜色不是唯一手段。进度指示可以连续、缓慢地变化,不要在每秒内多次全屏反色。
减少动态:系统偏好是硬边界
操作系统提供减少动态的偏好。页面要用媒体查询 prefers-reduced-motion: reduce 尊重它,脚本里也要用匹配媒体的接口读同一条条件,并监听变化。用户可能在页面打开之后才到系统设置里打开这一项,只在加载时读一次,会把后半段会话留在旧状态。
减少动态不是把动画停在起始关键帧。这里说的起始关键帧若是透明或移出视野,停在那里就等于内容缺失。减少动态时应当取消装饰性动画和过渡,让元素直接处于终态。滚动行为若设成平滑滚动,也只在用户没有要求减少动态时启用。平滑滚动会让整页内容持续位移,对一部分人来说比局部过渡更难受。锚点跳转、焦点滚动和脚本里的 scrollIntoView 都要走同一套判断,不能只改样式表里的 scroll-behavior。
哪些运动可以保留,看它是不是理解状态所必需,以及幅度是不是很小。焦点环出现、按下时的轻微颜色变化、进度条的数值更新,可以保留。视差、自动轮播、大范围飞入、背景跟随指针移动、整页共享元素的长距离变形,应当关掉或改成瞬间切换。视图过渡接口若用来做路由之间的变形,在减少动态下应退回为直接更换文档,不要保留跨页面的大位移。
视频和动图也算动态。装饰性视频在减少动态下不自动播放,并提供静止海报。信息若只存在于视频画面里,就要有文本稿或静态图系列作为替代,不能假设用户能看完一段自动播放。动图能提供静态帧时,默认显示静态帧,由用户主动播放。
实现时不要在减少动态的分支里把选择器写得过窄。只关掉某个类名上的过渡,后来新增的动画仍会运行。可以在根上对减少动态统一去掉装饰性动画和过渡,再把必须保留的进度动画用更具体的选择器加回来。脚本侧在每次启动定时器和动画帧之前读媒体查询,而不是只在初始化时分支一次。第三方组件若自带轮播,要查它是否暴露暂停,以及是否读取减少动态;不暴露就不要用它承载会自动切换的内容,或在外层包一层自己的暂停和偏好判断。
四条边界怎样同时落在一个轮播上
用一个放置推荐文章的区域当例子。节奏上,若必须切换,每次只做短淡入,不做横向滑过整栏;自动切换的间隔足够读完标题和摘要,而不是按设计稿的速度一闪而过。暂停上,区域边缘有命名清楚的暂停按钮,焦点进入任一链接时自动暂停,触屏下不依赖悬停。可见性上,区域滚出视口或页面进入后台时停止定时器,回来后仍停在同一条,不补跳。减少动态下,不自动切换,不位移,直接展示当前一条,并保留上一项与下一项的按钮,让用户自己换。
这四条任何一条缺失都不算完成。只有暂停按钮、却在减少动态下仍然自动滑过整栏,偏好被忽略。尊重了偏好、却把起始状态留在透明,内容又没了。动画很短、却在用户阅读时自行换页,节奏合适也不能代替暂停。区域不可见时仍在后台翻页,等用户滚回来内容已经变了,可见性没有被考虑。
把边界写进验收而不是写进心情
验收可以写成可以观察的句子。在减少动态打开时,路由切换没有整页位移,滚动定位是瞬间的,装饰视频停在海报上。自动轮播存在暂停按钮,暂停后等待超过一个切换间隔,内容不改变。焦点位于轮播内的链接时,内容不改变。把页面切到后台数秒再回来,显示的仍是离开时的那一项。把动画相关的样式表关掉之后,依赖动画才出现的文本仍然在文档里,并且对比度足够阅读。不透明度被用来隐藏的控件,不在 Tab 顺序中出现。
这些句子不依赖设计工具里的预览。预览几乎总是在没有偏好、没有键盘、页面始终可见的条件下播放。边界是在偏好、焦点、后台和静止终态这些条件下才看得见的。把它们放进每次改动动效时的检查,比再调一版缓动曲线更接近可用。
运动可以保留。它应当短,应当能停,应当在人看不见时休息,应当在系统要求减少动态时退回静止而完整的界面。四条同时成立,动效才是说明,而不是障碍。
见字如晤