在微信小程序里,自定义modal弹窗通常依赖wx.createAnimation生成动画对象,再绑定到view的animation属性上。默认动画是线性或缓动连续变化,看起来比较顺滑,但有些场景需要复古的逐帧跳动感,比如游戏化弹窗、像素风提示框,这时候就要用到steps函数把动画过程切成固定段数。

steps函数是CSS timing function的一种,它接收两个参数:步数和跳跃方式,例如steps(4, end)表示把动画分成4段,在每段结束时跳变。小程序基础库从很早期就支持在animation的timingFunction里写steps,不过只能以字符串形式传入,不能像web那样直接写cubic-bezier组合。
为什么steps能实现逐帧效果?因为普通缓动是在每一帧计算中间值,视觉连续;steps则在每个步长内保持起始值不变,到了步长边界瞬间切换到下一段值,于是原本0.5秒的缩放变成了4次突然放大的跳变,人眼就会觉得是逐帧播放。这种手法在像素艺术和复古UI里非常常见。
一、steps函数的参数与跳跃方式
steps的第一个参数是正整数,代表切割段数。段数越多,跳变越密集,看起来越接近连续;段数越少,顿挫感越强。比如steps(1)等于没有动画,steps(2)就是两帧切换。第二个参数有start、end、jump-none等,但小程序实测仅start和end稳定支持,start表示步长开始时跳,end表示步长结束时跳。
以缩放为例,modal从scale(0.2)到scale(1),用steps(5, end),会在0%、20%、40%、60%、80%时间点保持原值,到对应结束点突然变大,最后在100%完成。如果换成start,则在0%就先跳到20%大小,视觉上出场更快。开发者要根据弹窗内容节奏选,文字少的用end更稳,图形多的用start更有冲击力。
二、小程序modal中使用steps的写法
在js里先创建animation对象,设置duration和timingFunction为steps字符串。下面是一段常见代码逻辑:this.animation = wx.createAnimation({duration: 600, timingFunction: 'steps(6, end)'}); 之后用this.animation.scale(1).translateY(0).step(),最后this.setData({modalAnim: this.animation.export()})。
wxml中把modal容器绑定animation="{{modalAnim}}",初始样式设为transform: scale(0.3) translateY(100%)。这样弹窗出现时就会分6帧从下方缩小状态跳到正常位置。要注意step()方法每次调用会生成一个动画片段,如果连续step不加delay,steps会作用在整个链式过程,若想不同属性不同步数,需要拆成多个animation对象。
| 需求场景 | steps写法 | 视觉效果 |
|---|---|---|
| 轻微顿挫提示 | steps(3, end) | 三帧缓跳,不突兀 |
| 像素风弹窗 | steps(8, start) | 八帧硬切,复古感强 |
| 快速确认框 | steps(2, end) | 两帧出现,响应利落 |
三、真机调试的常见问题与规避
不少开发者在模拟器看到逐帧正常,真机却变成连续动画,多是基础库版本差异导致timingFunction字符串被忽略。解决办法是锁定基础库最低版本为2.10以上,并在onReady里延迟一帧再export,避免渲染时机过早。
另一个问题是帧率不稳,因为小程序渲染管线在后台会降频。若modal出现卡顿,可把duration调至500毫秒内,步数不超过8,同时避免在动画期间执行setData其他大数据。用微信开发者工具的trace面板能直接看到每一帧耗时,确认steps是否真的按段跳变。
四、结合关键帧补足细节
如果单纯steps不能满足,比如想要跳变中夹一点回弹,可以用小程序不支持的keyframes思路替代:把动画拆成多个step段落,每段之间用普通ease短过渡。虽然代码变长,但兼容性好,所有机型一致。
实际项目中,自定义modal往往还要覆盖蒙层渐显。蒙层用普通opacity过渡,内容用steps跳入,两者叠加既有背景柔化又有主体复古,是性价比很高的做法。掌握steps之后,你还能把它用在tabbar切换、榜单翻牌等地方,统一产品的动效语言。