本期精读的文章是：[best practices for modals overlays dialog windows](https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c)。

# 1 引言

<img src="https://img.alicdn.com/imgextra/i1/O1CN01DnoLTI1Yz7xqiVKKW_!!6000000003129-2-tps-1280-679.png" alt="logo" width="500" />

我为什么要选这篇文章呢？

1. 前端工程师今天在外界是怎么定位的。很多人以为前端都应该讨论架构层面的问题，其实不仅仅在此，我们不应该忽视交互体验这件事。
2. 对于用户体验的追求前端工程师从来没有停止过，而模态框在产品中的出现出现过很多争议，我想知道我们是怎么思考这件事的。

# 2 内容概要

来自 Wikipedia 的定义：模态框是一个定位于应用视窗定层的元素。它创造了一种模式让自身保持在一个最外层的子视察下显示，并让主视窗失效。用户必须在回到主视窗前在它上面做交互动作。

**模态框用处**

- 抓住用户的吸引力
- 需要用户输入
- 在上下文下显示额外的信息
- 不在上下文下显示额外的信息

不要用模态框显示错误、成功或警告的信息。保持它们在页面上。

**模态框的组成**

- 退出的方式。可以是模态框上的一个按钮，可以是键盘上的一个按键，也可以是模态框外的区域。
- 描述性的标题。标题其实给了用户一个上下文信息。让用户知道他现在在哪个位置作操作。
- 按钮的内容。它一定要是可行动的，可以理解的。不要试图让按钮的内容让用户迷惑，如果你尝试做一个取消动作，但框内有一个取消的按钮，那么我是要取消一个取消呢，还是继续我的取消。
- 大小与位置。模态框的大小不要太大或太小，不应该。模态框的位置建议在视窗中间偏上的位置，因为在移动端如果太低的话会失去很多信息。
- 焦点。模态框的出现一定要吸引你的注意力，建议键盘的焦点也切换到框内。
- 用户发起。不要对用户造成惊吓。用用户的动作，比如一个按钮的点击来触发模态框的出现。

**模态框在移动端**

模态框在移动端总是不是玩转得很好。其中一个原因是一般来说模态框都太大了，占用了太多空间。建议增加设备的按键或内置的滚动条来操作，用户可以左移或放大缩小来抓住模态框。

**无障碍访问**

1. 快捷键。我们应该考虑在打开，移动，管理焦点和关闭时增加对模态框的快捷键。
2. ARIA。在前端代码层面加上 aria 的标识，如 Role = “dialog” , aria-hidden, aria-label

# 3 精读

### 模态框定位

首先，Modal 与 Toast、Notification、Message 以及 Popover 都会在某个时间点被触发弹出一个浮层，但与 Modal（模态框）还是有所不同的。定义上看，上述组件都不属于模态框，因为模态框有一个重要的特性，即阻塞原来主视窗下的操作，只能在框内作后续动作。也就是说模态框从界面上彻底打断了用户心流。

当然，这也是我们需要讨论的问题，如果只是一般的消息提醒，可以用信息条、小红点等交互形式，至少是不阻塞用户操作的。在原文末引用的 10 Guidelines to Consider when using Overlays 一文中，第 8 条强调了模态框不到万不得以不应该使用。这时我们应该思考什么情况下你非常希望他不要离开页面，来读框内的信息或作操作呢？

反过来说，模态框有什么优点呢？要知道比起页面跳转来说，模态框的体验还是要轻量的多。例如，用户在淘宝上看中了一款商品，想登陆购买，此时弹出登陆模态框的体验就要远远好于跳转到登陆页面，因为用户在模态框中登陆后，就可以直接购买了。其次，模态框的内容对于当前页面来说是一种衍生或补充，可以让用户更为专注去阅读或者填写一些内容。

也就是说，当我们设计好模态框出现的时机，流畅的弹出体验，必要的上下文信息，以及友好的退出反馈，还是完全可以提升体验的。模态框的目的在于吸引注意，但一定需要提供额外的信息，或是一个重要的用户操作，或是一份重要的协议确认。在本页面即可完成流程或信息告知。

### 合理的使用模态框

我们也总结了一些经验，更好地使用模态框。

- 内容是否相关。模态框是作为当前页面的一种衍生或补充，如果其内容与当前内容毫不相干，那么可以使用其他操作（如新页面跳转）来替代模态框；
- 模态框内部应该避免有过多的操作。模态框应该给用户一种看完即走，而且走的流畅潇洒的感觉，而不是利用繁杂的交互留住或牵制住用户；
- 避免出现一个以上的模态框。出现多个模态框会加深了产品的垂直深度，提高了视觉复杂度，而且会让用户烦躁起来；
- 不要突然打开或自动打开模态框，这个操作应该是用户主动触发的；

还有两种根据实际情况来定义：

- 大小。对于模态框的大小应该要有相对严格的限制，如果内容过多导致模态框或页面出现滚动条，一般来说这种体验很糟糕，但如果用于展示一些明细内容，我们可能还是会考虑使用滚动条来做；
- 开启或关闭动画。现在有非常多的设计倾向于用动画完成流畅的过渡，让 Modal 变得不再突兀，[dribble 上有很多相关例子](https://dribbble.com/shots/3206370-Coverage-Modal-Motion-Study)。但在一些围绕数据来做复杂处理的应用中，如 ERP、CRM 产品中用户通常关注点都在一个表单和围绕表单做的一系列操作，页面来回切换或复杂的看似酷炫的动画可能都会影响效率。用户需要的是直截了当的完成操作，这时候可能就不需要动画，用户想要的就是快捷的响应。

举两个例子，Facebook 在这方面给我们很好的 demo，它的分享模态框与主视窗是在同一个位置，给人非常流畅的体验。还看到一个细节，从主视窗到模态框焦点上的字体会变大。对比微博，它就把照片等分享形式直接展示出来，焦点在输入框上时也没有变化。

第二个例子是 Quora，Quora 主页呈现的是 Feed 流，点击标题就会打开一个模态框展示它回答的具体内容，内容里面是带有滚动条的，按 ESC 键就可以关闭。非常流畅的体验。相比较之下知乎首页想要快速看内容得来回切换。

### 可访问性的反思

Accessibility 翻译过来是『无障碍访问』，是对不同终端用户的体验完善。每一个模态框，都要有通过键盘关闭的功能，通常使用 ESC 键。似乎我们程序员多少总会把我们自我的惯性思维带进实现的产品，尤其是当我们敲着外置的键盘，用着 PC 的时候。

下面的这些问题都是对可访问性的反思：

- 用户可能没有鼠标，或者没有键盘，甚至可能既没有鼠标也没有键盘，只使用的是语音控制？你让这些用户如何退出
- 很多的 Windows PC 都已经获得了很好的触屏支持，而你的网页依旧只支持了键盘跟鼠标？
- 在没有苹果触摸板的地方，横向滚动条是不是一个逆天的设计？
- 在网页里，使用 Command(Ctrl) and +/- 和使用触摸板的缩放事件是两个不同的表现？
- 如果你的终端用户没有好用的触摸板，但是他的确看不清你的网页上的内容。如果他用了前者，你能不能保证你的网页依然能够正常展示内容？

可访问性一直都是产品极其忽视的，在文章的最佳实践最后特别强调了它是怎么做的，对我们这些开发者是很好的督促。

### 模态框代码实现层面

前端开发还是少不了代码层面的实现，**业务代码对于有状态或无状态模态框的使用方式存在普遍问题**。

对有状态模态框来说，很多库会支持 `.show` 直接调用的方式，那么模态框内部渲染逻辑，会在此方法执行时执行，没有什么问题。不过现在流行无状态模态框(Stateless Modal)，模态框的显示与否交由父级组件控制，我们只要将模态框代码预先写好，由外部控制是否显示。

这种无状态模态框的方式，在模态框需要显示复杂逻辑的场景中，会自然将初始化逻辑写在父级，当模态框出现在循环列表中，往往会引发首屏触发 2-30 次模态框初始化运算，而这些运算最佳状态是模态框显示时执行一次，由于模态框同一时间只会出现一个，最次也是首屏初始化一次，但下面看似没问题的代码往往会引发性能危机：

```js
const TdElement = data.map(item => {
  return (
    <Td>
      <Button>详情</Button>
      <Modal show={item.show} />
    </Td>
  )
});
```

上面代码初始化执行了 N 个模态框初始化代码，显然不合适。对于 table 操作列中触发的模态框，所有行都对应一个模态框，通过父级中一个状态变量来控制展示的内容：

```js
class Table extends Component {
  static state = {
    activeItem: null, 
  };

  render() {
    const { activeItem } = this.state;

    return (
      <div>
        <Modal show={!!activeItem} data={activeItem} />
      </div>
    );
  }
}
```

这种方案减少了节点数，但是可能会带来的问题是，每次模态框被展示的时候，触发是会是模态框的更新 (componentDidUpdate) 而不是新增。当然结合 table 中操作的特点，我们可以这样优化：

```js
{activeItem ? <Modal show={true} data={activeItem} /> : null}
```

### 补充阅读

# 总结

这篇讲的是最佳实践，而且是 UX 层面的。但我们还是看到一些同学提出了相反的意见，我总结下就是不同的产品或不同的用户带给我们不同的认识。这时候是不是要死守着『最佳实践』呢？这时候，对于产品而言，我们可以采集用户研究的方法去判断，用数据结论代替感官上的结论。

另外，可访问性在这两年时不时会在一些文章中看到，但非常少。这是典型的长尾需求，很多研发在做产品只考虑 90% 的用户，不清楚我们放弃的一部分用户的需求。这是从产品到研发整体的思考的缺失。

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。**
