# 视觉舒适度

> 让一个屏幕整天看着都不累的规则：一条线放在哪里、延伸多远，间距节奏，内缩，对齐，密度，各级文字之间的对比，以及什么时候一块表面才用卡片。

Source: https://raft-design.botiverse.build/zh/foundations/comfort. Translated from https://raft-design.botiverse.build/foundations/comfort.md; where the two differ, the English page is right.

人们整天开着 Raft，所以屏幕必须一眼就能读懂，看上几个小时也不累。舒适来自几种关系，眼睛不用提醒就会去检查：哪些东西是一组，一样东西从哪里开始、到哪里结束，什么最重要。下面每条规则是一句话加上它背后的理由；数值来自[间距](https://raft-design.botiverse.build/zh/foundations/spacing.md)、[对齐](https://raft-design.botiverse.build/zh/foundations/alignment.md)、[字体排印](https://raft-design.botiverse.build/zh/foundations/typography.md)、[颜色](https://raft-design.botiverse.build/zh/foundations/color.md#lines)和[层级](https://raft-design.botiverse.build/zh/foundations/elevation.md)，这一页讲的是它们怎么配合。

## 原则
- **一条线延伸到它的容器看得见的边界为止。** 在有边框的框里，线碰到边框；没有看得见的边时，线缩进到内容的位置（[分隔线](#separators)）。
- **组内比组间更近。** 每个间距都来自间距阶梯（[间距节奏](#spacing-rhythm)）。
- **离每条边的内缩都一样。** 嵌套的内缩会累加（[边缘](#edges)）。
- **文字在同一条基线上，标记居中；每一栏一条起始边**（[对齐](#alignment)）。
- **密度跟着输入方式走，从不跟着内容走。** 紧凑列表的一行，触屏时保持至少 44px，鼠标时至少 36px（[密度](#density-and-touch-targets)）。
- **文字层级少，层级之间拉得开。** 次要文字是变浅，而不是变小（[层级](#hierarchy-contrast)）。
- **卡片用于放在其他内容里面、或在另一个区域旁边的对象。** 不是每一组都要卡片（[表面](#surfaces)）。

## 分隔线
线分隔的是一个容器里的同级元素，所以它横跨这个容器看得见的范围。这些规则针对的是紧凑列表：消息下面和 Tasks 卡片里的任务行，以及 Files 卡片里的行（[紧凑列表中的行](https://raft-design.botiverse.build/zh/patterns/tasks.md#rows-in-a-compact-list)）。其他列表保留它们自己的分隔线（见本节末尾）。

- **在有边框的框里，线从一边贯穿到另一边，两头都碰到边框。** 这样框和它的线组成一组格子。没碰到边框的线两头各留一个空隙，框看起来就像一个带划痕的画框。消息下面的任务就放在这样的框里：它们的线碰到框的边框（[任务](https://raft-design.botiverse.build/zh/patterns/tasks.md#rows-in-a-compact-list)）。
- **没有看得见的边时，线的两头都缩进到内容：从文字开始的地方，到最后一个标记结束的地方。** 没有可以碰到的东西时，通栏的线读起来像一个区域的边，一条接缝线，把页面切成一条一条。和内容同起同止的线属于内容。较大卡片里的列表，以及侧边卡片在手机上变成的页面，列表周围都没有自己的边，所以它们的线是缩进的：在侧边卡片和手机屏幕里离两边各 16px。
- **容器是离它最近、有看得见的边、并且只装着这些同级元素的那一个。** 只装着这些行的框，就是这些行的容器。还装着标题栏、筛选和分组标题的卡片不是：在那里，列表只是几个部分之一，没有自己的边，它的线和卡片的内容一样缩进。所以 Tasks 卡片的线停在离边 16px 的地方，而消息下面那个框的线碰到它的边框。
- **分组已经看得出来时，用空白，不用线。** 标题、间隔或填充的变化已经把各组分开了，所以标题和它的行之间、第一行上方、最后一行下方，以及两个各有标题的组之间，都不放线。紧挨着边框、接缝线或另一条线的线永远不需要：两条并排的 1px 线读起来是一条 2px 的线（[页面结构](https://raft-design.botiverse.build/zh/patterns/page-structure.md#one-line-between-two-regions)）。
- **悬停仍然填满整行。** 缩进的线是画在行上的，而不是把行变窄，所以不管线怎样，行的目标区域和它的悬停都从一边贯穿到另一边。

每种线（细线、接缝线、墨色线）的含义见[原则](https://raft-design.botiverse.build/zh/principles/principles.md#2-three-lines-three-meanings)。其他列表保留它们现在的分隔线，这条规则不改变它们：[InboxItem](https://raft-design.botiverse.build/zh/components/inbox-item.md) 列表（动态）和 [ListRow](https://raft-design.botiverse.build/zh/components/list-row.md) 列表的线从文字开始，一直延伸到右边；ListRow 的设置变体，也就是放在内缩卡片里的行，线从离边 16px 的地方开始，而不是从卡片的边框开始。不要把这些分隔线照搬到新的紧凑列表里。

## 间距节奏
属于一起的东西，彼此之间的距离要比到其他任何东西都近，而且每个间距都取自同一套阶梯。

- **每个间距都取自[间距](https://raft-design.botiverse.build/zh/foundations/spacing.md#spacing)里的阶梯**，包括其中的半级。阶梯之外的数值（5px、14px、18px）说明有东西是在被微调，而不是在被排布。
- **组与组之间的间距大约是组内间距的两倍，或者更多。** 眼睛在读之前就是靠远近来分组的。间隔大小接近时，什么都分不成组，屏幕读起来就是一片均匀的灰。
- **标题离它所指的内容，要明显比离它前面的内容更近**：Tasks 卡片里的状态标题采用 Sidebar 分区标题的间距，文字上方 16px、下方 6px，各组之间不另加间隔，所以它的文字离上面的行（大约 25px）明显比离自己的行（16px）更远。标题离上面的行和离自己的行一样远时，看起来像是两边都属于。标题是一个开关时，让它的触控目标伸进上方的空白，而不是把它推离自己的行。
- **一行自己的内边距也要算进去。** 测量字母和标记之间的距离，而不是框和框之间的距离：一行 36px、文字 14px 时，字母上下已经各有 11px 的空间。

## 边缘
内容离容器每条边的内缩都一样，这样容器看起来是围着它居中的。

- **第一个和最后一个看得见的东西缩进同样的距离**：左边是第一个字母或开头的标记，右边是最后一个图标框或词（[对齐](https://raft-design.botiverse.build/zh/foundations/alignment.md#usage)）。Files 卡片里的一个文件行，类型图标从离卡片左边 16px 的地方开始，最后一个操作的图标框在离右边 16px 的地方结束，点击区域向外伸出，所以这一行在卡片里是居中的（[紧凑列表中的行](https://raft-design.botiverse.build/zh/patterns/tasks.md#rows-in-a-compact-list)）。如果按按钮的框来放，操作结束的地方会比图标开始的地方更靠里，这一行看起来就偏向一边，即使谁也说不出为什么。
- **较宽的框里的小标记会移动边缘。** 一个 32px 的图标按钮按它自己的边来放，它 18px 的图标就在内缩线往里 7px 的地方；一个 44px 的 Back，它 22px 的图标比下面的内容往里 11px。让标记落在内缩线上，框的其余部分向外伸出（[标题栏的两端](https://raft-design.botiverse.build/zh/foundations/alignment.md#the-ends-of-a-header)）。
- **栏末端的无边框按钮按它的文字或图标框对齐**，内边距向外伸出：动态筛选右侧的 Mark All Read，文字结束在各行时间结束的那条边距线上（[对齐](https://raft-design.botiverse.build/zh/foundations/alignment.md#usage)）。
- **嵌套的内缩会累加。** 一个容器放在另一个容器里，从外层的内缩开始，它自己的内容再在此基础上加内层的内缩：消息下面的框从消息的文字栏开始，框里的行再往里 12px 开始。不要用负外边距抵消内缩来凑数字；改容器。唯一的例外是悬挂的内边距：放在纯文字旁边的形状，比如行内 DescriptionList 的值里的一个标签片，按它自己的内边距往外拉，让它的文字从纯文字开始的地方开始，填充向外伸出（[纯文字旁边的形状](https://raft-design.botiverse.build/zh/foundations/alignment.md#a-shape-beside-plain-text)）。
- **必须有最小宽度的容器，在去掉边距之后仍然保持这个宽度。** 至少 280px 的侧边卡片，是在它 12px 的留白以内有 280px，而不是 268px。最小宽度是由卡片必须装下的东西决定的，即一行的标题和它尾部的一组；留白是卡片周围的空间，不是给内容的地方，所以从卡片里扣掉留白，缩小的正是最小宽度要保护的东西。

## 对齐
一行和一栏里的每个部分落在哪里，由[对齐](https://raft-design.botiverse.build/zh/foundations/alignment.md)规定，理由也在那里；这一页不另立规则。这些规则最初针对的那些行，按它原样遵循：

- 任务行、文件行和一行属性，把不同大小的文字放在同一条基线上（[一行中不同大小的文字](https://raft-design.botiverse.build/zh/foundations/alignment.md#text-of-different-sizes-on-one-line)），把没有文字的标记，即头像、像素点、V 形箭头和图标，居中（[引领文字的标记](https://raft-design.botiverse.build/zh/foundations/alignment.md#a-mark-that-leads-text)）。
- 面板的标题、筛选、分组标题和各行都从面板的边距开始（[标题栏的两端](https://raft-design.botiverse.build/zh/foundations/alignment.md#the-ends-of-a-header)）。

## 密度与触控目标
一个屏幕放多少东西，跟着输入方式走，而不是跟着要显示多少走。

- **紧凑列表的一行，触屏时至少 44px，鼠标时至少 36px**（[紧凑列表中的行](https://raft-design.botiverse.build/zh/patterns/tasks.md#rows-in-a-compact-list)）。控件的尺寸和它们的点击区域见[间距](https://raft-design.botiverse.build/zh/foundations/spacing.md#hit-targets)。
- **空间不够时，截断或换行：永远不要缩小间距或字号。** 标题以省略号结尾，尾部一组整体换到第二行。比阶梯更紧的间距让每样东西都更难点中、更难读；想在屏幕上放更多东西的人，可以选更小的消息字号（[字体排印](https://raft-design.botiverse.build/zh/foundations/typography.md#desktop-and-phone)）。
- **一行最多两行文字**，只有尾部一组换行时才有第二行。出现第三行，说明这一行装的是一张卡片的内容。

## 层级对比
一个屏幕上的文字只有几个层级，每一级和下一级都明显分开。

- **只用少数几种字号和字重。** 在桌面端，大多数文字是 14/22，备注是 12/18；列表的一行，标题用一种字号，备注用一种字号。在列表或卡片里，标题靠字重突出，而不是靠自己的字号；只有整块表面的标题，即页面、面板或对话框的标题，才用字号阶梯里的 headline（[按用途选字号](https://raft-design.botiverse.build/zh/foundations/typography.md#sizes-by-use)）。
- **两个层级之间，字号、字重和颜色最多差两样**（[较深表面上的备注](https://raft-design.botiverse.build/zh/foundations/typography.md#notes-on-deeper-surfaces)）。三样同时不同就太吵了。
- **次要文字用 muted 或 hint，而不是越来越小。** 数字、时间、计数和分组标题用 `foreground-hint`；描述用 `foreground-muted`（[颜色层级](https://raft-design.botiverse.build/zh/foundations/typography.md#color-tiers)）。
- **一块表面讲的是什么，要排成标题，永远不要排成正文。** 在任务详情里，任务的标题就是这块表面的标题，所以它用 headline（`text-headline`），字重 600，放在说明这是哪个对话框、字重 700 的那条栏下面；排成普通的 16px 文字时，它读起来比栏里的 "Task #1" 还弱。

## 表面
只有内容本身是一个独立的对象时，才给它卡片或边框。

- **放在一串其他内容里的对象，值得一张卡片**：附件、消息下面的任务、文件卡片。它的边说的是「这是一样东西，和周围的消息分开」。
- **一个区域放在另一个区域旁边、中间又没有接缝线分开时，值得一张卡片**，比如消息列表旁边的侧边卡片。
- **不要给已经有边的东西再套卡片**：页面、面板、对话框，或者内容占满的手机屏幕。在手机上，Tasks 卡片作为一个页面盖住对话：它没有卡片，它的线是缩进的。
- **不要给每一行一张卡片，也不要嵌套边一样的卡片。** 每行一张卡片会把列表变成一摞控件；有边框的框里再套一个有边框的框，是在一条线就够的地方多了一条线。先用空白和标题分组，然后用填充的变化，最后才用线（[原则](https://raft-design.botiverse.build/zh/principles/principles.md#2-three-lines-three-meanings)）。
- **只有浮起的东西才投阴影**（[层级](https://raft-design.botiverse.build/zh/foundations/elevation.md#principles)）：布局里的卡片只靠它的边来标出。

## 相关
- [间距](https://raft-design.botiverse.build/zh/foundations/spacing.md)：阶梯、点击区域和布局宽度。
- [对齐](https://raft-design.botiverse.build/zh/foundations/alignment.md)：什么落在边距线或中线上。
- [字体排印](https://raft-design.botiverse.build/zh/foundations/typography.md)：字号阶梯和文字层级。
- [层级](https://raft-design.botiverse.build/zh/foundations/elevation.md)：什么会浮起并投阴影。
- [任务](https://raft-design.botiverse.build/zh/patterns/tasks.md#rows-in-a-compact-list)：这些规则最初就是为这种紧凑行写的。
