前端优秀实践不完全指南

来源:掘金
2021-02-27
1626
导语:本文从页面展示、交互细节、可访问性三个大方面入手,罗列一些在实际的开发过程中,积攒的一些有益的经验。虽然不够全面,不过从一开始也就没想着大而全,主要是一些可能有用但是容易被忽视的点,也算是一个不错的查缺补漏小指南。
阅读本文大概需要12分钟

本文其实应该叫,Web 用户体验设计提升指南。

一个 Web 页面,一个 APP,想让别人用的爽,也就是所谓的良好的用户体验,我觉得他可能包括但不限于:

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

所谓的用户体验设计,其实是一个比较虚的概念,是秉承着以用户为中心的思想的一种设计手段,以用户需求为目标而进行的设计。设计过程注重以用户为中心,用户体验的概念从开发的最早期就开始进入整个流程,并贯穿始终。

良好的用户体验设计,是产品每一个环节共同努力的结果。

除去一些很难一蹴而就的,本文将就页面展示交互细节可访问性三个方面入手,罗列一些在实际的开发过程中,积攒的一些有益的经验。通过本文,你将能收获到:

  1. 了解到一些小细节是如何影响用户体验的
  2. 了解到如何在尽量小的开发改动下,提升页面的用户体验
  3. 了解到一些优秀的交互设计细节
  4. 了解基本的无障碍功能及页面可访问性的含义
  5. 了解基本的提升页面可访问性的方法

页面展示

就整个页面的展示,页面内容的呈现而言,有一些小细节是需要我们注意的。

整体布局

先来看看一些布局相关的问题。

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

<div class="g-app-wrapper">
    
div>
复制代码

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

  1. 项目是全屏布局还是定宽布局?
  2. 对于全屏布局,需要适配的最小的宽度是多少?

对于定宽布局,就比较方便了,假设定宽为 1200px,那么:

.g-app-wrapper {
    width: 1200px;
    margin: 0 auto;
}
复制代码

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

">

对于现代布局,更多的是全屏布局。其实现在也更提倡这种布局,即使用可随用户设备的尺寸和能力而变化的自适应布局。

通常而言是左右两栏,左侧定宽,右侧自适应剩余宽度,当然,会有一个最小的宽度。那么,它的布局应该是这样:

<div class="g-app-wrapper">
    <div class="g-sidebar">div>
    <div class="g-main">div>
div>
复制代码
.g-app-wrapper {
    display: flex;
    min-width: 1200px;
}
.g-sidebar {
    flex-basis: 250px;
    margin-right: 10px;
}
.g-main {
    flex-grow: 1;
}
复制代码

">

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

当然,这是最基本的自适应布局。对于现代布局,我们应该尽可能的考虑更多的场景。做到:

">

底部 footer

下面一种情形也是非常常见的一个情景。

页面存在一个 footer 页脚部分,如果整个页面的内容高度小于视窗的高度,则 footer 固定在视窗底部,如果整个页面的内容高度大于视窗的高度,则 footer 正常流排布(也就是需要滚动到底部才能看到 footer)。

看看效果:

">

嗯,这个需求如果能够使用 flex 的话,使用 justify-content: space-between 可以很好的解决,同理使用 margin-top: auto 也非常容易完成:

<div class="g-container">
    <div class="g-real-box">
        ...
    div>
    <div class="g-footer">div>
div>
复制代码
.g-container {
    height: 100vh;
    display: flex;
    flex-direction: column;
}

.g-footer {
    margin-top: auto;
    flex-shrink: 0;
    height: 30px;
    background: deeppink;
}
复制代码

Codepen Demo -- sticky footer by flex margin auto

当然,实现它的方法有很多,这里仅给出一种推荐的解法。

处理动态内容 - 文本超长

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

image">

但是我们是否考虑到了文本会超长?超长了会折行还是换行?

image">

对于单行文本,使用单行省略:

{
    width: 200px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
复制代码

image">

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

{
    width: 200px;
    overflow : hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
复制代码

image">

处理动态内容 - 保护边界

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

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

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

.btn {
    ...
    min-width: 120px;
}
复制代码

image

当内容比较少的时候是没问题的,但是当内容比较长,就容易出现问题。使用了 min-width 却没考虑到按钮的过长的情况:

image

这里就需要配合 padding 一起:

.btn {
    ...
    min-width: 88px;
    padding: 0 16px
}
复制代码

借用Min and Max Width/Height in CSS中一张非常好的图,作为释义:

min-width-2">

0 内容展示

这个也是一个常常被忽略的地方。

页面经常会有列表搜索,列表展示。那么,既然存在有数据的正常情况,当然也会存在搜索不到结果或者列表无内容可展示的情形。

对于这种情况,一定要注意 0 结果页面的设计,同时也要知道,这也是引导用户的好地方。对于 0 结果页面,分清楚:

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

不同的情况可能对应不同的 0 结果页面,附带不同的操作引导。

譬如网络异常:

image">

或者确实是 0 结果:

image">

关于 0 结果页面设计,可以详细看看这篇文章:如何设计产品的空白页面?

小小总结一下,上述比较长的篇幅一直都在阐述一个道理,开发时,不能仅仅关注正常现象,要多考虑各种异常情况,思考全面。做好各种可能情况的处理

图片相关

图片在我们的业务中应该是非常的常见了。有一些小细节是需要注意的。

给图片同时设置高宽

有的时候和产品、设计会商定,只能使用固定尺寸大小的图片,我们的布局可能是这样:

image">

对应的布局:

<ul class="g-container">
    <li>
        <img src="http://placehold.it/150x100">
        <p>图片描述p>
    li>
ul>
复制代码
ul li img {
    width: 150px;
}
复制代码

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

image">

所以对于图片,我们总是建议同时写上高和宽,避免因为图片尺寸错误带来的布局问题:

ul li img {
    width: 150px;
    height: 100px;
}
复制代码

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

object-fit

当然,限制高宽也会出现问题,譬如图片被拉伸了,非常的难看:

image">

这个时候,我们可以借助 object-fit,它能够指定可替换元素的内容(也就是图片)该如何适应它的父容器的高宽。

ul li img {
    width: 150px;
    height: 100px;
    object-fit: cover;
}
复制代码

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

image">

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

ul li img {
    width: 150px;
    height: 100px;
    object-fit: cover;
    object-position: 50% 100%;
}
复制代码

image">

像是这样,object-position: 100% 50% 指明从底部开始展示图片。这里有一个很好的 Demo 可以帮助你理解 object-position

CodePen Demo -- Object position

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

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

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

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

<img src='photo@1x.png'
   srcset='photo@1x.png 1x,
           photo@2x.png 2x,
           photo@3x.png 3x' 
/>
复制代码

当然,这是比较旧的写法,srcset 新增了新的 w 宽度描述符,需要配合 sizes 一起使用,所以更好的写法是:

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

利用 srcset,我们可以给不同 dpr 的屏幕,提供最适合的图片。

上述出现了一些概念,dpr,图片的 srcset ,sizes 属性,不太了解的可以移步 前端基础知识概述

图片丢失

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

处理的方式有很多种。最好的处理方式,是我最近在张鑫旭老师的这篇文章中 -- 图片加载失败后CSS样式处理最佳实践 看到的。这里简单讲下:

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

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

img.error::after {
    content: attr(alt);
    /** 定位代码 **/
}
复制代码

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

image">

OK,到此,完整的对图片的处理就算完成了,完整的 Demo 你可以戳这里看看:

CodePen Demo -- 图片处理

交互设计优化

接下来一个大环节是关于一些交互的细节。对于交互设计,一些比较通用的准则:

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

过渡与动画

在我们的交互过程中,适当的增加过渡与动画,能够很好的让用户感知到页面的变化

譬如我们页面上随处可见 loading 效果,其实就是这样一种作用,让用户感知页面正在加载,或者正在处理某些事务。

">

滚动优化

滚动也是操作网页中非常重要的一环。看看有哪些可以优化的点:

滚动平滑:使用 scroll-behavior: smooth 让滚动丝滑

使用 scroll-behavior: smooth,可以让滚动框实现平稳的滚动,而不是突兀的跳动。看看效果,假设如下结构:

<div class="g-container">
  <nav>
    <a href="#1">1a>
    <a href="#2">2a>
    <a href="#3">3a>
  nav>
  <div class="scrolling-box">
    <section id="1">First sectionsection>
    <section id="2">Second sectionsection>
    <section id="3">Third sectionsection>
  div>
div>
复制代码

不使用 scroll-behavior: smooth,是突兀的跳动切换:

scrol">

给可滚动容器添加 scroll-behavior: smooth,实现平滑滚动:

{
    scroll-behavior: smooth;
}
复制代码

scroll2

使用 scroll-snap-type 优化滚动效果

sroll-snap-type 可能算得上是新的滚动规范里面最核心的一个属性样式。

scroll-snap-type:属性定义在滚动容器中的一个临时点(snap point)如何被严格的执行。

光看定义有点难理解,简单而言,这个属性规定了一个容器是否对内部滚动动作进行捕捉,并且规定了如何去处理滚动结束状态。让滚动操作结束后,元素停止在适合的位置。

看个简单示例:

当然,scroll-snap-type 用法非常多,可控制优化的点很多,限于篇幅无法一一展开,具体更详细的用法可以看看我的另外一篇文章 -- 使用 sroll-snap-type 优化滚动

控制滚动层级,避免页面大量重排

这个优化可能稍微有一点难理解。需要了解 CSS 渲染优化的相关知识。

先说结论,控制滚动层级的意思是尽量让需要进行 CSS 动画(可以是元素的动画,也可以是容器的滚动)的元素的 z-index 保持在页面最上方,避免浏览器创建不必要的图形层(GraphicsLayer),能够很好的提升渲染性能

这一点怎么理解呢,一个元素触发创建一个 Graphics Layer 层的其中一个因素是:

  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素

根据上述这点,我们对滚动性能进行优化的时候,需要注意两点:

  1. 通过生成独立的 GraphicsLayer,利用 GPU 加速,提升滚动的性能
  2. 如果本身滚动没有性能问题,不需要独立的 GraphicsLayer,也要注意滚动容器的层级,避免因为层级过高而被其他创建了 GraphicsLayer 的元素合并,被动的生成一个 Graphics Layer ,影响页面整体的渲染性能

如果你对这点还有点懵,可以看看这篇文章 -- 你所不知道的 CSS 动画技巧与细节

点击交互优化

在用户点击交互方面,也有一些有意思的小细节。

优化手势 -- 不同场景应用不同 cursor

对于不同的内容,最好给与不同的 cursor 样式,CSS 原生提供非常多种常用的手势。

在不同的场景使用不同的鼠标手势,符合用户的习惯与预期,可以很好的提升用户的交互体验。

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

{
    cursor: pointer;    // 可点击
    cursor: not-allowed;    // 不可点击
    cursor: wait;    // loading
}
复制代码

image

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

image">

一些常用的简单列一列:

  • 按钮可点击: cursor: pointer
  • 按钮禁止点击:cursor: not-allowed
  • 等待 Loading 状态:cursor: wait
  • 输入框:cursor: text;
  • 图片查看器可放大可缩小:cursor: zoom-in/ zoom-out
  • 提示:cursor: help;

当然,实际 cursor 还支持非常多种,可以在 MDN 或者下面这个 CodePen Demo 中查看这里看完整的列表:

CodePen Demo -- Cursor Demo

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

按钮是我们网页设计中十分重要的一环,而按钮的设计也与用户体验息息相关。

考虑这样一个场景,在摇晃的车厢上或者是单手操作着屏幕,有的时候一个按钮,死活也点不到。

让用户更容易的点击到按钮无疑能很好的增加用户体验及可提升页面的访问性,尤其是在移动端,按钮通常都很小,但是受限于设计稿或者整体 UI 风格,我们不能直接去改变按钮元素的高宽。

那么这个时候有什么办法在不改变按钮原本大小的情况下去增加他的点击热区呢?

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

.btn::befoer{
  content:"";
  position:absolute;
  top:-10px;
  right:-10px;
  bottom:-10px;
  left:-10px;
}
复制代码

当然,在 PC 端下这样子看起来有点奇怪,但是合理的用在点击区域较小的移动端则能取到十分好的效果,效果如下:

608782-20160527112625428-906375003">

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

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

操作系统或者浏览器通常会提供一些快速选取文本的功能,看看下面的示意图:

layout3">

快速单击两次,可以选中单个单词,快速单击三次,可以选中一整行内容。但是如果有的时候我们的核心内容,被分隔符分割,或者潜藏在一整行中的一部分,这个时候选取起来就比较麻烦。

利用 user-select: all,可以将需要一次选中的内容进行包裹,用户只需要点击一次,就可以选中该段信息:

.g-select-all {
    user-select: all
}
复制代码

给需要一次选中的信息,加上这个样式后的效果,这个细节作用在一些需要复制粘贴的场景,非常好用:

layout4">

CodePen -- user-select: all 示例

选中样式优化 -- ::selection

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

layout5">

CodePen -- user-select: all && ::selection 控制选中样式

添加禁止选择 -- 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 */
}
复制代码

这样,无论点击的频率多快,都不会出现尴尬的内容选中:

btn-click-unselect">

跳转优化

现阶段,单页应用(Single Page Application)的应用非常广泛,Vue 、React 等框架大行其道。但是一些常见的写法,也容易衍生一些小问题。

譬如,点击按钮、文本进行路由跳转。譬如,经常会出现这种代码: