Flinto底部弹出动效教程:手势拖拽、缓动曲线与滚动冲突处理
做移动端原型的人大概都遇到过这种场面:评审会上有人指着静态稿问"这个筛选面板弹出来是什么感觉",你翻遍设计稿也说不清,最后只能用手比划一句"就是从底下唰一下上来"。这种时候,像 Flinto 这类专门做交互动效的软件就该上场了。这次我拿最典型的底部弹出效果做拆解——点一下按钮,半透明遮罩把背景压暗,一块圆角面板从屏幕底边滑上来,手指按住能往下拽,拽过一定距离松手就自己收回去,点遮罩也能关掉。
这东西看着是动效里最简单的一类,真到 Flinto 里一帧一帧抠,坑其实集中在三个地方:图层结构怎么分、手势和滚动打架怎么解、缓动节奏怎么调才不像 PPT 动画。下面我按自己实际动手的顺序写,从状态怎么切、Behavior 怎么建,一直讲到拖拽跟手怎么做、参数怎么算、松手判定怎么设,尽量让刚摸这软件的人能照着做出来,也让已经会做转场但总觉得"弹出得有点假"的人找到差在哪。
1. 底部弹出效果到底在解决什么问题
1.1 为什么移动端偏爱"从下往上"这一手
先说清这个模式为什么成立。手机屏幕是竖的,人手握持时拇指最舒服的活动区域在屏幕下半部,顶部两个角是公认的难够到区。把一层新内容从底部推上来,等于把操作焦点主动送到拇指跟前,用户不需要抬手、不需要改变握姿,看完还能顺手往下推回去。这是纯粹的人机工程学考虑,跟审美无关。
再一层是方向语义。界面里的方向不是随便定的,"从哪来、回哪去"是用户潜意识里的空间模型。底部弹窗从屏幕底边出生,关闭时原路返回消失在底边,用户的大脑会自动把这块内容和"临时、可召回、不属于当前页面"这几个标签绑在一起。反过来,如果你让它从右边滑进来,用户会本能地以为这是页面层级的跳转,看完想按返回键。
还有一个常被忽略的点:位置暗示了层级的高低。底部面板天然带有"覆盖但不打断"的意味——它压在当前页面上面,但当前页面还在,遮罩半透明,用户知道底下的东西没消失。这套语义在 iOS 和 Android 上都是系统级惯例,用户零学习成本。所以做原型的时候,这套方向语义不能被"我觉得从上面掉下来更好看"给破坏掉,那属于设计师自嗨,不是产品决策。
1.2 Flinto 在这类动效里的定位:它适合做哪一段活
很多人搞不清 Flinto 到底该在哪一步用,我见过有人拿它画视觉稿,也见过有人指望它直接出可上线的代码,两头都走偏了。它真正的价值区间,是把静态设计稿变成"能上手摸、能点、能拖"的高保真动效原型,用来做内部评审和给开发看效果预期。
对比几种常见做法就更清楚了。纯用视频剪辑软件做动效演示,缺点是只能播不能玩,评审时没法验证"点遮罩能不能关"这种真实交互;直接写代码做 Demo,成本高到不现实,何况很多还在方案探索阶段;用通用原型工具呢,做静态跳转没问题,但一旦涉及手势驱动、弹簧曲线、转场随手指插值这类细节,表达能力就吃紧了。Flinto 恰好卡在中间:它有状态机式的 Behavior 机制,能表达"关闭态 / 打开态"这种双状态结构;有独立的手势触发器,能挂拖拽;有可视化的缓动和弹簧参数面板,改一个数字就能立刻预览到效果。
选它做底部弹窗的核心理由,是它能表达"过程的中间态"。这个太重要了——底部弹窗最考验功力的不是打开那一下,是手指按在面板上往下拖、拖到一半又松手弹回去的那段过程。静态工具做不出来,视频工具演示不了,只有这类带状态机的手势驱动工具能如实模拟。
1.3 拆解成品:一个合格的底部弹窗到底有几层
动手之前得先把结构想清楚,否则做到一半发现图层关系错了,返工成本极高。我习惯拆成四层,从上到下说。
第一层是遮罩,一个铺满整个屏幕的矩形,纯黑或深灰,初始透明度为零,打开时涨到一个特定值(常见 0.35 到 0.5 之间)。它的职责是压暗背景、引导视线,同时承担"点击空白处关闭"这个交互。
第二层是面板容器,一个圆角矩形,宽度通常等于屏幕宽(或左右各留 8 到 16 个点的间距做成悬浮卡片式),圆角一般给到上边两个角 16 到 24。这个容器只管形状和背景色,不承担任何内容。
第三层是内容层,贴在容器内部,可以是列表、表单、筛选条件。如果内容超过面板高度就要挂滚动。这一层是最容易出问题的地方,后面会专门讲手势冲突。
第四层是把手和关闭按钮这类视觉提示。把手就是顶部那条几十点宽、四五点高的小灰条,作用是告诉用户"这玩意能拖"。有些方案会省掉,但我建议留着,它比任何文字提示都直观。
2. 上手前的结构与命名准备
2.1 图层分组与命名:决定后面好不好改
从设计工具导入图层时,最怕的就是一堆零散的未命名分组。导入进来之后你随便加一个动画、改一个位移值,就可能牵动半个面板跟着乱跑,找问题要找半小时。所以导入前务必在源文件里把结构整理干净,命名用能一眼看懂功能的名字,比如 mask、sheet-container、sheet-content、handle,别用什么 Rectangle 12、Group 3 这种系统默认名。
分组的逻辑我建议按"动什么、不动什么"来切。打开的瞬间,遮罩的透明度要变、面板容器的位置要变、内容层跟着容器整体位移;而对于容器内部的内容,如果不是刻意要做"内容延迟入场",它不该有自己独立的动画。把内容和容器绑在同一组里,动画一次性配完,出错概率低得多。
还有一个小习惯,我强烈建议保持:所有和这个弹窗相关的图层,统一丢进一个顶层分组里,命名带着统一前缀。原因很简单,Flinto 的图层列表一旦超过几十个,靠滚动找目标图层非常痛苦,有前缀就能连着选中、整组隐藏、整组复制到另一个屏幕复用。这个习惯在你需要做第二版、第三版方案对比的时候能救命。
2.2 画布尺寸、底部安全区与内容高度
尺寸基准建议直接按目标机型的逻辑像素来,别用缩放后的比例。常见的基准就是 375×812 这一档,宽度和高度都按真实值来设,这样后面算位移、算圆角、算内边距,数值和设计稿完全对得上,交付时也不用换算。
底部安全区是新人最容易忘的一环。全面屏机型底部有一条横条指示器,系统会占用大约 34 个点的高度,你的面板里如果有按钮或者输入框,贴着屏幕最底边放就会被遮住一部分。处理办法是给内容层底部留出这段内边距,视觉上看不出突兀,但真机上不出事故。我一般直接在面板内容底部加 34 的空白,按钮往上提。
面板高度的定法有讲究。全屏高度弹窗那种"几乎占满"的做法,会让人产生页面跳转的错觉,丧失"可覆盖"的语义。我的经验区间是屏幕高度的 40% 到 75% 之间,超过 85% 就不如直接做页面跳转了。具体数值应该由内容决定——内容列表有五行就给能装下五行加标题加把手的高度,别硬凑一个整数。以 812 高的屏幕为例,如果面板定 420,那打开后面板顶边就落在 812 减 420 等于 392 的位置,屏幕上方还留 392 的可见区域,这个比例用户能清楚看到底下是被压暗的原页面,层级关系一眼分明。
2.3 建 Screen 还是建 Behavior:一个关键判断
这是刚上手时最常犯的结构性错误。Flinto 里有两套东西:Screen 代表一个页面,Behavior 挂在 Screen 下面代表页面内的局部交互。有人一看要"弹出一个新东西",立刻新建一个 Screen 然后把底部面板画进去,用页面转场来连接,结果做出来的效果怎么调都不对。
正确做法是:底部弹窗建在当前 Screen 的一个 Behavior 里。因为它在语义上就不是新页面,用户关掉之后回到的还是同一个页面、同样的滚动位置、同样的状态。如果建成新 Screen,页面转场机制会顺带把原页面也做一些默认处理,滚动位置丢失、返回动效叠加,一堆莫名其妙的副作用。
判断标准很简单:这个东西关掉之后,底下页面的状态要不要保持原样?要,就建 Behavior;不要,属于页面级跳转,才考虑 Screen。底部弹窗、气泡提示、下拉刷新、Tab 切换,全是 Behavior 的活儿。养成这个判断习惯,后面能省掉大量排查时间。
3. 从零搭出可拖拽的底部面板
3.1 第一步:把两个状态摆好
Behavior 建好之后,第一件事是把两个状态摆到位。关闭态是初始状态,此时遮罩透明度为零,面板整体位于屏幕底边之下——注意是"完全在屏幕外",不是"贴着底边",否则你会在真机预览时看到面板的边缘露在屏幕下面一条缝。
位移量怎么算?刚才定的面板高 420,屏幕高 812,面板顶边在屏幕外时的位置就是 Y 等于 812。打开态的 Y 则是 812 减 420 等于 392。两者的差值 420 就是整个动画的位移距离,这个数字后面算时长和速度要用到,先记下来。
这一步有个细节必须注意:位移的基准点要统一。Flinto 里的位置参数是相对于父级容器的,如果你把面板放在了某个带偏移的分组里,那么读数会和你预期不一致。我的做法是让面板容器的父级就是画布根层,所有位置参数直接看绝对值,不绕弯子。要是必须嵌套,就统一在同一个父级下操作,别一半改父级一半改子级,那样调起来全是玄学。
3.2 第二步:Tap 触发打开,转场里只动该动的属性
在关闭态的触发按钮上挂一个 Tap 交互,指向打开态,转场类型选最朴素的那种"无预设动画"的溶解式转场。为什么不直接用现成的滑入预设?因为预设会同时处理位移和透明度,容易把你不想动的属性也带进去,比如不该消失的内容被淡出了。从零手配动画属性,每个数值都在自己掌控里,改起来心里有数。
转场里需要加的动画,按优先级只有三条。第一条是面板容器的位置动画,从 Y 812 到 Y 392。第二条是遮罩的透明度动画,从 0 到 0.4 左右。第三条如果做了把手的高亮或者面板圆角变化,再加一条。除此之外什么都别加,尤其是不要给内容层单独加动画,除非你确实想做那种"面板先上来、内容后滑入"的错位感。
发生顺序上有个反直觉的点:面板的位移通常要比遮罩的淡入慢一点。用户的心理预期是"面板是被点出来的主体",遮罩只是陪衬,陪衬先到位、主体随后缓入,节奏上才有主次。如果两者同时结束,会显得整体很"平",像两个无关的东西一起动了。
3.3 第三步:遮罩的淡入与点击关闭
遮罩的最终透明度给多少,这个数值我踩过坑。给 0.6 以上,底下的原页面几乎看不见,用户会觉得"这是个新页面吧",覆盖语义没了;给 0.2 以下,压暗效果太弱,面板和背景糊在一起,边界不清。实测下来 0.35 到 0.45 是比较稳的区间,浅色界面偏 0.3 到 0.4,深色界面可以再低一点,因为深色本身就有压暗效果。
遮罩上挂一个 Tap 交互指回关闭态,这是必须的。少了它,用户只能通过拖拽关闭,可发现性大打折扣——大部分人不会主动去试"能不能拖"。同时注意把遮罩的点击区域覆盖完整,别只覆盖了可见部分,有时候遮罩做了裁切或者尺寸设小了,边角位置点了没反应,用户会觉得软件卡了。
还有一个体验细节:点击遮罩关闭的时候,面板要不要一起动?答案是必须一起,而且要走和打开时完全对称的反向动效。这一点在 Flinto 里靠"反向转场"就能白拿——你只要把打开那条转场配好,从打开态回关闭态的交互选中那条已有转场反向播放,所有属性动画会自动倒放,位置、透明度全部对上,不需要重新配一遍。这个机制能省掉至少一半工作量,务必用上。前提是打开和关闭的时长要一致,如果时长不同,反向播放会显得节奏突兀。
3.4 第四步:缓动曲线的选择与参数计算
这里是决定"像不像"的分水岭。默认的线性缓动,位移速度全程恒定,观感上非常机械,像电梯门在匀速平移。现实中任何被手推动的东西都不是匀速的——起步有加速、临近终点有减速,减速段通常比加速段更长,这是物体带阻尼运动的天然特征。
所以打开动画的曲线应该用减速型,也就是前快后慢。如果软件里给了预设,选带"减速"语义的那一档;如果支持自定义贝塞尔,我常用的一组控制点大概是起步时斜率较陡、末端缓慢收敛的形状。下面这组数值可以当作起点,交付给前端时也能直接抄:
/* 底部面板上滑:减速型,末端收敛 */ --sheet-ease-out: cubic-bezier(0.32, 0.72, 0, 1); /* 遮罩淡入:稍微更快收敛一点,让压暗先到位 */ --mask-ease-out: cubic-bezier(0.4, 0, 0.2, 1); --sheet-duration: 320ms; --mask-duration: 240ms;时长怎么定?我用一个简单粗暴的算法做校验:位移距离除以时长,得到平均速度,再看这个速度在人眼舒适区间里合不合适。刚才算的位移是 420 点,如果时长取 320 毫秒,平均速度就是 420 除以 0.32,约等于每秒 1312 点。这个量级在移动端的观感是"干脆但不突兀"。如果时长缩到 200 毫秒,平均速度飙到每秒 2100 点,动效会显得很急、像被弹出来的;如果拉到 500 毫秒,平均速度只有每秒 840 点,整个动作会显得拖沓、老气。
所以我的经验区间是:小面板(高度 300 以内)用 250 到 280 毫秒,中等面板(300 到 500)用 300 到 350 毫秒,大面板(500 以上)用 350 到 420 毫秒,再长就该考虑分段动效了。这个算法的好处是它不依赖感觉,换个人来算也能得出相近结论,团队里定规范的时候好用。
3.5 第五步:拖拽跟手与松手判定
最后这块是整个效果里技术含量最高的。目标是:手指按住面板往下拖,面板实时跟着手指的位置走,拖多少跟多少,没有延迟;松手的时候判断——拖得够远就顺势关掉,拖得不够就弹回原位。
Flinto 里给面板容器挂一个拖拽手势,方向限定为向下。关键是这个手势要走"手势驱动的转场",也就是面板位移和手指位移做实时映射,而不是松手之后才播放一段预设动画。这两种做法的观感天差地别:前者是"你拽着它",后者是"你告诉它该动了",用户一上手就能感觉出来。
跟手的比例可以调,常见是 1 比 1,手指拖多少面板走多少。有些方案会做阻尼,越过边界之后位移速度减半,制造出"拽到头了"的阻力感。这个细节对质感提升很大,尤其是面板往下拖过某个点之后开始变沉,用户的手指会自然收到"到头了"的反馈,比生硬地卡住舒服得多。
松手判定用两个条件叠加,别只看位移。第一个条件看距离:拖拽距离超过面板高度的 25% 左右就判定为关闭,否则回弹。第二个条件看速度:如果松手瞬间手指还在快速下划,哪怕距离只到 15% 也应该关掉,因为用户意图很明确就是"甩掉它"。只按距离判断会显得迟钝,只按速度判断又会让轻轻一碰就误关。两个条件做或运算,命中任一就关闭。
注意:面板不同版本里拖拽行程、判定的菜单叫法会有出入,参数位置大致在手势属性或者转场属性里,找的时候优先看跟"距离""阈值""跟手"相关的选项,别被名字绕晕。
4. 让动效"有质感"的细节调优
4.1 遮罩与位移的节奏配比
前面提过遮罩要比面板快,但"快多少"是有讲究的。我试过三种配比,效果差异挺明显。
第一种是完全同步,两者同起同落。观感是"整体在动",像贴图平移,很平,但也不算错,适合极简风格。
第二种是遮罩先起、面板稍后。给面板的位移动画加 40 到 60 毫秒的延迟,遮罩立刻开始淡入,面板稍等一下再上来。这样做的效果是"压暗先行、主体随后入场",层次感明显更强,用户的眼睛会先感知到背景变暗,接着注意力被吸引到上来的面板上。这个 40 到 60 毫秒的延迟量很微妙,多了会显得两件事没关系,少了又看不出差别,建议在这个区间里微调后定死。
第三种是面板先起、遮罩稍后。这个组合我基本不用,因为背景还没来得及压暗,面板就已经闯进来了,视觉上会觉得"多了个东西"而不是"打开了一层"。
时长方面,遮罩因为只做透明度变化、位移距离为零,时长短一点不会显得突兀,240 到 260 毫秒足够。面板因为位移距离长,按前面那套速度算法,300 到 350 毫秒比较稳。两者结束时间不用刻意对齐,稍微错开反而更自然。
4.2 弹簧参数的调法
如果这个软件的动画类型里带弹簧模式,一定要试。弹簧和缓动曲线的区别在于它能产生"过冲"——物体冲过目标位置一点点,再弹回来,这个微小的回弹是真实感的重要来源。
弹簧参数一般有两个旋钮,一个管"劲大不大",一个管"散得快不快"。直白点说:劲越大,冲过头的幅度可能越大、动作越猛;阻尼越大,晃动衰减越快、回弹次数越少。调的时候我按这个顺序来。
先把阻尼放到比较高的位置,让整个动作干脆不晃,只保留一点点尾声,确认面板不会冲出屏幕底边——这个必须检查,因为过冲是双刃的,往上冲那侧如果没有约束,面板顶边可能会冲出屏幕外,露出底下的空白,非常难看。
然后慢慢调劲道,从偏软开始往上加,加到"看起来有力量但不过火"为止。判断标准是:打开动作完成的那一刻,面板应该有一个几乎察觉不到的下沉回稳,而不是明显地"砸"到位。如果一眼就能看出在回弹,说明过冲太多,该加阻尼了。
还要注意,弹簧不一定只能用在位移上。遮罩的透明度如果也用弹簧,会出现透明度短暂超过目标值的情况,这在某些场景下是瑕疵(本该 0.4 的遮罩冲到 0.5 再回落),所以透明度这类有明确上限的属性,老老实实用缓动曲线,别凑热闹。
4.3 圆角、阴影与边缘处理
这三样是纯视觉的,但对"高级感"的贡献相当大,而且成本极低。
圆角:容器只做上面两个角的圆角,下面两个角是直角,因为面板底部贴着屏幕底边,做了圆角反而会露出背景。圆角半径 16 到 24 之间,跟整体设计语言保持一致。如果做成悬浮卡片式的(左右留白),四个角都要圆。
阴影:面板顶部建议加一层很淡的向上阴影,颜色用黑、透明度很低、模糊半径大、Y 轴偏移为负。它的作用是让面板和下面的原页面之间产生厚度差,面板像是浮在上面的一张纸,而不是从底边长出来的一块。这层阴影很容易做过头,透明度给到 0.08 到 0.12 就够,多了会显脏。
边缘处理还有一个容易忽略的点:如果面板顶部有内容紧贴着边缘,比如标题文字,注意别让它撞上圆角的弧线。视觉上留出至少 16 个点的水平内边距,标题和圆角之间才不会别扭。
4.4 内容滚动区和拖拽手势的冲突
这块是重灾区,我敢说十个做底部弹窗的人里有八个在这栽过。矛盾在于:面板要响应"向下拖拽关闭",但面板里的内容如果比面板高,它又要响应"上下滚动"。同一根手指,同一个方向的滑动,到底听谁的?
常见的错误做法是把两个手势都挂在同一个容器上,结果是拖内容的时候面板跟着跑,或者拖面板的时候内容在滚,两种都很难受。
我的解决方案是分区承担。面板顶部留出一块大约 48 到 56 高的区域,只放把手,这块区域专门承担拖拽关闭的手势,不响应滚动。内容区的滚动交给滚动容器,且滚动方向上做限制,让它在内容到顶、继续往下拉的时候不把滚动事件传导出去。用户的心理预期其实很清楚——抓住顶部那条把手拖,是关;在内容区滑动,是滚。分区之后两件事互不干扰,学习成本也低。
还有一种情况要处理:内容不够长,根本不需要滚动。这时候整个面板都可以承担拖拽,体验更连贯。所以做的时候可以按内容高度分两套逻辑,内容短就全区域可拖,内容长就顶部把手区可拖。
提示:做完一定要在真机上试。模拟窗口里的鼠标拖拽和真实手指的滑动差异很大,尤其是纵向滚动的判定阈值,鼠标感觉不出来的问题,手指一碰就暴露。
5. 常见问题与排查实录
5.1 现象、原因、处理 速查表
下面这张表是我自己踩过、也帮别人排查过的典型问题汇总,遇到不对劲先扫一遍,能省不少时间。
| 现象 | 大概率原因 | 处理办法 |
|---|---|---|
| 面板打开了但底下背景没变暗 | 遮罩层被放在了面板图层的上方,或者遮罩尺寸没铺满 | 把遮罩移到面板之下,检查宽高是否等于画布尺寸 |
| 面板边缘露出一条缝在屏幕下方 | 关闭态的位置算错了,没完全移出画布 | 关闭态 Y 值设为屏幕高度,别用"接近屏幕高度" |
| 点遮罩没反应,点上去什么都不发生 | 遮罩的点击区域被面板或者其它透明层挡住了 | 检查图层顺序和点击区域重叠,把遮挡层设为不接收点击 |
| 关闭之后回不到原页面,跳到别的画面上 | 底部弹窗被建成了独立 Screen,走了页面转场 | 改成在当前 Screen 内建 Behavior,用状态切换 |
| 拖拽跟手但松手后不知道去哪 | 缺少位移阈值和速度判定 | 补上距离 25% 左右和甩动速度两个条件 |
| 面板上滑时内容跟着抖一下 | 内容层被单独加了动画,和容器动画打架 | 删掉内容层独立的位移或透明度动画 |
| 弹簧动画时面板冲出屏幕 | 过冲量太大,缺少上限约束 | 加大阻尼,减少劲道,或改用减速曲线 |
| 滚动内容和拖拽关闭互相干扰 | 两个手势挂在同一个容器上 | 分区处理,顶部把手区专管拖拽 |
5.2 预览端和设计端不一致怎么办
这个问题极高频。你在编辑器里调到自我感觉良好,连着手机一看,节奏完全不对——要么快了,要么面板颜色发灰,要么滚动卡顿。
第一个排查方向是预览链路本身。真机预览靠的是同一局域网内的连接,网络质量会直接影响帧率,卡顿的时候人眼会误判成"动效不流畅"。所以判断节奏之前,先确认预览画面本身是流畅的,别把网络问题当动效问题改。
第二个方向是机型差异。你在 375 宽的基准上按比例调好的数值,换到大屏机上逻辑像素变了,视觉观感会跟着变,面板显得更扁、动效显得更快,因为屏幕更大、位移距离相对更短。解决办法是准备好两套尺寸的基准页面,或者至少在目标机型尺寸的画布上核对一遍关键数值。
第三个方向是颜色。半透明遮罩叠在不同背景上,成品颜色会不一样,编辑器里的背景可能是纯白,真机上的页面可能是浅灰,遮罩叠上去的效果会差一档。核对的时候要在真实页面内容上盖遮罩看效果,别在空白画布上判断。
5.3 交付给开发时该带哪些东西
原型做完不是终点,得让人能照着实现。我的交付清单一般包括四样。
一是动效录制,把打开、拖拽中、松手回弹、点击遮罩关闭这几个状态都录一遍,最好是慢放版本加正常速度版本各一份,慢放用来看清细节顺序。
二是参数清单,位移距离、时长、缓动曲线控制点、遮罩透明度、判定阈值,全部写成表格,别让开发去视频里估数值。
三是状态图,标明哪两个状态之间怎么切换、由什么触发、用哪条转场。这一项很多人省掉,结果开发实现出来两个方向的动效不对称,回头还要返工。
四是边界说明,明确写清楚哪些是可调的(时长、圆角、透明度可以按设计规范微调),哪些是不可动的(手势方向、分区逻辑、判定条件的组合方式),因为后者涉及交互逻辑,改了就不成立。
最后分享一个我自己反复验证的小习惯:把这个底部弹窗的单次动效时长卡在 320 毫秒上下、让遮罩比面板早结束一点点、拖拽判定同时看距离和速度,这三个数定下来之后,几乎不用再大改,后面所有类似的底部面板、筛选抽屉、操作菜单都能套同一套参数,一致性也就顺带解决了。