前言
本文是基于 Shopee 供应链团队内部 WMS(Warehouse Management System,仓库管理系统) 项目的整体重构,总结而出的一份 Web 用户体验设计提升指南。因为是对已经存在的项目进行完全的推翻重构,所以在整个过程中,我们一直在思考如何尽可能地站在用户的角度,通过前端设计去提升改进用户的体验与感受,真正达到重构的目的及意义。
一个 Web 页面或是一个 App,想让别人用得爽,获得良好的用户体验,可能需要包括但不限于:
所谓的用户体验设计,是秉承着以用户为中心的思想的一种设计手段,以用户需求为目标而进行的设计。设计过程注重以用户为中心,用户体验的概念从开发的最早期就开始进入整个流程,并贯穿始终。
良好的用户体验设计,是团队在产品开发中每一个环节共同努力的结果。
本文将主要从页面呈现、交互细节、可访问性三个方面入手,分享一些在实际开发过程中积攒的有益经验。通过这篇文章,你将能:
就整个页面的展示和页面内容的呈现而言,不同的展示方式,所得到的效果截然不同。
这其中有非常多值得注意的细节。接下来分为几个要点进行阐述:
先来看一些布局相关的问题。
布局,是前端在重构页面过程中需要提前进行规划思考的,一般应该考虑清楚以下几个问题:
到今天,各种设备浩如烟海,移动端屏幕尺寸纷繁复杂(下图仅仅是到 2019 年各种安卓设备屏幕尺寸图的分布):
不过,我们的重构项目整体是以 PC 为主的 ToB 项目,所以这里主要以 PC 端为例进行讲解。
对于大部分 PC 端的项目,首先需要考虑的肯定是最外面的一层包裹。假设就是 .g-app-wrapper
。
<div class="g-app-wrapper">
<!-- 内部内容 -->
</div>
首先,对于 .g-app-wrapper
,有几点是我们在项目开发前必须弄清楚的:
对于定宽布局,就比较方便了,假设定宽为 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)。
看看效果:
这个需求如果能够使用 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 当然,实现它的方法有很多,这里仅给出一种推荐的解法。
下面这一块关于重点内容的展示。
让吸引用户注意力的元素前置。如果我们的页面存在需要让用户了解、处理的核心信息或者表单,尽可能将其位置放在上方,让用户更容易获取这部分信息。
将用户需要的信息、重要的功能展示出来而不是藏起来。
类似于导航、搜索等高频操作,一定不要让用户多次点击才能用到。
对于所有接收后端接口字段的文本展示类的界面,都需要考虑全面。正常情况如下,是没有问题的:
但是我们是否考虑到了文本会超长?超长了会折行还是换行?
对于单行文本,使用单行省略:
{
width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
当然,目前对于多行文本的超长省略,兼容性也已经非常好了:
{
width: 200px;
overflow : hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
对于一些动态内容,我们经常使用 min/max-width
或 min/max-height
对容器的高宽限度进行合理的控制。
在使用它们的时候,也有一些细节需要考虑到。
譬如经常会使用 min-width
控制按钮的最小宽度:
.btn {
...
min-width: 120px;
}
当内容比较少的时候是没问题的,但是如果内容比较长,就容易出现问题。下图就是使用了 min-width
却没考虑到按钮的过长的情况:
这里就需要配合 padding 一起:
.btn {
...
min-width: 88px;
padding: 0 16px
}
借用 Min and Max Width/Height in CSS 中一张非常好的图,作为释义:
这个模块是兼容不同场景与异常回退,是一个常常被忽略的地方。
页面经常会有列表搜索、列表展示。那么,既然存在数据的正常情况,当然也会存在搜索不到结果或者列表无内容可展示的情形。
对于这种情况,一定要注意空数据结果页面的设计;同时要知道,这也是引导用户的好地方。对于空数据结果页面,分清楚:
(1)数据为空
(2)异常状态
例如网络异常,可以引导用户刷新页面:
或者确实是零结果,譬如没有订单信息,可以引导用户去进行订单的创建(引导消费):
小小总结一下,上述篇幅一直都在阐述一个道理:开发时,不能仅仅关注正常现象,要多考虑各种异常情况,思考全面,做好各种可能情况的处理。
图片在我们的业务中非常常见。但是要完美处理图片,并不轻松。
有的时候产品和设计会商定,只能使用固定尺寸大小的图片,我们的布局可能是这样:
对应的布局:
<ul class="g-container">
<li>
<img src="http://placehold.it/150x100">
<p>图片描述</p>
</li>
</ul>
ul li img {
width: 150px;
}
假设后端接口出现一张非正常大小的图片,上述不加保护的布局就会出问题:
所以对于图片,我们总是建议同时写上高和宽,避免因为图片尺寸错误带来的布局问题。
ul li img {
width: 150px;
height: 100px;
}
另外,给 <img>
标签同时写上高宽,可以在图片加载之前提前占住位置,避免图片从未加载状态到渲染完成状态高宽变化引起的重排问题。
object-fit
限制高宽也可能会出现问题,比如图片被拉伸了,非常难看:
这个时候我们可以借助 object-fit
,它能够指定可替换元素的内容(也就是图片)该如何适应它的父容器的高宽。
ul li img {
width: 150px;
height: 100px;
object-fit: cover;
}
利用 object-fit: cover
,使图片内容在保持宽高比的同时填充元素的整个内容框。
object-fit
还有一个配套属性 object-position
,它可以控制图片在其内容框中的位置(类似于 background-position
),m 默认是 object-position: 50% 50%
。如果你不希望图片居中展示,可以使用它去改变图片实际展示的位置。
ul li img {
width: 150px;
height: 100px;
object-fit: cover;
object-position: 50% 100%;
}
像是这样,object-position: 100% 50%
指明从底部开始展示图片。
这里有一个很好的 Demo 可以帮助你理解
object-position
: CodePen Demo - Object position
正常情况下,图片的展示应该没有什么问题了,但我们还可以做得更好。
在移动端或者一些高清的 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'
/>
不过,这是比较旧的写法,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 的屏幕提供最适合的图片。
好了,当图片链接没问题时,已经处理好了。接下来还需要考虑,当图片链接挂了,应该如何处理。
处理的方式有很多种。最好的处理方式,是我在张鑫旭老师《图片加载失败后 CSS 样式处理最佳实践》这篇文章中看到的。这里简单讲讲:
<img>
元素的 onerror
事件,给加载失败的 <img>
元素新增一个样式类;<img>
元素的伪元素,展示默认兜底图的同时,还能一起展示 <img>
元素的 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
信息:
到此,完整的对图片的处理就算完成了,完整的 Demo 你可以戳这里看看:CodePen Demo - 图片处理。
好的页面呈现需要适当的过渡与动画,让整体交互体验更加流畅。适当增加过渡与动画,能够很好地让用户感知到页面的变化,它们有如下作用:
这一块内容也可以放在交互设计优化,读者朋友们了解就好。
页面上随处可见的 loading 效果,其实就是这样一种作用,让用户感知页面正在加载,或者正在处理某些事务。
骨架屏的布局与页面的视觉呈现保持一致,能引导用户将关注点聚焦到感兴趣的位置,并且能避免过长时间的等待。
scroll-behavior: smooth
让滚动丝滑使用 scroll-behavior: smooth
,可以让滚动框实现平稳地滚动,而不是突兀地跳动。看看效果,假设如下结构:
<div class="g-container">
<nav>
<a href="#1">1</a>
<a href="#2">2</a>
<a href="#3">3</a>
</nav>
<div class="scrolling-box">
<section id="1">First section</section>
<section id="2">Second section</section>
<section id="3">Third section</section>
</div>
</div>
不使用 scroll-behavior: smooth
,就是突兀地跳动切换:
要改善这种现象,可以给可滚动容器添加 scroll-behavior: smooth
,实现平滑滚动:
{
scroll-behavior: smooth;
}
scroll-snap-type
优化滚动效果sroll-snap-type
可能算得上是新的滚动规范里面最核心的一个属性样式。
scroll-snap-type:属性定义在滚动容器中的一个临时点(snap point)如何被严格地执行。
光看定义有点难理解,简单而言,这个属性规定了一个容器是否对内部滚动动作进行捕捉,并且规定了如何去处理滚动结束状态。让滚动操作结束后,元素停止在适合的位置。
看个简单示例:
当然,scroll-snap-type
用法非常多,可控制优化的点很多,限于篇幅无法一一展开。
这个优化可能稍微有一点难理解。需要了解 CSS 渲染优化的相关知识。
先说结论,控制滚动层级的意思是尽量让需要进行 CSS 动画(可以是元素的动画,也可以是容器的滚动)的元素的 z-index 保持在页面最上方,避免浏览器创建不必要的图形层(GraphicsLayer),能够很好地提升渲染性能。
这一点怎么理解呢?一个元素触发创建一个 Graphics Layer 层的其中一个因素是:
根据上述这点,我们对滚动性能进行优化的时候,需要注意两点:
从一个模块跳转到另外一个模块的时候,转场动画就派上了用场。它的作用在于:在合适的时机,将视线引导到适当的位置。
看下面的例子:
在点击按钮弹出弹窗的过程中,弹窗不是突兀地出现,而是从点击的地方放大至视窗中间,这个引导的过程让体验更加丝滑。
这个和 loading 有点类似,遇到一些耗时操作,比如下载时,我们可以通过定制一个特殊的动画,减缓用户等待的烦躁、焦虑感。
上述动画的代码,你可以猛击: CodePen Demo - Download interaction By Milan Raring
当然,除了下载等待,我们也可以在一些重要的操作交互上,例如点赞、关注等,定制特殊的动画,让过程更加生动有趣。下面这个是某网站的点赞动画:
上述点赞动画的代码,你可以猛击:CodePen Demo - Twitter 点赞动画
合理使用动画能让页面增色不少,但同时要避免踩入下面的一些坑:
简单解释一下。譬如动画关联性,关联性背后的逻辑能帮助用户在界面布局中理解刚发生的变化,是什么导致了变化。
下图中,左边是关联性差的,右边是关联性好的:
还有一点,大部分动画不宜过久,要足够迅速。
缓慢的动画产生了不必要的停顿。过渡动画应该保持简短,因为用户会频繁看到它们。让动画持续时间保持在 300ms 或更短。
看下图演示,同一个转场动画会被频繁触发,所以尽可能地让每次的动画不要持续过久,能够帮助用户节省更多时间。
总而言之,动画和过渡要用得恰当好处,避免为了动画而动画非常重要。
接下来一个大环节是一些关于交互的细节。
什么是交互设计?交互设计(Interaction Design, IXD)是定义、设计人造系统的行为的设计领域,它定义了两个或多个互动的个体之间交流的内容和结构,使之互相配合,共同达成某种目的。
Web 端交互围绕计算机为中心,主要涉及键盘、鼠标两类设备的交互。
关于交互设计,一些比较通用的准则有:
有一本书就叫 Don’t make me think,它所想表述的核心是,尽可能让产品做到高可用性,让用户无须思考也能明确无误地使用产品中的各项功能。
那么,我们的页面如何尽可能做到高可用性呢?
使用习惯用法。当我们设计一个新的页面时,页面位置、功能设置、视觉元素应当和惯常用法相差无几,这样才能让用户有舒适感。
这里并不是要扼杀大家的想象力,让所有页面都一成不变。根据产品的形态及受众,大部分页面是不适用于各种天马行空的想象的。
例如下面这样一个 PC 端页面结构,这种布局已经非常常见。遇到这种页面,用户能够非常快速地在指定区域找到想要的元素及信息:
可以再和我们的 WMS 重构后的页面作比较,整体是非常类似的:
采用习惯用法的设置能够让人快速上手新的陌生的页面或者功能。
另外一种做法是降低视觉噪声,让用户快速聚焦。
譬如弹窗背后的阴影、虚化,就是非常好的降低视觉噪声的手段。
这一点还是非常好理解的,也属于常见设计手段之一,在我们内部的相关组件、自研组件库已经沉淀得很好了。
接下来是符合用户的习惯与预期,它的意义在于减少用户的思考,符合用户的习惯,让用户的体验更加舒适。
下面这几种弹窗,哪一个是更好的选择?
三个弹窗可供操作的按钮分别是:
更好的选择应该是第一个:同时有取消和确认按钮,右上角的 X 可以关闭弹窗。因为这样最符合用户的习惯预期。
看看这个 Windows 下的弹窗:
大部分 Windows 的弹窗,都是有取消、确认加上右上角的 X 按钮的。这种弹窗的好处在于:
cursor
对于不同的内容,最好给与不同的 cursor
样式,CSS 原生提供了非常多种常用的手势。
在不同的场景使用不同的鼠标手势,符合用户的习惯与预期,可以很好地提升用户的交互体验。
首先对于按钮,就至少会有三种不同的 cursor
,分别是可点击、不可点击、等待中:
{
cursor: pointer; // 可点击
cursor: not-allowed; // 不可点击
cursor: wait; // loading
}
除此之外,还有一些常见用法:对于一些可输入的 Input 框,使用 cursor: text
;对于提示 Tips 类,使用 cursor: help
;放大缩小图片 zoom-in
、zoom-out
等等:
一些常用的简单列一列:
cursor: pointer
cursor: not-allowed
cursor: wait
cursor: text
cursor: zoom-in/ zoom-out
cursor: help
当然,实际 cursor
还支持非常多种,可以在 MDN 或者下面这个 CodePen Demo 中查看完整列表:
按钮是我们网页设计中十分重要的一环,而按钮的设计也与用户体验息息相关。
考虑这样一个场景:在摇晃的车厢上或者是单手操作着屏幕,有的时候一个按钮死活也点不到。
让用户更容易点击到按钮无疑能很好地提升用户体验,并提升页面的访问性。尤其是在移动端,按钮通常都很小,但是受限于设计稿或者整体 UI 风格,我们不能直接去改变按钮元素的高宽。
那么,有什么办法在不改变按钮原本大小的情况下增加点击热区呢?
借助伪元素可以轻松实现。伪元素也是可以代表其宿主元素来响应鼠标交互事件的。我们可以这样写:
.btn::before{
content:"";
position:absolute;
top:-10px;
right:-10px;
bottom:-10px;
left:-10px;
}
当然,在 PC 端下这样子看起来有点奇怪,但是合理地用在点击区域较小的移动端则能取得十分好的效果:
好的系统,操作起来应该是流畅的。同时,它能够通过一些小细节打动用户。
user-select: all
操作系统或者浏览器通常会提供一些快速选取文本的功能。看看下面的示意图:
快速单击两次,可以选中单个单词;快速单击三次,可以选中一整行内容。但如果核心内容被分隔符分割,或者成为潜藏在一整行中的一部分,这个时候选取起来就比较麻烦。
利用 user-select: all
,可以将需要一次选中的内容进行包裹,用户只需要点击一次,就可以选中该段信息:
.g-select-all {
user-select: all
}
给需要一次选中的信息加上这个样式,该细节作用在一些需要复制粘贴的场景时,非常好用。
在我们 WMS 的很多操作页面,需要频繁从表格中复制一些基础信息,例如下述的 Location ID
、Cell Name
,由于完整的字段使用了分隔符 -
,所以一次点击是无法选中整段信息的,而利用 user-select: all
可以很好地解决这个痛点:
别看只是减少一次点击鼠标的次数,但正是这些细节的累积,才更能让用户感受到开发者的用心。
::selection
CSS 还提供了一个 ::selection
伪类,可以控制选中的文本样式(只能控制 color
、background
、text-shadow
),进一步加深效果。
CodePen - user-select: all && ::selection 控制选中样式
user-select: none
有快速选择,也就会有它的对立面——禁止选择。
对于一些可能频繁操作的按钮,可能会出现如下尴尬场景:
对于这种场景,我们需要把不可被选中的元素设置为不可被选中,利用 CSS 可以快速实现这一点:
{
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
这样,无论点击频率多快,都不会出现内容被选中的尴尬情况:
现阶段,单页应用(Single Page Application)的应用非常广泛,Vue 、React 等框架大行其道。但是有的常见写法也容易衍生一些小问题。
譬如,点击按钮、文本进行路由跳转,经常会出现这种代码:
<template>
...
<button @click="gotoDetail">
Detail
</button>
...
<template>
...
gotoDetail() {
this.$router.push({
name: 'xxxxx',
});
}
大致逻辑就是给按钮添加一个事件,点击之后跳转到另外一个路由。当然,这个功能本身是没有问题的,但是没有考虑到用户实际使用的场景。
实际使用的时候,用户可能会希望保留当前页面的内容,同时打开一个新的窗口。这个时候,他会尝试点击鼠标右键,选择在新标签页中打开页面。遗憾的是,上述写法不支持鼠标右键打开新页面。
原因在于浏览器是通过读取 <a>
标签的 href
属性,来展示类似在新标签页中打开页面这种选项,对于上述写法,浏览器无法识别它是一个可以跳转的链接。简单的示意图如下:
所以,对于所有路由跳转按钮,建议都使用 <a>
标签,并且内置 href
属性,填写跳转的路由地址。实际渲染出来的 DOM 可能类似这样:
<a href="/xx/detail">Detail</a>
在实际的 WMS 重构过程中,我们对于所有有页面跳转功能的按钮,包括但不限于路由菜单、面包屑导航、跳转按钮等,都进行了跳转优化,以满足用户的不同诉求。
路由菜单导航:
表格中的一些按钮跳转:
输入及选择于用户而言,是一项高交互成本的操作。下面提供了一些小的建议来减少用户输入出错、提升用户体验。
将表单做得简单点,确保用户在抓狂之前能进入下一步(表单越复杂,流失率越高):
对于没法省去的输入项,尽可能简化用户的输入:
表单及时校验,而不是用户填完一堆信息,统一提交后才告诉用户填错了:
还有一些比较有益的建议,可以有效的提升交互过程中用户的体验,根据实际情况可以考虑:
这一点非常有意思,什么叫先探索后表态呢?就是我们不要一上来就强迫用户去做一些事情,例如登录。
想一想一些常用网站的例子:
上述易用性和先探索后表态的内容,部分来源于 “Learn From What Leading Companies A/B Test”,推荐阅读。
由于业务的类型限制,在这一块,我们实际中运用的并不多,但是它也是增强用户体验非常有益且重要的一环,下文将简单讲一讲。
这一类交互为结合产品的创意交互动画。通过定制化的有仪式感的交互,提升品牌价值,能给予用户深刻的印象。
结合产品及业务的创意动画,是需要挖掘,不断打磨、不断迭代的。比如 bilibili 官网的顶部 banner,配合一些节日、活动,经常会出现一些有意思的创意交互动画:
以及这个:
我非常多次在不同地方看到有人讨论 bilibili 的顶部 banner 动画,可见它这块的动画是成功的。很好地结合了一些节日、实事、热点,作为一种比较固定的产品去不断推陈出新,在不同时候带给用户不同的体验。
字体的选择与使用其实是非常有讲究的。
在 WMS 项目重构过程中,我们使用的全局字体定义是:
font-family: Roboto,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Fira Sans,Droid Sans,Helvetica Neue,"sans-serif";
。
这里,我们的设计师对英文字体有一些强限制,首选字体是 Roboto,并且在系统中提供了该字体包。
如果网站没有强制必须使用某些字体。最新的规范建议我们使用系统默认字体。也就是 “CSS Fonts Module Level 4: Generic font families” 中新增的 font-family: system-ui
关键字。它能够自动选择本操作系统下的默认系统字体。
默认使用特定操作系统的系统字体可以提高性能,因为浏览器或者 WebView 不必去下载任何字体文件,而是使用已有的字体文件。font-family: system-ui
字体设置的优势之处在于它与当前操作系统使用的字体相匹配,对于文本内容而言,它可以得到最恰当的展示。
举两个例子,天猫的字体定义与 Github 的字体定义:
font-family: "PingFang SC",miui,system-ui,-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,sans-serif;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
简单而言,它们总体遵循了以下几项基本原则:
使用系统默认字体的主要原因是性能,系统字体的优点在于它与当前操作系统相匹配,因此它的文本展示效果必然也是令人舒适的。
中文或者英文(西文)都要考虑到。由于大部分中文字体也带有英文部分,但是英文部分又不怎么好看,并且英文字体中大多不包含中文。因此我们通常会先进行英文字体的声明,选择最优的英文字体,这样不会影响到中文字体的选择。
选择字体的时候要考虑多操作系统。例如 macOS 下的很多中文字体在 Windows 都没有预装。为了保证 Mac 用户的体验,在定义中文字体的时候,先定义 Mac 用户的中文字体,再定义 Windows 用户的中文字体。
当使用一些非常新的字体时,要考虑向下兼容,兼顾到一些极旧的操作系统,使用字体族系列 serif 和 sans-serif 结尾总归是不错的选择。
可访问性(accessibility,也被称为 “A11Y”)在网站中属于非常重要的一环,但是大部分前端(其实应该是设计、前端、产品)同学都忽视了它。
传统上我们认为这只与残障人士有关,但提升网站的可访问性也可以让其他用户群体(每个人)受益。
根据网页内容可访问性准则(即 “Web Content Accessibility Guidelines (WCAG) 2.0”),Web 可访问性有以下四个基础性原则。
(1)可感知
(2)可操作
(3)可理解
(4)健壮性
我潜伏在一个叫无障碍设计小组的群里,其中包含了很多无障碍设计师以及患有一定程度视觉、听力、行动障碍的用户,他们在群里经常会表达出一个观点,就是国内的大部分 Web 网站及 App 基本没有考虑过残障人士的使用(或者可访问性做得很差),非常令人揪心。
尤其在一些重交互、重逻辑的网站中,我们需要从高可访问性的角度考虑用户的使用习惯、使用场景。比如:假设用户没有鼠标,仅仅使用键盘,能否顺畅使用我们的网站?
假设用户没有鼠标,这个真不一定是针对残障人士。很多情况下,用户拿鼠标的手可能在干其他事情,比如在吃东西;又或者在 To B 类的业务,如超市收银、仓库收货,很可能用户拿鼠标的手操作着其他设备(扫码枪)等等。
本文不会专门阐述无障碍设计的方方面面,只是从一些我觉得前端工程师需要关注的,并且仅需要花费少量代价就能做好的一些无障碍设计细节。记住,无障碍设计对所有人都更友善。
颜色,也是我们天天需要打交道的属性。大部分视觉正常的用户可能对页面的颜色敏感度还没那么高。但是对于一小部分色弱、色盲用户,他们对网站的颜色会更加敏感,不好的设计会给他们访问网站带来极大的不便。
是否曾关心过页面内容的展示,使用的颜色是否恰当?色弱、色盲用户能否正常看清内容?良好的色彩使用,在任何时候都是有益的,而且不仅仅局限于色弱、色盲用户。在户外使用手机,阳光很强看不清时,符合无障碍标准的高清晰度、高对比度文字就更容易阅读。
这里引入了一个概念——色彩对比度。简单地说,就是两种颜色在亮度(brightness)上的差别。运用到我们的页面上,大多数的情况就是背景色(background-color)与内容颜色(color)的对比差异。
“最权威的互联网无障碍规范——WCAG AA 规范规定,所有重要内容的色彩对比度需要达到 4.5:1 或以上(字号大于 18 号时达到 3:1 或以上),才算拥有较好的可读性。”
看看下面这张图:
很明显,对比度由低到高。前两个例子,别说视障用户,正常用户都已经很难看得清了。
当然,也会存在一些特例,譬如输入框的 placeholder
、按钮的禁用状态等等。因此,在网页重构的过程中,我们需要尽可能遵循这个规范。但又不是盲目遵循,导致毫无回旋余地。
Chrome 浏览器从很早开始,就已经支持检查元素的色彩对比度了。以 WMS 页面中的一个 Table Header 为例,我们可以去检查一些无法直观界定是否达到标准的文字:
审查元素,分别可以看到当前文字与背景的对比度。下述结果表示,这个对比度是没有问题的:
当然,我们的页面上也会存在这样的面包屑导航:
可以看到,色彩对比度没有达到标准的部分,被用黄色叹号标识了出来。
除此之外,在审查元素的 Style 界面的取色器,改变颜色,也能直观地看到当前的色彩对比度:
为了保证无障碍的准确性,应当确保你没有完全依赖颜色来展示系统不同层级的关键信息。
看看下面这个例子:
本来我们期待利用绿色和红色来表达正确与错误,但是对于部分视觉障碍用户,他可能根本感受不到这个提示。
正确的做法是,使用必要的文字和图标进行说明:
因此在实际应用中,我们需要利用能传达准确信息的图标配合文字描述去表达,譬如 WMS 登录错误页面:
类似百度、谷歌的首页,进入页面后会默认让输入框获得焦点:
并非所有的有输入框的页面都需要进入页面后进行聚焦,但是焦点能够让用户非常明确地知道当前自己在哪,需要做些什么。尤其是对于无法操作鼠标的用户来说。
页面上可以聚焦的元素,称为可聚焦元素,获得焦点的元素,则会触发该元素的 focus
事件。对应地,也会触发该元素的 :focus
伪类。
浏览器通常会使用元素的 :focus
伪类,给元素添加一层边框,告诉用户当前的获焦元素在哪里。
我们可以通过键盘的 Tab
键,进行焦点的切换。而获焦元素则可以通过元素的 :focus
伪类的样式,告诉用户当前焦点位置。
当然,除了
Tab
键之外,对于一些多输入框、选择框的表单页面,我们也应该想着如何简化用户的操作,例如用户按回车键时自动前进到下一字段。一般而言,用户必须执行的触按越少,体验越佳。👍🏻
下面的截图,完全由键盘操作完成:
通过元素的 :focus
伪类以及键盘 Tab 键切换焦点,用户可以非常顺畅的在脱离鼠标的情况下,对页面的焦点切换及操作。
然而,在许多 reset.css
中,经常能看到这样一句 CSS 样式代码,为了样式的统一,消除了可聚焦元素的 :focus
伪类:
:focus {
outline: 0;
}
我们给上述操作的代码。也加上这样一句代码,全程再用键盘操作一下:
除了在 input
框有光标提示,当使用 Tab
进行焦点切换到 select
或者到 button
时,由于没有了 :focus
样式,用户会感到迷茫,不知道页面的焦点现在处于何处。
:focus-visible
造成上述结果很重要的一个原因在于,:focus
伪类不论用户在使用鼠标还是使用键盘,只要元素获焦,就会触发。
而其本身的默认样式又不太能被产品或者设计接受,导致了很多人会在焦点元素触发 :focus
伪类时,通过改变 border 的颜色或者其他一些方式替代或者直接禁用。而这样做,从可访问性的角度来看,对于非鼠标用户,无疑是灾难性的。
基于此,在 W3 CSS selectors-4 规范中,新增了一个非常有意思的 :focus-visible
伪类。这个选择器可以有效地根据用户的输入方式(鼠标或是键盘)展示不同形式的焦点。
有了这个伪类,当用户使用鼠标操作可聚焦元素时,就可以做到不展示 :focus
样式或者让其表现较弱;而当用户使用键盘操作焦点时,利用 :focus-visible
,让可获焦元素获得一个较强的表现样式。
看个简单的 Demo:
<button>Test 1</button>
button:active {
background: #eee;
}
button:focus {
outline: 2px solid red;
}
使用鼠标点击:
可以看到,使用鼠标点击的时候,触发了元素的 :active
伪类,也触发了 :focus
伪类,不太美观。但是如果设置了 outline: none
又会使键盘用户的体验非常糟糕。尝试使用 :focus-visible
伪类改造一下:
button:active {
background: #eee;
}
button:focus {
outline: 2px solid red;
}
button:focus:not(:focus-visible) {
outline: none;
}
看看效果,分别是在鼠标点击 Button 和使用键盘控制焦点点击 Button:
CodePen Demo - :focus-visible example
可以看到,使用鼠标点击,不会触发 :foucs
,只有当键盘操作聚焦元素,使用 Tab
切换焦点时,outline: 2px solid red
这段代码才会生效。
这样,我们就既保证了正常用户的点击体验,也保证了一批无法使用鼠标的用户的焦点管理体验。
值得注意的是,有同学会疑惑,这里为什么使用了 :not
这么绕的写法而不是直接这样写呢:
button:focus {
outline: unset;
}
button:focus-visible {
outline: 2px solid red;
}
为的是兼容不支持 :focus-visible
的浏览器,当 :focus-visible
不兼容时,还是需要有 :focus
伪类的存在。
我们在实际的 WMS 重构过程中,也会尽量保持这一点,让用户尽可能在非鼠标操作下(仅仅使用键盘),也能使用我们的页面,能做到基础的焦点切换、回车响应事件。
下图是一个简单的演示(仅仅使用键盘进行页面的操作,能够知道当前焦点在哪,可以回车触发点击):
div
等非可获焦元素模拟获焦元素还有一个非常需要注意的点。
现在很多前端同学在前端开发的过程中,喜欢使用非可获焦元素模拟获焦元素,例如:
div
模拟 button
元素ul
模拟下拉列表 select
等等当下很多组件库都是这样做的,像是 element-ui 和 ant-design。
在使用非可获焦元素模拟获焦元素的时候,一定要注意,不仅仅只是外观长得像就完事了,其行为表现也需要符合原本的 button
、select
等可聚焦元素的性质,能够体现元素的语义,能够被聚焦,能够通过 Tab 切换等等。
基于大量类似的场景,有了 WAI-ARIA 标准。它是一个为残疾人士等提供无障碍访问动态、可交互 Web 内容的技术规范。
简单来说,WAI-ARIA 提供了一些属性,用于增强标签的语义及行为:
tabindex
属性控制元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航role
属性,来标识元素的语义及作用,譬如使用 <div id="saveChanges" tabindex="0" role="button">Save</div>
来模拟一个按钮aria-*
属性,表示元素的属性或状态,帮助我们进一步地识别以及实现元素的语义化,优化无障碍体验我们来看看 Github 页面是如何定义一个按钮的。以 Github Issues 页面的 Edit 按钮为例:
这一块清晰地描述了这个按钮在可访问性相关的一些特性。比如 Contrast
(色彩对比度)、Name
(按钮的描述),是给屏幕阅读器看到的;Role
标识是这个元素的属性,它是一个按钮,Keyboard focusable
则表明能否被键盘的 Tab 按钮捕获。
这里随便选取了我们业务中一个使用 span 模拟按钮的场景,是一个面包屑导航,点击可进行跳转:
HTML 代码:
<span class="ssc-breadcrumb-item-link"> Inbound </span>
基本上可访问性为零。作为一个按钮,它不可被聚焦,无法被键盘用户选中,没有具体的语义,色彩对比度太低,可能视障用户无法看清。并且,作为一个能进行页面跳转的按钮,它没有 <a>
标签,没有 href
属性。
对于面包屑导航,我们可以不将它改造成 <a>
标签,但也需要做到最基本的一些可访问性改造:
<span role="button" aria-label="goto inbound page" tabindex="0" class="ssc-breadcrumb-item-link"> Inbound </span>
不要忘了再改一下颜色,达到最低色彩对比度以上,再看看:
这样,一个最最最基本的,满足最低可访问性需求的按钮算是勉强达标。当然,这个按钮可以再更进一步进行改造,涉及了更深入的可访问性知识,本文不详细展开。
最后,我们来看看常用的 ant-design 在提升可访问性上的一些相关功能。
以 Select 选择框组件为例,ant-design 利用了大量的 WAI-ARIA 属性,使得用 div 模拟的下拉框不仅仅在表现上符合一个下拉框,在语义、行为上都符合一个下拉框。
看看使用 div 模拟下拉框的 DOM 部分:
再看看在交互体验上:
上述操作完全在键盘下完成,看着平平无奇,实际上组件库在正常响应可获焦元素切换的同时,给用 div 模拟的 select
加了很多键盘事件的响应,可以利用回车、上下键等对可选项进行选择。其实下了很多功夫。
对于可访问性相关的内容非常多,本文无法一一展开,这里有一份简单的指南:
本文从页面展示、交互细节、可访问性三个大方面入手,列举了一些在实际开发过程中积攒的有益经验。虽然不够全面,主要是一些可能有用但是容易被忽视的点,也算是一个不错的查缺补漏小指南。
提升用户体验并非易事,但也不难:
以上观点和想法可能有一些理解存在问题,一些概念没有解读到位,也希望大家参与交流并指正。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。