本期精读的文章是:best practices for modals overlays dialog windows。
我为什么要选这篇文章呢?
来自 Wikipedia 的定义:模态框是一个定位于应用视窗定层的元素。它创造了一种模式让自身保持在一个最外层的子视察下显示,并让主视窗失效。用户必须在回到主视窗前在它上面做交互动作。
模态框用处
不要用模态框显示错误、成功或警告的信息。保持它们在页面上。
模态框的组成
模态框在移动端
模态框在移动端总是不是玩转得很好。其中一个原因是一般来说模态框都太大了,占用了太多空间。建议增加设备的按键或内置的滚动条来操作,用户可以左移或放大缩小来抓住模态框。
无障碍访问
首先,Model 与 Toast、Notification、Message 以及 Popover 都会在某个时间点被触发弹出一个浮层,但与 Modal(模态框)还是有所不同的。定义上看,上述组件都不属于模态框,因为模态框有一个重要的特性,即阻塞原来主视窗下的操作,只能在框内作后续动作。也就是说模态框从界面上彻底打断了用户心流。
当然,这也是我们需要讨论的问题,如果只是一般的消息提醒,可以用信息条、小红点等交互形式,至少是不阻塞用户操作的。在原文末引用的 10 Guidelines to Consider when using Overlays 一文中,第 8 条强调了模态框不到万不得以不应该使用。这时我们应该思考什么情况下你非常希望他不要离开页面,来读框内的信息或作操作呢?
反过来说,模态框有什么优点呢?要知道比起页面跳转来说,模态框的体验还是要轻量的多。例如,用户在淘宝上看中了一款商品,想登陆购买,此时弹出登陆模态框的体验就要远远好于跳转到登陆页面,因为用户在模态框中登陆后,就可以直接购买了。其次,模态框的内容对于当前页面来说是一种衍生或补充,可以让用户更为专注去阅读或者填写一些内容。
也就是说,当我们设计好模态框出现的时机,流畅的弹出体验,必要的上下文信息,以及友好的退出反馈,还是完全可以提升体验的。模态框的目的在于吸引注意,但一定需要提供额外的信息,或是一个重要的用户操作,或是一份重要的协议确认。在本页面即可完成流程或信息告知。
我们也总结了一些经验,更好地使用模态框。
还有两种根据实际情况来定义:
举两个例子,Facebook 在这方面给我们很好的 demo,它的分享模态框与主视窗是在同一个位置,给人非常流畅的体验。还看到一个细节,从主视窗到模态框焦点上的字体会变大。对比微博,它就把照片等分享形式直接展示出来,焦点在输入框上时也没有变化。
第二个例子是 Quora,Quora 主页呈现的是 Feed 流,点击标题就会打开一个模态框展示它回答的具体内容,内容里面是带有滚动条的,按 ESC 键就可以关闭。非常流畅的体验。相比较之下知乎首页想要快速看内容得来回切换。
Accessibility 翻译过来是『无障碍访问』,是对不同终端用户的体验完善。每一个模态框,都要有通过键盘关闭的功能,通常使用ESC键。似乎我们程序员多少总会把我们自我的惯性思维带进实现的产品,尤其是当我们敲着外置的键盘,用着 PC 的时候。
下面的这些问题都是对可访问性的反思:
可访问性一直都是产品极其忽视的,在文章的最佳实践最后特别强调了它是怎么做的,对我们这些开发者是很好的督促。
前端开发还是少不了代码层面的实现,业务代码对于有状态或无状态模态框的使用方式存在普遍问题。
对有状态模态框来说,很多库会支持 .show
直接调用的方式,那么模态框内部渲染逻辑,会在此方法执行时执行,没有什么问题。不过现在流行无状态模态框(Stateless Modal),模态框的显示与否交由父级组件控制,我们只要将模态框代码预先写好,由外部控制是否显示。
这种无状态模态框的方式,在模态框需要显示复杂逻辑的场景中,会自然将初始化逻辑写在父级,当模态框出现在循环列表中,往往会引发首屏触发 2-30 次模态框初始化运算,而这些运算最佳状态是模态框显示时执行一次,由于模态框同一时间只会出现一个,最次也是首屏初始化一次,但下面看似没问题的代码往往会引发性能危机:
const TdElement = data.map(item => {
return (
<Td>
<Button>详情</Button>
<Modal show={item.show} />
</Td>
)
});
上面代码初始化执行了 N 个模态框初始化代码,显然不合适。对于 table 操作列中触发的模态框,所有行都对应一个模态框,通过父级中一个状态变量来控制展示的内容:
class Table extends Component {
static state = {
activeItem: null,
};
render() {
const { activeItem } = this.state;
return (
<div>
<Modal show={!!activeItem} data={activeItem} />
</div>
);
}
}
这种方案减少了节点数,但是可能会带来的问题是,每次模态框被展示的时候,触发是会是模态框的更新 (componentDidUpdate) 而不是新增。当然结合 table 中操作的特点,我们可以这样优化:
{activeItem ? <Modal show={true} data={activeItem} /> : null}
这篇讲的是最佳实践,而且是 UX 层面的。但我们还是看到一些同学提出了相反的意见,我总结下就是不同的产品或不同的用户带给我们不同的认识。这时候是不是要死守着『最佳实践』呢?这时候,对于产品而言,我们可以采集用户研究的方法去判断,用数据结论代替感官上的结论。
另外,可访问性在这两年时不时会在一些文章中看到,但非常少。这是典型的长尾需求,很多研发在做产品只考虑 90% 的用户,不清楚我们放弃的一部分用户的需求。这是从产品到研发整体的思考的缺失。