# 优秀前端的优秀开场指引

返回:四信培训分享

TIP

一个 Web 页面,一个 APP,想让别人用的爽,也就是所谓的良好的用户体验,应该包括但不限于:

  • 急速的打开速度
  • 眼前一亮的 UI 设计
  • 酷炫的动画效果
  • 丰富的个性化设置
  • 便捷的操作
  • 贴心的细节
  • 关注残障人士,良好的可访问性
  • ……

用户体验的概念从开发的最早期就开始进入整个流程,并贯穿始终

# 页面展示

# 整体布局

对于大部分 PC 端的项目,我们首先需要考虑的肯定是最外层的一层包裹。假设就是【.home-wrapper

<div class="home-wrapper">
  <!-- 内容 -->
</div>
1
2
3

首先,对于 .home-wrapper,有几点,是我们在项目开发前必须弄清楚的:

  • 项目是全屏布局还是定宽布局?
    • 对于定宽布局,就比较方便了,假设定宽为 1366px,那么:
.home-wrapper {
  width: 1366px;
  margin: 0 auto;
}
1
2
3
4

利用 margin: 0 auto 实现布局的水平居中。在屏幕宽度大于 1366px 时,两侧留白,当然屏幕宽度小于 1366px 时,则出现滚动条,保证内部内容不乱。

  • 对于全屏布局,需要适配的最小的宽度是多少?
    • 对于现代布局,更多的是全屏布局。其实现在也更提倡这种布局,即使用可随用户设备的尺寸和能力而变化的自适应布局。
<div class="home-wrapper">
  <div class="home-side"></div>
  <div class="home-main"></div>
</div>
1
2
3
4
.home-wrapper {
  /* width: 1366px; */
  /* margin: 0 auto; */
  min-height: 768px;
  display: flex;
  min-width: 1280px;
}
.home-wrapper .home-side {
  flex-basis: 300px;
  margin-right: 10px;
  background-color: aquamarine;
}
.home-wrapper .home-main {
  flex-grow: 1;
  background-color: lemonchiffon;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

flex 布局下的 flex-grow: 1,让 .main 进行伸缩,占满剩余空间,利用 min-width 保证了整个容器的最小宽度

<div class="home-main">
  <div class="home-content">我是内容</div>
  <div class="home-foot">我是固定的底部区域</div>
</div>
1
2
3
4
.home-wrapper .home-main {
  flex-grow: 1;
  background-color: lemonchiffon;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.home-wrapper .home-content {
  flex-grow: 1;
  background-color: tomato;
}
.home-wrapper .home-foot {
  margin-top: 8px;
  flex-basis: 200px;
  background-color: greenyellow;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 处理动态内容 - 文本超长

对于所有接收后端接口字段的文本展示类的界面。都需要考虑全面(防御性编程:所有的外部数据都是不可信的),正常情况如下,是没有问题的。

.single-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
1
2
3
4
5

目前对于多行文本的超长省略,兼容性也已经非常好了

.multipleLine {
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  display: -webkit-box;
}
1
2
3
4
5
6
7

# 处理动态内容 - 保护边界

对于一些动态内容,我们经常使用 min/max-widthmin/max-height 对容器的高宽限度进行合理的控制。

在使用它们的时候,也有一些细节需要考虑到。

譬如经常会使用 min-width 控制按钮的最小宽度:

# 0 内容展示

  • 数据为空:其中又可能包括了用户无权限、搜索无结果、筛选无结果、页面无数据
  • 异常状态:其中又可能包括了网络异常、服务器异常、加载失败等待

开发时,不能仅仅关注正常现象,要多考虑各种异常情况,思考全面。做好各种可能情况的处理。

如何设计产品的空白页面

# 图片相关

# 给图片同时设置高宽

有的时候和产品、设计会商定,只能使用固定尺寸大小的图片,

当然,万一假设后端接口出现一张非正常大小的图片,上述不加保护的布局就会出问题:

ul li img {
  width: 150px;
  height: 100px;
}
1
2
3
4

同时,给 <img> 标签同时写上高宽,可以在图片未加载之前提前占住位置,避免图片从未加载状态到渲染完成状态高宽变化引起的重排问题。

<img src="http://placehold.it/150x300" width="100" height="150" />
1
  • object-fit

当然,限制高宽也会出现问题,譬如图片被拉伸了,非常的难看:
这个时候,我们可以借助 object-fit,它能够指定可替换元素的内容(也就是图片)该如何适应它的父容器的高宽。

ul li img {
  width: 150px;
  height: 100px;
  object-fit: cover;
}
1
2
3
4
5

利用 object-fit: cover,使图片内容在保持其宽高比的同时填充元素的整个内容框。

object-fit

object-fit 还有一个配套属性 object-position,它可以控制图片在其内容框中的位置
(类似于 background-position),默认是 object-position: 50% 50%,如果你不希望图片居中展示,可以使用它去改变图片实际展示的 position 。

object-position:第一个值为 x 坐标位置的值,第二个值为 y 坐标位置的值

ul li img {
  width: 150px;
  height: 100px;
  object-fit: cover;
  object-position: 50% 100%;
}
1
2
3
4
5
6

谷歌浏览器可能不符合预期,可做更改

object-position: calc(100% - 20px) calc(100% - 10px);
1

# 考虑屏幕 dpr -- 响应式图片

正常情况下,图片的展示应该没有什么问题了。但是对于有图片可展示的情况下,我们还可以做的更好。

在移动端或者一些高清的 PC 屏幕(苹果的 MAC Book),屏幕的 dpr 可能大于 1。这种时候,我们可能还需要考虑利用多倍图去适配不同 dpr 的屏幕。

正好,<img> 标签是有提供相应的属性 srcset 让我们进行操作的。

<img
  src="photo@1x.png"
  srcset="photo@1x.png 1x, photo@2x.png 2x, photo@3x.png 3x"
/>
1
2
3
4
<img src = "photo.png" sizes = “(min-width: 600px) 600px, 300px" srcset
="photo@1x.png 300w, photo@2x.png 600w, photo@3x.png 1200w," >
1
2

# 图片丢失

好了,当图片链接没问题时,已经处理好了。接下来还需要考虑,当图片链接挂了,应该如何处理。

  • 利用图片加载失败,触发 <img> 元素的 onerror 事件,给加载失败的 <img> 元素新增一个样式类
  • 利用新增的样式类,配合 <img> 元素的伪元素,展示默认兜底图的同时,还能一起展示 <img> 元素的 alt 信息
<img src="test.png" alt="图片描述" onerror="this.classList.add('error');" />
1
img.error {
  position: relative;
  display: inline-block;
}

img.error::before {
  content: "";
  /** 定位代码 **/
  background: url(error-default.png);
}

img.error::after {
  content: attr(alt);
  /** 定位代码 **/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

我们利用伪元素 before ,加载默认错误兜底图,利用伪元素 after,展示图片的 alt 信息

# 交互细节

  • Don’t make me think
  • 符合用户的习惯与预期
  • 操作便利
  • 做适当的提醒
  • 不强迫用户

# 过渡与动画

# 滚动优化

  • 滚动平滑:使用 scroll-behavior: smooth 让滚动丝滑
  • 使用 scroll-snap-type 优化滚动效果
    • sroll-snap-type 可能算得上是新的滚动规范里面最核心的一个属性样式。
    • 属性定义在滚动容器中的一个临时点(snap point)如何被严格的执行。
 {
  scroll-snap-type: none | [ x | y | block | inline | both ] [ mandatory |
    proximity ]?;
}
1
2
3
4
  • 控制滚动层级,避免页面大量重排
    • 先说结论,控制滚动层级的意思是尽量让需要进行 CSS 动画(可以是元素的动画,也可以是容器的滚动)的元素的 z-index 保持在页面最上方,避免浏览器创建不必要的图形层(GraphicsLayer),能够很好的提升渲染性能。
  • 你所不知道的 CSS 动画技巧与细节

# 点击交互优化

  • 优化手势 -- 不同场景应用不同 cursor
    • 对于不同的内容,最好给与不同的 cursor 样式,CSS 原生提供非常多种常用的手势。
    • 在不同的场景使用不同的鼠标手势,符合用户的习惯与预期,可以很好的提升用户的交互体验

首先对于按钮,就至少会有 3 种不同的 cursor,分别是可点击不可点击等待中

 {
  /* 可点击 */
  cursor: pointer;
  /* 不可点击 */
  cursor: not-allowed;
  /* loading */
  cursor: wait;
}
1
2
3
4
5
6
7
8

除此之外,还有一些常见的,对于一些可输入的 Input 框,使用 cursor: text,对于提示 Tips 类使用 cursor: help,放大缩小图片 zoom-inzoom-out 等等:

.auto {
  cursor: auto;
}
.default {
  cursor: default;
}
.none {
  cursor: none;
}
.context-menu {
  cursor: context-menu;
}
.help {
  cursor: help;
}
.pointer {
  cursor: pointer;
}
.progress {
  cursor: progress;
}
.wait {
  cursor: wait;
}
.cell {
  cursor: cell;
}
.crosshair {
  cursor: crosshair;
}
.text {
  cursor: text;
}
.vertical-text {
  cursor: vertical-text;
}
.alias {
  cursor: alias;
}
.copy {
  cursor: copy;
}
.move {
  cursor: move;
}
.no-drop {
  cursor: no-drop;
}
.not-allowed {
  cursor: not-allowed;
}
.all-scroll {
  cursor: all-scroll;
}
.col-resize {
  cursor: col-resize;
}
.row-resize {
  cursor: row-resize;
}
.n-resize {
  cursor: n-resize;
}
.e-resize {
  cursor: e-resize;
}
.s-resize {
  cursor: s-resize;
}
.w-resize {
  cursor: w-resize;
}
.ns-resize {
  cursor: ns-resize;
}
.ew-resize {
  cursor: ew-resize;
}
.ne-resize {
  cursor: ne-resize;
}
.nw-resize {
  cursor: nw-resize;
}
.se-resize {
  cursor: se-resize;
}
.sw-resize {
  cursor: sw-resize;
}
.nesw-resize {
  cursor: nesw-resize;
}
.nwse-resize {
  cursor: nwse-resize;
}
.zoom-in {
  cursor: zoom-in;
}
.zoom-out {
  cursor: zoom-out;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

# 点击区域优化 -- 伪元素扩大点击区域

伪元素也是可以代表其宿主元素来响应的鼠标交互事件的。借助伪元素可以轻松帮我们实现,我们可以这样写:

.btn::after {
  content: "";
  position: absolute;
  top: -10px;
  right: -10px;
  bottom: -10px;
  left: -10px;
}
1
2
3
4
5
6
7
8

在按钮的伪元素没有其它用途的时候,这个方法确实是个很好的提升用户体验的点

# 快速选择优化 -- user-select: all

快速单击两次,可以选中单个单词,快速单击三次,可以选中一整行内容。但是如果有的时候我们的核心内容,被分隔符分割,或者潜藏在一整行中的一部分,这个时候选取起来就比较麻烦。
利用 user-select: all,可以将需要一次选中的内容进行包裹,用户只需要点击一次,就可以选中该段信息:

<ul class="g-container">
  <li class="">Codepen</li>
  <li class="g-select-all">Not Single Word</li>
  <li class="g-select-all">138-2254-6698</li>
  <li class="">
    这是一长串的地址,<span class="g-select-all"
      >广东省,深圳市,南山区,xxxxxxx</span
    >
  </li>
  <li class="">
    这是无用信息,<span class="g-select-all">中间的有用信息,被分割</span
    >,这是无用信息
  </li>
  <!--     <li class="g-select-all">123-456-789</li> -->
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
html,
body {
  width: 100%;
  height: 100%;
  place-items: center;
  padding: 20px;
}

ul li {
  display: block;
  line-height: 24px;

  span {
    color: deeppink;
  }
}

.g-select-all {
  user-select: all;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 选中样式优化 -- ::selection

当然,如果你想更进一步,CSS 还有提供一个 ::selection 伪类,可以控制选中的文本的样式(只能控制color, background, text-shadow),进一步加深效果。

html,
body {
  width: 100%;
  height: 100%;
  place-items: center;
  padding: 20px;
}

ul li {
  display: block;
  line-height: 24px;
  letter-spacing: 4px;

  span {
    // color: deeppink;
  }
}

.g-select-all {
  user-select: all;
}

.g-select-all::selection {
  background: #f7ec91;
  color: #333;
  text-shadow: 0 0 0.5px #aaa, 1px 1px 0.5px #aaa, 2px 2px 0.5px #aaa, 3px 3px
      0.5px #aaa, 4px 4px 0.5px #aaa;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 添加禁止选择 -- user-select: none

对于一些可能频繁操作的按钮,可能出现如下尴尬的场景:

  • 文本按钮的快速点击,触发了浏览器的双击快速选择,导致文本被选中: btn-click
  • 翻页按钮的快速点击,触发了浏览器的双击快速选择:

对于这种场景,我们需要把不可被选中元素设置为不可被选中,利用 CSS 可以快速的实现这一点:

 {
  -webkit-user-select: none; /* Safari */
  -ms-user-select: none; /* IE 10 and IE 11 */
  user-select: none; /* Standard syntax */
}
1
2
3
4
5

# 跳转优化

所以,对于所有路由跳转按钮,建议都使用 <a> 标签,并且内置 href 属性,填写跳转的路由地址。实际渲染出来的 DOM 可能是需要类似这样:

<a href="/xx/detail">Detail</a>
1

# 易用性

易用性也是交互设计中需要考虑的一个非常重要的环节,能做的有非常多。简单的罗列一下:

  • 注意界面元素的一致性,降低用户学习成本
  • 延续用户日常的使用习惯,而不是重新创造
  • 给下拉框增加一些预设值,降低用户填写成本
  • 同类的操作合并在一起,降低用户的认知成本
  • 任何操作之后都要给出反馈,让用户知道操作已经生效

# 先探索,后表态

这一点非常的有意思,什么叫先探索后表态呢?就是我们不要一上来就强迫用户去做一些事情,譬如登录。 想一想一些常用网站的例子:

类似虎牙、Bilibili 等视频网站,可以先观看体验,一定观看时间后才会要求登录(登录享受蓝光) 电商网站,只有到付款的时候,才需要登录

# 字体优化

字体的选择与使用其实是非常有讲究的。

如果网站没有强制必须使用某些字体。最新的规范建议我们更多的去使用系统默认字体。也就是 CSS Fonts Module Level 4 -- Generic font families 中新增的 font-family: system-ui 关键字。

  • font-family: system-ui 能够自动选择本操作系统下的默认系统字体。

举两个例子,天猫的字体定义与 Github 的字体定义:

  • 天猫:font-family: "PingFang SC",miui,system-ui,-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,sans-serif;

  • Github:font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;

  • 1、尽量使用系统默认字体

  • 2、兼顾中西,西文在前,中文在后

    • 中文或者西文(英文)都要考虑到。由于大部分中文字体也是带有英文部分的,但是英文部分又不怎么好看,但是英文字体中大多不包含中文。通常会先进行英文字体的声明,选择最优的英文字体,这样不会影响到中文字体的选择,中文字体声明则紧随其次。
  • 3、兼顾多操作系统

    • 选择字体的时候要考虑多操作系统。例如 MAC OS 下的很多中文字体在 Windows 都没有预装,为了保证 MAC 用户的体验,在定义中文字体的时候,先定义 MAC 用户的中文字体,再定义 Windows 用户的中文字体;
  • 4、兼顾旧操作系统,以字体族系列 serifsans-serif 结尾

    • 当使用一些非常新的字体时,要考虑向下兼容,兼顾到一些极旧的操作系统,使用字体族系列 serif 和 sans-serif 结尾总归是不错的选择。

# 可访问性

可访问性,在我们的网站中,属于非常重要的一环,但是大部分前端(其实应该是设计、前端、产品)同学都会忽视它。

尤其在我们一些重交互、重逻辑的网站中,我们需要考虑用户的使用习惯、使用场景,从高可访问性的角度考虑,譬如假设用户没有鼠标,仅仅使用键盘,能否顺畅的使用我们的网站?

TIP

记住,无障碍设计对所有人都更友善。

# 色彩对比度

颜色,也是我们天天需要打交道的属性。对于大部分视觉正常的用户,可能对页面的颜色敏感度还没那么高。但是对于一小部分色弱、色盲用户,他们对于网站的颜色会更加敏感,不好的设计会给他们访问网站带来极大的不便。

# 检查色彩对比度的工具

# 焦点响应

并非所有的有输入框的页面,都需要进入页面后进行聚焦,但是焦点能够让用户非常明确的知道,当前自己在哪,需要做些什么。尤其是对于无法操作鼠标的用户。

页面上可以聚焦的元素,称为可聚焦元素,获得焦点的元素,则会触发该元素的 focus 事件,对应的,也就会触发该元素的 :focus 伪类。

当然,除了 Tab 键之外,对于一些多输入框、选择框的表单页面,我们也应该想着如何简化用户的操作,譬如用户按回车键时自动前进到下一字段。一般而言,用户必须执行的触按越少,体验越佳

通过元素的 :focus 伪类以及键盘 Tab 键切换焦点,用户可以非常顺畅的在脱离鼠标的情况下,对页面的焦点切换及操作。

# 保证非鼠标用户体验,合理运用 :focus-visible

当然,造成上述结果很重要的一个原因在于。:focus 伪类不论用户在使用鼠标还是使用键盘,只要元素获焦,就会触发。

  • :focus-visible:这个选择器可以有效地根据用户的输入方式(鼠标 vs 键盘)展示不同形式的焦点。

有了这个伪类,就可以做到,当用户使用鼠标操作可聚焦元素时,不展示 :focus 样式或者让其表现较弱,而当用户使用键盘操作焦点时,利用 :focus-visible,让可获焦元素获得一个较强的表现样式。

看个简单的 Demo:

<button>Test 1</button>
1
button:active {
  background: #eee;
}
button:focus {
  outline: 2px solid red;
}
1
2
3
4
5
6

使用鼠标点击:

可以看到,使用鼠标点击的时候,触发了元素的 :active 伪类,也触发了 :focus 伪类,不太美观。但是如果设置了 outline: none 又会使键盘用户的体验非常糟糕。尝试使用 :focus-visible 伪类改造一下:

button:active {
  background: #eee;
}
button:focus {
  outline: 2px solid red;
}
button:focus:not(:focus-visible) {
  outline: none;
}
1
2
3
4
5
6
7
8
9

看看效果,分别是在鼠标点击 Button 和使用键盘控制焦点点击 Button:

可以看到,使用鼠标点击,不会触发 :foucs,只有当键盘操作聚焦元素,使用 Tab 切换焦点时,outline: 2px solid red 这段代码才会生效。

值得注意的是,有同学会疑惑,这里为什么使用了 :not 这么绕的写法而不是直接这样写呢:

button:focus {
  outline: unset;
}
button:focus-visible {
  outline: 2px solid red;
}
1
2
3
4
5
6

为的是兼容不支持 :focus-visible 的浏览器,当 :focus-visible 不兼容时,还是需要有 :focus 伪类的存在。

# 使用 WAI-ARIA 规范增强语义 -- div 等非可获焦元素模拟获焦元素

WAI-ARIA

现在很多前端同学在前端开发的过程中,喜欢使用非可获焦元素模拟获焦元素,譬如:

  • 使用 div 模拟 button 元素
  • 使用 ul 模拟下拉列表 select 等等

当下很多组件库都是这样做的,譬如 element-ui 和 ant-design。

WARNING

在使用非可获焦元素模拟获焦元素的时候,一定要注意,不仅仅只是外观长得像就完事了,其行为表现也需要符合原本的 button、select 等可聚焦元素的性质,能够体现元素的语义,能够被聚焦,能够通过 Tab 切换等等。

简单来说,它提供了一些属性,增强标签的语义及行为:

  • 可以使用 tabindex 属性控制元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航
  • 可以使用 role 属性,来标识元素的语义及作用,譬如使用 <div id="saveChanges" tabindex="0" role="button">Save</div> 来模拟一个按钮
  • 还有大量的 aria-* 属性,表示元素的属性或状态,帮助我们进一步地识别以及实现元素的语义化,优化无障碍体验

# 使用工具查看标签的语义

p

这一块,清晰的描述了这个按钮在可访问性相关的一些特性,譬如

  • Contrast 色彩对比度
  • 按钮的描述,也就是 Name,是给屏幕阅读器看到的,
  • Role 标识是这个元素的属性,它是一个 tab,
  • Keyboard focusable 则表明他能否被键盘的 Tab 按钮给捕获

# 分析使用非可聚焦元素模拟的按钮

基本上可访问性为 0

  • 作为一个按钮,它不可被聚焦,
  • 无法被键盘用户选中,
  • 色彩对比度太低,可能视障用户无法看清。
  • 并且,作为一个能进行页面跳转的按钮,它没有不是 a 标签,没有 href 属性。
  • 即便对于面包屑导航,我们可以不将它改造成 <a> 标签,也需要做到最基本的一些可访问性改造:
<div role="button" aria-disabled="false" aria-selected="true" class="ant-tabs-tab-active ant-tabs-tab" tabindex="0">巡检计划设置</div>
1

不要忘了再改一下颜色,达到最低色彩对比度以上,

这样,一个最最最基本的,满足最低可访问性需求的按钮算是勉强达标,当然,这个按钮可以再更进一步进行改造,

# 分析组件库的 A11Y