前段时间研究了一阵子原型工具,有些自己的思考,想分享给大家。主要从抽象的角度研究一下:原型是什么?原型工具的能力边界在哪里?
对于原型工具,每个人都会有自己的具体印象。但在这里,我想对原型工具作一番抽象的分析,然后希望,
原型用于展示产品的界面和交互,要素有 2 个,用户的操作(触发),不同的界面显示(状态切换)。
所以,我对原型工具的抽象就是:触发+状态切换。
这个抽象太普通了。不过你应该很难找出哪个原型不符合这个定义。
但定义太抽象也有坏处,比如,真实的产品也符合“触发+状态切换”的抽象。那么真实产品也要算作原型吗?按直觉肯定不行。原型和产品应该是有一条分界线的。
所以在这个抽象定义里面,还要加上一些条件,用来区分原型和产品。加什么呢?
我考虑的是从制作原型的工具入手,给它加 2 个条件
第 2 条容易让人疑惑,先解释一下。它具体指的是原型包含的页面的数量、页面中显示元素的数量、元素属性的数量、制作过程作出的一些操作数量等等。这些数量一旦呈现爆炸性增长,工具本身也没法用了。
这 2 条限制看起来很普通很符合直觉,让人没有啥可说的。所以,我就要下定义啦:
原型的抽象就是,“触发+状态切换”,要求它可以被加了上述 2 个限制条件的原型工具制作出来。
等等,
Q. 为什么这就是原型的抽象定义呢?
A. 首先这个抽象的定义肯定是涵盖了所有原型的,所有原型都符合这些条件。然后真实产品是不能被加上了上述的 2 个限制条件的原型工具制作出来的,也就是说这个定义将原型和产品区分开了。
Q. 可是这定义太抽象太宽泛了吧,有什么用呢? A. 看下面的分析吧。。
大家可能会对“数量组合爆炸”这个词耿耿于怀,它到底有啥特别的呢? 先来看看真正的产品有什么特征?首先,产品的数据量和交互路径组合起来的数量是会爆炸的。然后,为了解决这个爆炸数量,要用编程。最简单的例子,从“一个列表中搜索出包含指定文字的条目”,这个小产品,待所有的列表数据有无限种可能,输入的指定文字也是无限种可能。试问原型工具怎么能实现它呢?想实现它,要么加入编程的功能,要么允许原型组合爆炸。
肯定有人会有疑问,界面上哪会出现组合爆炸的情形呢? 下面我就举个例子。假设页面 b 上有 10 个开关 switch,要求根据这些开关每个不同的状态组合,切换到另外一个不同的页面。那么页面 b 有多少个状态组合呢? 2^10=1024 个。
如果觉得根据 10 个开关的状态切换到不同的页面显得太脱离实际,那让我们来考察一下“组件(Component)”这个概念的由来,你就会发现组合爆炸确实存在。
一般认为定义组件(比如开关,复选框,下拉列表等)是为了复用,这点没错。但换个角度,从状态切换的角度来看,会有新的发现: 想象包含 10 个独立开关(switch)的某页面,在点击第一个 switch(以下简称 switch1)时,切换其开关的显示状态。如果不使用组件的概念,而是仅仅使用切换页面的方式来实现,会发生什么呢? 考虑到 10 个开关都是独立的,那么其他 9 个开关的状态可能是, 关,关,关,。。。 开,关,关,。。。 关,开,关,。。。 开,开,关,。。。 这 2^9=512 种情况中的任一种,所以如果要用切换页面的方式来改变 switch1 的显示状态,需要考虑这 512 种情形,对于每种情形都要各画 2 个页面,1 个页面上 switch1 是打开状态,另 1 个则是关闭状态,然后再给这 512 对页面添加点击 switch1 时的页面相互切换效果。
这,就是所谓的组合爆炸。
那么实际中为什么没有发生这么令人尴尬的情形呢?
因为这些组合爆炸是可以消除的。比如 switch1 由关闭状态切换到打开状态时,其他 9 个开关状态任意组合,一共构成了 512 对不同页面的切换。但这所有 512 个页面切换中,其他 9 个开关状态都是保持不变的,所以这 512 个状态变换,可以合并为一个变换,用一句话来描述就是:switch1 由关闭状态切换到打开状态。512 个页面的全局状态变换,合并成了 switch1 的局部状态变换。
源自于它自己,也只影响它自己,它是独立的。这就是从状态切换的角度观察,组件的定义和由来。而为了避免组合爆炸,组件也确实有存在的必要性。
其实页面的抽象也是一个道理:其他页面的所有元素无论处于怎样的状态组合,本页面的初始状态和状态变换都是相对它们来说独立的。
一个页面切换到另一个页面,从状态切换的角度,很容易理解。比如从包含 10 个独立 switch 的页面 b 切换到页面 c,尽管切换时,页面 b 的状态可能是 1024 种组合状态中的任一种,页面 b 到页面 c 的切换,其实包含了 1024 种可能的状态到 1 个状态的切换,但显然,这 1024 种状态切换可以合并为 1 个:页面 b(无论在何种内部状态)切换到页面 c 。
上面的例子看起来似乎有点说服力不足,但它很好的说明了组合爆炸能被消除的原因。不信换个例子看,
还是从页面 b(包含 10 个 switch)切换到另一个页面 d,这次要求,页面 d 内包含着 20 个开关,与页面 b 内的 10 个开关是 2 对 1 的关系,每 2 个开关与页面 b 中对应的 1 个开关的状态保持一致。虽然从字面上看,仍然是页面到页面的切换,但显然,与 b 到 c 到切换是不同的:b 的 1024 个状态切换到 d 的对应状态,这些状态切换不能再简单的被合并为 1 个状态切换了。
那么是否这 1024 个状态的切换就无法合并呢?仔细观察一下,因为页面 d 的 20 个开关与页面 b 的 10 个开关是 2 对 1 的独立对应关系。这意味着,可以像组件的剥离一样,将页面 b 中的每个开关的状态对页面 d 中对应开关的影响剥离出来。从而页面 b 到页面 d 的状态切换,就被分解成了 10 组独立的、局部的状态切换,从而消除了组合爆炸,
从上面几个例子可以看出,组合爆炸能被消除的根本原因,就像前述的组件、页面、局部状态切换一样,是因为多个状态切换是可以合并为一个状态切换的。也只有在这种情况下,组合爆炸才能消除。
再就页面切换举一个常见的例子:从某个页面切换到另一个页面后再返回原页面。 还拿上面的页面 b 切换到页面 c,然后再返回页面 b 。因为页面 b 本身可以有 1024 种开关组合状态,所以从页面 c 返回页面 b 时,要指定到底是返回哪个状态。这时如果原型工具本身不支持保存页面 b 切换时的状态,或者说从其他页面返回 b 页面时,会返回页面 b 的原始状态。在这种情况下,c 怎样才能返回 b 切换到 c 时的状态呢?似乎没有好的办法,我们只好手动建好页面 b 所有可能的 1024 种状态对应的页面,然后每个页面都切换到页面 c 的一个拷贝(也是 1024 个页面),这样就可以完美返回了。这同样是组合爆炸的情形——只是因为原型工具不支持!原型工具只要保存一下在离开某个页面时该页面的状态,就可以简单地避免组合爆炸。
可见是否会出现组合爆炸,不仅仅在于多个状态切换是否能够合并成一个状态切换。即使状态切换能够合并,也需要原型工具本身能够支持定义合并后的状态,并且能处理合并后的状态切换。上面的“返回上一个页面离开时的状态”例子中,“离开时的状态”就是一个典型的合并后的状态定义,如果原型工具不支持这种定义,那么就会出现组合爆炸。
前面谈了离散的状态切换。比如点击、页面间的状态切换、组件内部状态切换。其实触发和状态,不只有离散,还有连续的。否则鼠标拖拽、滑动条、甚至时间动画,这些就都不能处理了,那些流畅的切换和动效也不复存在了。
而连续则意味着有无数个状态,明显是数量爆炸了。怎样处理这么多数量的状态呢?做过动画的都知道制作动画可以用关键帧,对于线性动画,给定起点和终点,中间的无数个位置都是可以插值得到的。
所以,处理连续导致的组合爆炸,一般来说也是用插值实现。
举个例子,一个进度条,在 2s 的时段内从 0%加载到 100%。简单的几个数字,就定义好了无数个时间到状态的切换,这可以看作是无数的状态切换,被合并为了一个状态切换。
类似的例子,拖拽页面的滑动条,页面随之上下滑动。实现这个效果,也只需要给定滑动条的坐标值与页面坐标值的一个映射。而定义这种映射,同样也只需要滑动条的起止位置、页面的起止位置这几个数字而已。
看到这里,你可能会说,这种定义插值的方法,不就是定义了一个映射函数吗?不是说好不能加入编程功能吗?当然可以这么理解。不过一个更宽容的理解方法是:
既然原型工具可以普遍定义离散的状态,并定义离散状态切换的起始状态和终止状态。那么为什么不允许它能定义一种连续状态的切换呢?而定义 2 个连续状态对应的切换,还有什么比线性映射更简单直接的呢?
综上,在不引入编程的情况下,原型工具能否实现某些交互效果,一方面取决于交互效果涉及到的状态切换,能否简化成单对单的状态切换,从而避免数量的组合爆炸,另一方面取决于该原型工具是否支持对合并后的状态进行定义和操作。
这里特别提一下条件判断。因为有时状态切换是有条件的,比如 10 个 switch 中某几个处于打开状态时,才允许跳转到其他页面。这时,因为这种条件可以简单的用一个状态来描述,不存在状态组合爆炸的问题,所以,只要原型工具支持,这种是很容易实现的。不过很遗憾的是,大部分原型工具都不支持条件判断。
为什么说很遗憾呢?
这就又要回到文章的主题:原型工具的能力边界。
前面论证了,原型不是真实产品。因为原型工具无法处理真实产品的复杂逻辑(数量太大,组合爆炸),那么原型工具能够做到的最好效果,就是把产品的所有操作路径、业务逻辑都至少能走一遍,形象的说,真实的产品可以处理无穷的组合输入,并给出输出。而原型不能也不需要做到那样,它只需将某个场景下无穷个的输入组合中,选取一组,然后演示该场景的产品逻辑即可。
要达到这个目的,只有使用预设的数据、预设的操作路径。这时候,条件判断就很重要了——虽然用户可以在界面上流畅无障碍地做各种界面操作,但一旦要触发执行某个业务逻辑,比如“删除”某条数据,那么由于业务的复杂性,我们不能允许用户任意删除数据,而只能是预设好的那条。然后再把预设好的处理过后的数据在界面上呈现出来。所以这里就要使用条件判断,确定用户是否执行的是指定的操作,如果不是,可能还要提醒用户。
简单说下我尝试过的原型工具吧,把上面的抽象定义投射到具体的工具上:
Figma
————
有页面(Page)、帧(Frame)、局部覆盖层(Overlay)的概念。
其中 Overlay 起到的作用,类似于前面说的组件的作用,即只是对单个页面的局部进行独立地修改,而不影响页面中的其他元素。
但不支持单个触发事件定义多个动作。比如点击某个元素时,同时显示 /隐藏多个元素。这可能会导致状态的组合爆炸。比如 10 个 switch,每个 switch 都可以独立切换。但开关第 10 个 switch 时,会同步修改前 2 个 switch 的开关状态。这种情况下,由于不支持点击事件时同时定义多个动作,导致不得不另画单独的帧(Frame)并设置为切换后的状态。由于 switch 是可以独立切换的,这就意味着另画单独的帧会导致组合爆炸。
也不支持连续触发、连续状态的定义。
Principle
————
有页面(Artboard)、组件(Component)、动画(Animate)、连续映射(Driver)的概念。
其中动画、连续映射的概念,解决了连续状态的切换映射。可以非常好地制作复杂动效。
不过让人困惑的是,Principle 也不支持单个触发事件定义多个动作。很遗憾地成为它的美中不足。
Axure
————
有页面(Page)、组件(动态面板)、动作(Action)、中继器(Repeater)、条件判断(Condition)等概念
可以做比较复杂的交互。
连续状态对应的功能没看到,可能中继器功能可以实现。
FramerX
————
有页面(Page)、帧(Frame)、组件(Component)等概念。
引入了编程,且深度渗透到交互中。对设计师不友好。
ProtoPie
————
有页面(Scene)、组件(Component)、动作(Action)、条件(Condition)等概念。
连续状态对应的功能没看到,可能变量(Variable)功能可以实现。
Origami Studio
————
有页面(Artboard)、组件(Component)、模组(Patch)等概念
因为模组的就相当于是函数编程的概念,所以可以实现条件判断、动效等所有的功能。
总体感觉就是,Origami Studio 是最让我满意的,它在能处理几乎所有复杂交互时,却不像 FramerX 一样强制过早地引入编程。也不像 ProtoPie 或 Axure 一样概念复杂。它的设计理念异常简洁清晰,而且对于原型工具非常适用,然后易用性也毫不逊色。从中你可以感受到它的简洁和美。
Principle 是我非常满意的,它的设计理念也很简洁,巧妙统一了时间连续状态切换(Animation)和交互连续状态切换(Driver)的交互方式,页面和组件也通过递归简化了概念。而且对于设计师非常友好,非常易用。如果它支持单个触发事件定义多个动作的话,我可能不会在后面遇到 Origami 。
ProtoPie 和 Axure,功能比其他一般的设计软件更全。但,感觉 ProtoPie 像是一个大杂烩,而 Axure 则显得太古板。都缺少美感。
FramerX 似乎完全不在乎设计师的感受,它强调它适用于所有水平的设计师,我看未必,设计一个稍复杂的交互,就必须动用代码,我觉得它还不成熟。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.