CSS 颜色与对比
本章概要
- 将设计师的视觉稿转为 HTML 和 CSS 的方法
- 利用对比设计将用户注意力吸引到页面最重要部分的方法
- 颜色的选取及相关处理
- 深入理解色彩空间及 CSS 的各类颜色表示方法
- 深度解析 OKLCH 色彩空间
- 与颜色相关的无障碍浏览问题
使用 CSS 和 HTML 来实现设计师提供的视觉稿(designer mockup),是 Web 开发中一个至关重要的环节。着手这类实现工作,其实也是将艺术作品高效转为代码的过程。这种转化过程,有时是简单明了的,而更多的时候则往往需要和设计师进行沟通,最终采取折中的方案。设计师对视觉稿做出的每一次微调,都需要我们站在系统组织 CSS 代码的角度综合考虑,使其更容易复用。相较于单页面的视觉稿,我们的 CSS 代码应该更具通用性。
转化工作完成后,基于设计师的最初构想、继续完成网站开发的重要任务就落到了开发者身上。您至少应该具备一些设计师的基本素养,站在设计师的角度去思考间距、颜色和排版等方面的问题,这一点极其重要。您得知道怎样确保最终实现的效果是准确的。如果您认可设计师的目标,那整个过程就会比较顺利。
当然了,您身边可能并非总有一名配合默契的设计师,要是身处一家小型创业公司或者面对的是某个个人项目,就只能靠您自己了。在这种情况下,掌握一些基本的设计原则无疑是大有裨益的,这样一来就能自行设计了。
接下来的两章内容将介绍如何像设计师一样思考,完成页面视觉稿,并将其转化为代码。本章将重点关注颜色的处理;第 12 章则重点讨论排版与间距方面的问题。设计稿的转换过程将为这些 CSS 主题的实际研究提供一套行之有效的思维框架。而颜色本身也是个很大的话题,并且在过去几年中发生了很大变化,因此单列出来进行介绍。
其间,我还将强调一些设计师可能会考虑的因素,旨在让您在学完这些内容之后,可以在一定程度上将其应用到实际项目设计中,甚至无需设计师参与也能推进项目开发。为此,需要构建如图 11.1 所示的演示页面。
图 11.1 为 Ink 协同软件设计的最终网页效果图
以上截图即为要实现的页面最终效果。如果是从设计师处拿到的设计稿,那么可能还会带有很多附加信息,后续会专门予以介绍;这里先明确设计过程中的几个要素。
11.1 通过对比进行交流
Communicating with contrast
当您看到图 11.1 所示的截图时,留意一下您的视线会落在哪里。大概率是被 Team collaboration done right
这句广告语和它下面的 Get Started
按钮吸引了。您也会看到页面上的一些别的东西 —— 像左上角的公司名称、右上角的导航菜单、页面下方的三栏内容等等 —— 但图片中间的内容无疑是最具吸引力的。原因就在于使用了 对比(contrast)。
对比是设计中的一种手段,通过突出某物来达到吸引注意力的目的。我们的眼睛和思维天生对模式(patterns)比较敏感。一旦某样东西破坏了模式的整体效果,我们的注意力就会立即转移到它身上。
若要起到对比的效果,页面就必须先建立起模式;如果连规矩都没有,打破规矩就更无从谈起了。在图 11.1 中,“Read More” 字样的导航按钮间的间距保持一致,其所在的三个内容栏的大小和间距也是一样的。此外,三个 “Read More” 按钮样式也完全相同。虽然页面颜色不尽相同,但都来自相同的绿色色调,只是明暗深浅不同罢了。这也是模块化 CSS 为何如此重要的一个原因(详见第 9 章)—— 我们要创建的是一个可以在任意位置使用的按钮,而不是通过层层嵌套的选择器构建的“某个板块下的按钮”(“button-in-a-tile”)。
推行复用样式代码后,网站上就能确保出现一致的模式。专业设计师的一个核心思路,就是先建立统一的模式,然后打破模式、突出页面中最重要的部分。
使用不同的颜色、间距和大小是建立对比的一些关键手法。如果好几个对象(items)是亮色的,还剩一个是暗色的,那您就会率先注意到这个暗色的对象;如果某个项目周围有很多无用的间距(也称 留白(whitespace)),那它就会比较突出;同理,尺寸较大的元素也会从一系列较小元素中脱颖而出。为了达到更强烈的对比效果,还可以多种方法组合使用,就像上面示例页中用到的那句 Team Collaboration Done Right
标语那样,它的字号偏大、且周围有留白,此外还紧贴着一个醒目的深色按钮。
不过标语部分并不是页面上唯一用到对比的地方。您会发现,通过对比,信息的重要程度和信息传递效果都有了层次感。除了标语和 “Get Started” 开始按钮,导航菜单(如图 11.2 所示)和页面底部的每个板块(如图 11.3 所示)明显也用到了对比。虽然这些元素的对比程度不如标语部分那么强烈,但在各自的区域内部也都能吸引人们的注意。而页脚是整个页面中相对不太重要的内容,因此设计得很小,也没用到什么对比。
图 11.2 亮色的登录按钮比其他三个深绿色按钮更引人注目
【图 11.3 带彩色文字和边框效果按钮在清一色黑白文字中格外显眼】
每个网页都应该有一个目的,或许是讲述某个故事,或许是为了收集信息,亦或是要求用户完成某类任务。除了核心目的,还可能会有导航元素、文本段落、广告以及包含版权信息及友情链接的页脚等。设计师的工作是让最重要的信息凸显出来;而我们开发者的工作,则是不要弄乱设计师的设计。
11.1.1 模式的建立
Establishing patterns
为了创建模式,有些在我们看来并没有那么重要的细节,在设计师的眼中反而会倍受关注。比如某些元素间的精确距离,对多个不同的组件使用相同的边框圆角与盒阴影(box shadow)。设计师甚至还会关注字符的间距和文本行间距。
图 11.4 展示了一张视觉稿,它使用像素单位精确标注了各个子元素的间距。在将设计稿转为代码时,保证精确还原这个过程可能稍显枯燥与繁琐(有时甚至还很棘手)。
图 11.4 带有测量标注的页面设计视觉稿
视觉稿里使用了几个小方框来标注这些测量过的间距。例如,导航菜单中的按钮相互之间的距离为 10px
,主图的底端和三个白底内容栏之间的距离为 40px
,各分栏标题以及后续的文本段落之间的距离为 30px
,等等。有些固定的间距长度可能会在页面上多次重复出现,这有助于建立模式的视觉一致性。比如,10px
和 25px
的间距在该页面就较为通用。
下面再来看看紧凑设计的一个关键方面:颜色选择(间距和排版的控制也非常重要,下一章将重点关注)。我们将演示如何精确地还原如图 11.4 所示设计。同时也必须意识到网站是会随时间不断演化的。还原视觉稿只是完成了一部分工作,后续可能还需要添加新的特性功能和新的内容,需要始终忠于设计师的愿景。为此,再来看看做好这类工作还需要考虑哪些重要因素。
11.1.2 还原设计稿
Implementing the design
先创建一个新页面,并关联到一个新的样式表。然后将代码清单 11.1 中的 HTML 标记复制到页面中。我们会把整个设计页面划分为多个模块(modules),后续章节会讲解如何编写样式。
代码清单 11.1 页面 HTML 代码
<!doctype html>
<html lang="en-US">
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<nav class="nav-container"><!-- 顶部导航容器 -->
<div class="nav-container__inner">
<!--<a class="home-link" href="/">Ink</a>-->
<ul class="top-nav">
<li><a href="/features">Features</a></li>
<li><a href="/pricing">Pricing</a></li>
<li><a href="/support">Support</a></li>
<li class="top-nav__featured"><a href="/login">Login</a></li>
</ul>
</div>
</nav>
<div class="hero"><!-- 巨幅主图 -->
<div class="hero__inner">
<h2>Team collaboration done right</h2>
<a href="/sign-up" class="button button--cta">Get started</a>
</div>
</div>
<div class="container">
<div class="tile-row"><!-- 三栏板块 -->
<div class="tile">
<h4>Work together, even if you're apart</h4>
<p>Organize your team conversations into threads. Collaborate together on documents in real-time. Use face-to-face <a href="/features/video-calling">video calls</a> when typing just won't do.</p>
<a href="/collaboration" class="button">Read more</a>
</div>
<div class="tile">
<h4>Take it with you</h4>
<p>Ink is available on a wide array of devices, so you can work from anywhere:</p>
<ul class="tag-list">
<li>Web</li>
<li>iOS</li>
<li>Android</li>
<li>Windows Phone</li>
</ul>
<a href="/supported-devices" class="button">Read more</a>
</div>
<div class="tile">
<h4>Priced right</h4>
<p>Whether you work on a team of three or a three hundred, you'll find our tiered pricing reasonable at every level.</p>
<a href="/pricing" class="button">Read more</a>
</div>
</div>
</div>
<footer class="page-footer">
<div class="page-footer__inner">
Copyright © 2024 Ink, Inc.
</div>
</footer>
</body>
</html>
以上代码使用了 BEM 风格来为样式类命名,以便清楚地知道哪个元素属于哪个模块。双下划线表示模块的子元素,例如 hero__inner
;双连字符代码模块的变体形式,例如 button--cta
(更多 BEM 用法详见第 9 章)。我们将以自己的方式逐步实现这些模块。第一步先来看看它们所使用的颜色。
11.2 颜色的定义
Defining color
设计师交付设计稿时,通常会提供一份包含几个部分的大型文档。该文档的大部分篇幅多半为类似图 11.4 所示的整页视觉稿。但在此之前,设计师需要先准备一些基础设计。文档中可能会包含一两页关于多种标题和正文副本的排版示例,也可能会有一些基础 UI 元素的详情列表,比如链接和按钮设计,同时包含它们的各种状态,比如鼠标悬停和激活状态。此外还会包含网站用到的调色板(color palette)。
调色板的外观如图 11.5 所示。其中列出了全站使用的所有颜色样本以及对应的十六进制颜色值。设计师通常会为每种颜色分别命名,并在后续的明细规范文档中引用这些命名。
图 11.5 网站的调色板
调色板通常会有一种主色调,其他颜色均以该主色调为基础。主色一般从公司的品牌或者 LOGO 商标中衍生而来。在本例中,主色调即为品牌绿(位于图 11.5 左上角)。调色板中的其他颜色一般取同一色调中明暗各异的颜色或者与该色调互补的颜色。大部分调色板都会包含黑色和白色(尽管可能不是纯黑的 #000000
或纯白的 #ffffff
),以及少量的灰色。
因为这些颜色会在 CSS 中多次反复出现,所以将它们赋给自定义属性可以节省大量时间;否则只能一遍又一遍地反复输入这些十六进制值,无法保证一定不出差错。
我们先为页面同一添加一些基础样式,包括将调色板中的每种颜色赋给特定的变量,为后续的复用创造条件。图 11.6 所示的页面效果感觉不怎么像视觉稿,但从颜色上看已经开始贴近视觉稿了。
图 11.6 添加了基础样式和部分颜色的页面效果
然后将代码清单 12.2 中的样式添加到本地样式表。
代码清单 11.2 带有颜色变量的全局样式代码
@layer reset, theme, global, layout, modules, utility;
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: unset;
}
button,
input,
textarea,
select {
font: inherit;
}
img,
picture,
svg,
canvas {
display: block;
max-width: 100%;
}
}
@layer theme {
:root { /* 将每种颜色赋给某个自定义属性 */
--brand-green: #076448;
--dark-green: #099268;
--medium-green: #20c997;
--text-color: #212529;
--gray: #868e96;
--light-gray: #f1f3f5;
--extra-light-gray: #f8f9fa;
--white: #fff;
}
}
@layer global {
body {
font-family: Helvetica, Arial, sans-serif;
line-height: 1.4;
background-color: var(--extra-light-gray); /* 在需要指定颜色的位置使用自定义属性值 */
color: var(--text-color);
}
h1,
h2,
h3,
h4 {
font-family: Georgia, serif; /* 设置标题文字 */
}
a {
color: var(--medium-green); /* 在需要指定颜色的位置使用自定义属性值 */
}
a:visited {
color: var(--brand-green); /* 在需要指定颜色的位置使用自定义属性值 */
}
a:hover {
color: var(--brand-green); /* 在需要指定颜色的位置使用自定义属性值 */
}
a:active {
color: var(--red); /* 先为链接元素的激活状态占位,后面再来定义该红色的具体取值 */
}
}
跟前面章节的做法一样,这里也给需要的颜色用上了自定义属性。有了这些变量,后续一旦要修改这些值,就会很省事。我曾参与过一个项目,已经推进到后期了,设计师却决定调整品牌颜色。当时只需要在一个地方改一下变量的值即可,但要是没用变量,那就得把用到这个值的多达 100 多处的代码老老实实改一遍。
本例中我还对激活状态下的链接样式预留了一个占位符,稍后再来确定颜色变量 --red
的值。在此之前,先初步完成页面布局:先把页面涉及的各主要区域放置到恰当的位置,然后在设置部分颜色和字体(如图 11.7 所示)。至于间距先缓缓,暂不做过多处理。
图 11.7 初步完成布局并添加了初始样式后的页面效果
我们将从页面顶部开始,分三个部分进行初步调整:页头区域、主图区域以及分三栏展示的主体部分。其间大部分工作都可以复用前面章节介绍过的技术,之后再回过头来认真琢磨页面的微调问题。
首先是页头及其导航条区域。这部分由三个模块组成:nav-container
、home-link
和 top-nav
,具体样式详见代码清单 11.3。将它们添加到本地样式表。
代码清单 11.3 页头样式代码
@layer modules {
.nav-container {
background-color: var(--medium-green);
}
.nav-container__inner {
display: flex;
justify-content: space-between;
/* 令内容居中,且最大宽度为 1080px */
max-inline-size: 1080px;
margin-inline: auto;
}
.home-link {
color: var(--text-color);
font-size: 1.6rem;
font-family: Georgia, serif;
font-weight: bold;
text-decoration: none;
}
.top-nav {
display: flex; /* 利用弹性布局将导航菜单项水平展布 */
list-style-type: none;
}
.top-nav a {
/* 给各导航链接指定颜色与内边距 */
display: block;
padding: 0.3em 1.25em;
color: var(--white);
background: var(--brand-green);
text-decoration: none;
border-radius: 3px;
}
.top-nav a:hover {
background-color: var(--dark-green);
}
.top-nav__featured > a {
color: var(--brand-green);
background-color: var(--white);
}
.top-nav__featured > a:hover {
color: var(--medium-green);
background-color: var(--white);
}
}
可以看到,整个页头区域都在 nav-container
容器内,并且使用了双容器模式来居中内部元素(相关内容详见第 3 章),这样就能在主体内容受最大宽度约束的同时,让背景色覆盖到页面边缘。而双容器中的内部容器本身就是一个 Flexbox 容器,并且设置了 justify-content: space-between
,可将其内部元素分置左右:左侧为 home-link
链接元素,右侧则为 top-nav
顶部导航条。接着再让 top-nav
也使用 Flexbox 布局,让里面的导航菜单项都排成一行;而导航链接的颜色则使用自定义属性进行配置。
页头搞定后,再来看主图区域。该区域的样式包含两个新模,一个用于主图,另一个则留给下方的按钮。具体样式详见代码清单 11.4。同样将它们添加到本地样式表。
代码清单 11.4 主图及其下方按钮的样式代码
@layer modules {
.hero {
background: url(collaboration.jpg) no-repeat;
background-size: cover;
margin-block-end: 40px;
}
.hero__inner {
/* 双容器模式 */
max-inline-size: 1080px;
margin-inline: auto;
padding-block: 50px 200px; /* 简单通过内边距来定位标语和按钮 */
text-align: end;
}
.hero h2 {
font-size: 1.95rem;
}
.button {
display: inline-block;
/* 标准按钮样式 */
padding: 0.4em 1em;
color: var(--brand-green);
border: 2px solid var(--brand-green);
border-radius: 0.2em;
text-decoration: none;
font-size: 1rem;
}
.button:hover {
background-color: var(--dark-green);
color: var(--white);
}
.button--cta {
/* CTA 按钮的变体样式 */
padding: 0.6em 1em;
background-color: var(--brand-green);
color: var(--white);
border: none;
}
}
与页头类似,主图区域也使用了双容器模式。其内部容器还设置了一些内边距,其大小目前只能粗略估计。本章会把所有页面元素放置到合适的位置;下一章我们再回过头来,看看如果精确匹配设计师的视觉稿会遇到哪些关键问题。
我们还定义了一个按钮模块,默认的外观是一个带绿色边框和文字的白底按钮。页面主体部分底部使用的按钮就是该默认样式。此外我们还为按钮定义了一个 CTA 变体,用的是纯绿色背景、白色文字(之前解释过,CTA 即 “Call To Action” 的缩写,是一个针对核心元素的营销术语,旨在引导用户交互行为。在本例中,CTA 按钮即醒目的 “Get Started” 开始按钮)。最后一步,是给页面下方的三栏主体部分添加样式,如图 11.8 所示。
图 11.8 页面主区域的初步样式
页面主体部分由一下几部分组成:限制了最大宽度的容器、控制三栏布局的 title-row
以及各分栏内的白色板块区域 tile
。将代码清单 11.5 中的代码添加到样式表,其中也包含了页脚的样式。因为之前添加过按钮样式,所以不需要再考虑这些内容了。
代码清单 11.5 三个内容分栏及对应板块的样式
.container {
margin-inline: auto;
max-inline-size: 1080px; /* 和页面其他部分一样,令最大宽度为 1080px */
}
.tile-row {
display: flex;
}
.tile-row > * { /* 令各内容栏宽度相等 */
flex: 1;
}
.tile {
background-color: var(--white);
border-radius: 0.3em;
}
.page-footer {
margin-block-start: 3em;
padding-block: 1em;
background-color: var(--light-gray);
color: var(--gray);
}
.page-footer__inner {
margin-inline: auto;
max-inline-size: 1080px; /* 和页面其他部分一样,令最大宽度为 1080px */
text-align: center;
font-size: 0.8rem;
}
可以看到,这里的样式再次用到了双容器模式,并将宽度限制在 1080px
以内。同时为 tile
板块区域设置了白色背景与圆角边框。
页脚部分也应用了对比,不同的是,这里并非为了吸引注意力,而是淡化注意力。该部分设置了浅灰色背景,并且使用了小号字和灰色文本。因为页脚是整个页面最不重要的内容,所以无需制造视觉上的冲突。使用让人容器忽视的配色就仿佛在告诉用户:“页面的这部分内容可能并非您要找的”。
11.2.1 色域与色彩空间
Gamuts and color spaces
在本节开头介绍的调色板中,颜色都是用十六进制的数值表示的。这是一种简洁明了的表示法,也是 Web 开发者从 Web 早期就开始使用的一种表示法;但该表示法也存在一些不足。
十六进制颜色值十分晦涩(cryptic),毕竟人们很难一目了然地知晓颜色值 #076448
究竟代表什么颜色。此外,十六进制表示法也只能在有限的 色域(gamut) 范围内描述颜色。
色域的定义
色域(gamut) (译注:读作
/'ɡæmət/
)是指某个颜色范围。它通常用于表征某设备上可以显示多少种颜色,或者该设备通过特定的颜色表示法(如十六进制表示法)可以描述多少种颜色。
颜色问题曾一度是 CSS 中相对简单的一个子领域,但随着显示器和智能手机的不断推陈出新,新一代硬件所支持的色域范围也在不断扩展。在对比 CSS 提供的各种颜色表示法时,如果能了解一些色域相关的基础知识,无疑将大有裨益。
就在十年前,大多数消费类电子产品的色域范围还仅仅停留在 标准 RGB(Standard RGB) 阶段,其缩写即为 sRGB
。该色域覆盖了人们在屏幕上可以看到的熟悉的色彩范围,但也仅仅是肉眼可以感知的所有色彩中的一部分。而现在市面上的高端设备都支持显示范围更广的色域标准,即 Display P 3 色域 1。P 3 色域所涵盖的色彩范围比 sRGB
色域扩大了约 25%,它也是目前绝大多数智能手机和高端笔记本电脑支持的色域标准。时至今日,少数显示器还支持显示范围更宽的色域标准,即 Rec2020
色域 2。该色域覆盖了约 75% 的可见色彩。
一种常见的用以展示每种色域标准对应的色彩范围的可视化方法,如图 11.9 所示。从图中不难看出,在 sRGB
色域范围之外还存在大量的可见色彩。
【图 11.9 不同色域标准所代表的色彩范围对比图】
CSS 之前的颜色表示法只能表达 sRGB
色域标准内的颜色。即便硬件显示器支持更多色彩,如果不使用更新的颜色语法,也无法启用更广泛的色域范围。这也是 CSS 在过去几年中采用新的颜色标准的一个主要原因。
说明
可以使用媒体查询来检测用户浏览器和硬件的色域支持情况。例如,
@media (color-gamut: p3)
规则下的样式声明仅在浏览器支持 P 3 色域时生效。其他的合法值还可以是srgb
和rec2020
。
色域描述的是可用颜色空间的范围;而 色彩空间(color space) 则说的是该颜色的特定排列组合(arrangement)。它通常被映射到三维空间中,旨在帮助人们更直观地理解色彩,并且能从概念上深化理解。对于同一个色域标准,如果映射方式不同,对应的色彩空间也各不相同。
译注:辨析色域与色彩空间
关于 色域 和 色彩空间,二者既相互关联又存在细微差别,作者这里并未详细展开。鉴于准确理解这两个概念对于色彩管理、图像处理和渲染技术都起着至关重要的作用,这里略作补充说明。
一、关于 色域(gamut):
定义:通常指的是 色域 的 实际颜色范围。它是指某个设备、颜色模型或色彩系统所能够显示、打印或表示的所有颜色的集合。
本质:更多强调的是颜色的 范围 本身,即某个设备或色彩空间可以显示的颜色的 范围和边界。通常用 二维 或 三维 的色彩模型图形来表示。
示例:
- 显示器的色域:表示屏幕可以显示的颜色集合。比如,sRGB 色域表示屏幕可以显示的颜色范围,Adobe RGB 色域则表示更广泛的颜色范围。
- 打印机的色域:打印机能打印的颜色范围,通常比显示器的色域要小。
二、关于 色彩空间(color space):
定义:色彩空间 说的是一个描述颜色的数学模型或坐标系统。它定义了颜色通过数字或物理属性来进行描述的具体方法。一个色彩空间通常会包括一组颜色的表示方法、坐标轴、模型和映射规则,可能涉及色度(色调、饱和度)、亮度、RGB 值等方面。
本质:强调的是 颜色的表示方法和标准,即颜色的 定义。它包括了用于描述颜色的数学模型和标准,并规定了某个颜色与颜色模型中的某个点的映射关系。
示例:
- RGB 色彩空间:表示由红、绿、蓝三原色组合而成的颜色模型,通常用于显示器和数字设备。
- CMYK 色彩空间:常用于打印设备,代表青色(Cyan)、品红(Magenta)、黄色(Yellow)和黑色(Key)的组合。
- XYZ 色彩空间:由 CIE(国际照明委员会)定义的标准色彩空间,用于科学精确的色彩研究。
二者的主要区别:
特征 | 色域(gamut) | 色彩空间(color space) |
---|---|---|
定义 | 指设备或色彩模型可以呈现的颜色的实际范围。 | 定义了颜色的数学模型、坐标系和表示方法。 |
焦点 | 强调 颜色的范围,即能显示或呈现的颜色数量。 | 强调 颜色的表示和定义,包括数值如何表示颜色。 |
表现形式 | 通常通过二维或三维的图形来展示,表示颜色的边界。 | 通过数学模型定义颜色的数值,例如 RGB、CMYK 值等。 |
应用范围 | 通常用于讨论显示设备、打印机、摄像机等的颜色表现范围。 | 用于描述具体的颜色模型和标准,适用于设备、图像、视频等领域。 |
大多数色彩空间都采用矩形或圆柱形排列。图 11.10 展示的是基于矩形的 RGB 色彩空间,其中红色、绿色、蓝色的颜色值分别被映射为直角坐标系下的 x
轴坐标、y
轴坐标以及 z
轴坐标。图 11.11 则展示的是基于圆柱形的 HSL 色彩空间。其中色调(hue)是通过围绕极坐标系下的中心轴、以圆形方式排列映射的。(以下图片均由 SharkD 提供,详见 https://en.wikipedia.org/wiki/HSL_and_HSV,图片已使用 CC BY-SA 3.0
许可协议)。
图 11.10 RGB 矩形色彩空间示意图
图 11.11 HSL 极坐标色彩空间
通常情况下,圆柱形色彩空间往往更加直观、也更容易上手。在 RGB 色彩空间中,要描述清楚增加蓝色并减少绿色对橙色产生的影响往往非常抽象;而增加颜色亮度或者转到不同的色调则让目标颜色效果更容易预测。接下来,就让我们深入考察一下 CSS 提供的各种色彩空间及其相关的表示方法。
-
Display P 3 色域是由 国际电视联合会(ITU) 和 国际标准化组织(ISO) 等机构制定的色域标准。它是基于 DCI-P 3 色域的一个扩展,并且专为显示器和屏显技术(尤其是苹果公司的相关设备)进行了优化。苹果公司在 2015 年 发布的 iMac 5 K 中首次引入并推广了 Display P 3 色域,标志着该色域标准的普及。
-
Rec. 2020(BT. 2020)色域 是由 国际电信联盟(ITU) 的 无线电通信部门(ITU-R) 早在 2012 年 制定并推行的色域标准。该标准是专为 4 K 和 8 K 超高清电视(UHDTV) 设计的,旨在为超高清电视和高动态范围(HDR)视频提供一种更广的色域,能够更精确地呈现色彩。该标准中的 2020 并非制定年份,而是代表该标准的编号,即 ITU-R 推荐标准第 2020 号。
11.2.2 CSS 颜色表示法
CSS color notations
CSS 目前支持多种颜色表示方法,包括 rgb()
表示法和 hsl()
函数表示法等。这些函数在各种浏览器中都得到了很好的支持。
另外还有一些新的表示法尚未得到很好的支持,例如 hwb()
、lab()
、lch()
、oklab()
、oklch()
等等。这些颜色表示方法也是直到 2022 年末至 2023 年 6 月前后才陆续添加到浏览器中。截止到 2024 年 6 月前后,启用这些颜色表示法可能还需要定义回退值才妥当;不过随着浏览器的逐步升级,这些新的表示法也将逐渐推广普及。想了解最新的浏览器兼容情况,可以查看 Can I Use 网站发布的最新数据:https://mng.bz/EZKo。
上面提到的每一种表示方法都对应一个特定的色彩空间,它们整体为 CSS 的颜色管理提供了海量的选择。接下来我将简要介绍每种表示法的工作原理以及它们之间的相互关系。然后会从中选取几种表示法进行对比演示。
我最偏好的表示法是 hsl()
—— 这也是浏览器目前兼容性最好的颜色表示方法了。此外还有 oklch()
表示法 —— 一种最为直观(intuitive)的颜色表示法。相信在不久的将来,一旦得到浏览器的普遍支持,oklch()
必将成为我新的首选表示法。
11.2.2.1 RGB 与十六进制颜色表示法
RGB and hex color
十六进制颜色表示法分别规定了红、绿、蓝三原色的取值,且每种颜色分量都用两位数的十六进制数值来表示,取值范围介于 00
到 FF
之间。十六进制是一种以 16 为基数的数字系统,十到十五的数值分别用字母 A
到 F
表示。例如颜色 #80c090
表示红色 80
、绿色 c0
、蓝色 90
(相当于十进制下的红色 128
、绿色 192
和蓝色 144
)。
新版的八位十六进制表示法还包括了另外两位数,用于表示 α 通道(alpha channel) 值,即透明度的大小。该值的取值范围同样介于 00
(即完全透明)到 FF
(即完全不透明)。因此,#80c09088
其实是颜色值 #80c090
的半透明版本。
注意
十六进制表示法有时也可以简写为三位数,其中每位数字都各重复一次(doubled)。例如,
#c90
等效于#cc9900
。此外还有一种对应的四位数表示,可以通过类似处理扩展为带α
值的八位十六进制颜色值。
而 rgb()
函数则是一种使用十进制数而非十六进制数来描述颜色的方法。其各颜色分量的取值范围不再是十六进制下的 00
到 FF
,而是对应的 0
到 255
。例如 rgb(0 0 0)
表示纯黑色(等效于 #000
);rgb(136 0 0)
则表示砖红色(等效于 #800
)。
RGB
与十六进制表示法仅适用于 sRGB
色域标准。无论是 RGB 表示法还是十六进制表示法,理解起来都有点费劲。我们见到一个颜色值,例如 #2097c9
,或者它对应的 RGB 颜色,无法联想到它在页面会渲染成什么样。如果尝试分解一下,它的红色值(20
)将非常少,绿色值(97
)为中等水平,蓝色值(c9
)则相对偏高。经分析得出,该颜色的蓝色和绿色占主导,但是颜色有多深呢?又有多鲜艳呢?都说不准。事实上,RGB 颜色表示法很不直观,它们本来就是方便计算机读取的,并不适合用肉眼识别。
十六进制颜色表示法只有当需要某种书写简洁、且可以方便地在不同应用程序之间复制粘贴颜色值时才有一席用武之地;而 RGB 表示法同样问题多多,和十六进制法相比妥妥的令人费解有余而方便简洁不足。然而其他大部分颜色表示法都比它们更加好懂且易用。
11.2.2.2 HSL 颜色表示法
HSL color
HSL 则是一种更适合人类读取的颜色表示法,其名称分别代表色调(Hue)、饱和度(Saturation)和亮度(Lightness)。HSL 的语法形如 hsl(198deg 73% 46%)
,相当于十六进制下的 #2097c9
。
在 HSL 中,代表色调的第一个参数其实是一个角度值,取值范围介于 0 度到 359 度之间。它代表色相环 1 上的度数,可以从红色(red,0 度)依次过渡到黄色(yellow,60 度)、绿色(green,120 度)、青色(cyan,180 度)、蓝色(blue,240 度)、洋红色(magenta,300 度),然后再回到红色。此外,角度取值范围以外的值仍然是有效的,并可通过加减 360 度来找到等效的色相环上的色调值。书写时也可以省略角度单位 deg
,直接写成一个数字。
HSL 表示法的第二个参数代表饱和度,是一个代表颜色强度的百分数:值为 100%
时颜色最鲜艳;为 0%
则意味着没有彩色,仅呈现一片灰色。
HSL 表示法的第三个参数表示亮度。它也是百分数,用以描述颜色的明暗程度。大部分鲜艳的颜色采用的是 50%
的亮度值,且亮度值越高,颜色越浅,浅至 100%
时就变成了纯白色;而当亮度值越低,颜色越深,深至 0%
就变成了黑色。
再比如,hsl(198deg 73% 46%)
这个颜色值,其色调为青蓝色,饱和度相当高(73%
),而亮度接近 50%
,因此会产生一个比天蓝色更深一些的蓝色。HSL 颜色表示法也只适用于 sRGB
色域标准。
提示:一种更新的、不使用逗号的颜色函数表示法
在 CSS 首次引入 RGB 表示法和 HSL 表示法时,
rgb()
和hsl()
颜色函数在使用时必须用逗号分隔每个参数,不能写作rgb(136 0 0)
,而必须写作rgb(136, 0, 0)
。为了向下兼容,这些颜色函数继续保留用逗号分隔的写法;而对于其他新推出的颜色函数就只支持不带逗号的写法。因此推荐使用无逗号写法,这样所有浏览器都支持该语法。
rgb()
和hsl()
也有相应的rgba()
版本和hsla()
版本,这些扩展函数可以接受第四个参数来设置代表颜色透明度的 alpha 通道值。现在已经不用再写这些扩展函数了,因为新版语法也支持写在斜线后的alpha
参数设置。例如,rgb(136 0 0 / 0.7)
定义了一个 不透明度 为70%
的砖红色(brick red)。后续介绍的所有颜色函数都支持通过这种方式设置的不透明度。
11.2.2.3 HWB 颜色表示法
HWB color
HWB 表示色调(Hue)、白度(Whiteness)和黑度(Blackness)。例如,某颜色利用该表示法可以写作 hwb(198deg 12.5% 20.4%)
。它其实等效于之前介绍过的用 HSL 表示法描述的那个深蓝色。
HWB 颜色表示法中的色调值等同于 HSL 表示法中的色调值;同样,它既可以写成一个角度值,也可以直接写成一个数字。
HWB 表示法中的 W 值和 B 值均为百分数,分别用于描述该颜色混入了多少白色或黑色。如果两个值都偏低,那么最终颜色就会更鲜艳。hwb(0deg 100% 0%)
即为纯白色(此时的颜色与色调无关);而 hwb(0deg 0% 100%)
则为纯黑色;如果 W 和 B 之和大于或等于 100%
,则最终颜色将为纯灰色,其深浅程度取决于二者中取值更大的一方。HWB 表示法同样仅适用于 sRGB
色域标准。
11.2.2.4 新一代色彩空间
Next-generation color spaces
接下来的四种颜色表示法适用于 广色域色彩空间(wide gamut color spaces)。这些表示方法不局限于 sRGB
色域或其他任何色域,而是能够代表人眼所能看到的任何颜色。换句话说,它们甚至可以描述超出硬件能力边界的颜色。在这种情况下,浏览器会自动将这些颜色做圆整处理,并归入最接近的可用色域范围内——只要不超出预期的取值范围,通常就能得到可接受的备选颜色。比如,可以利用这个原理来指定 P 3 色域下的颜色,对于不支持 P 3 色域的显示器而言,用户最终看到的将是最接近该颜色的 sRGB
色域下的近似值。
这些颜色表示法的另一个好处在于,它们力求在色彩感知上做到线性展示。也就是说,其颜色值是根据我们人类的感知来定制的。例如,在 HSL 体系中,亮度为 50%
的绿色会比同等亮度下的蓝色看起来更亮一些,因为我们的眼睛会认为绿色更亮;而在新一代色彩空间中,色彩是均衡的(balanced),因此相同亮度的所有色调对我们的眼睛而言都是一样的。同理,在控制色彩鲜艳度的数值上也是如此。
LAB 和 LCH 是其中最先创建的两种颜色表示法,但它们的规范尤其是在一些蓝色和紫色的色调方面还存在一些与规范不一致的地方,因此其表现也差强人意。后来 OKLAB 和 OKLCH 表示法取代了它们。新方法在概念上类似,但行为上可预测性更强。虽然接下来我将分别介绍这四种颜色表示法的工作原理,但还是强烈推荐使用 OKLAB 和 OKLCH 表示法。浏览器对以上四种表示法的支持情况也几乎完全相同。
11.2.2.5 LAB 和 OKLAB 颜色表示法
LAB and OKLAB color
LAB 颜色及其后续版本 OKLAB 都定义了三个维度值:亮度、A 轴(A axis)与 B 轴(B axis)。其语法格式形如 lab(58% -19 -35)
或者 oklab(65% -0.06 -0.1)
。虽然二者的数值不同,但在概念上都是相同的。
第一个参数,亮度,是一个值域介于 0%
(即黑色)到 100%
(白色)的百分数。在 LAB 表示法中,也可以使用 0 到 100 间的数字来表示;而在 OKLAB 中,还可以写成 0 到 1.0 之间的数字。鉴于这种写法上的差异可能引起混淆,推荐还是使用百分数为上。
而 A 轴的取值范围介于红色到绿色;B 轴则介于蓝色到黄色。在 OKLAB 中,这些值的具体范围分别是 -0.4
到 0.4
(也可以使用 -100%
到 100%
之间的百分数来表示)。而在 LAB 中,上述值的值域则为 -125
到 125
。
LAB 和 OKLAB 都是矩形色彩空间,类似于 RGB 的广色域版本;但与 RGB 类似,它们使用起来都不是很直观。
11.2.2.6 LCH 和 OKLCH 颜色表示法
LCH and OKLCH color
LCH 和 OKLCH 则是与 HSL 相似的圆柱形色彩空间(cylindrical color spaces)。它们定义的是这三个维度值:亮度(Lightness)、色度(Chroma)和色调(Hue),语法上则可以写作 lch(58% 39.8 241.5deg)
以及 oklch(64% 0.12 233deg)
。
提示
OKLCH 这个名字有点拗口。Web 开发者 埃里克·波蒂斯(Eric Portis) 首创了 “Oklachroma” 这个词来作为 OKLCH 的一种更为友好的发音方式,别说还挺好记的。
第一个参数定义了感知到的亮度。它也是一个介于 0%
(即黑色)到 100%
(即白色)的百分数,也可以写成 0
到 1.0
的十进制形式,与 HSL 中的亮度定义非常类似,但 LCH 和 OKLCH 在不同的色调表现中更加一致,更符合我们人眼的感知。
第二个参数色度值(chroma value)表示色彩的鲜艳程度。色度值为 0
则表示纯灰色,这与 HSL 中的饱和度也十分类似。从理论上讲,该参数值是没有范围限制的;但在实际应用中,OKLCH 色度的取值往往不超过 0.5
(在 sRGB
色域中也不会超过 0.33
)。而在 LCH 中,色度的取值范围则是 0
到 230
。
最后一个参数是色调(hue)。它也是色相环上的某个角度值,类似 HSL 中的色调概念,只是具体数值重新做了调整。其取值范围为:从红色(red,30)依次过渡到黄色(yellow,90)、绿色(green,140)、青色(cyan,195)、蓝色(blue,260)、洋红色(magenta,330),最后又回到红色。
利用 color () 函数在更宽的色域中使用 RGB 颜色
CSS 提供了一个
color()
函数,可以用来手动设置色域及颜色值。例如,color(display-p3 0.3 0.59 0.77)
可以对P3
色域下的红色、绿色、蓝色分别进行定义。该颜色值等效于oklch(64% 0.12 233deg)
。个人认为在通常情况下,该颜色表示法不如前面介绍的几种实用,因为它采用的是一种基于 RGB 的范式(RGB-based paradigm),写起来也比较冗长。由于 OKLAB 和 OKLCH 等广色域表示法可以描述肉眼可见光谱中的任何颜色,因此,如若将来显示器升级到支持更广的色域标准,它们仍然可以胜任各种色彩的展示。
不过,
color()
函数除了支持sRGB
、Display p3
和Rec2020
色域外,还支持更多冷僻色域,因此如果需要在特定色域内定义颜色,color()
函数应该会非常有用。想了解更多color()
函数的相关信息,可以参考 MDN 的在线文档:https://mng.bz/lMQ6。
总结一下,以上介绍的每种颜色表示法都是由以下参数分别定义的:
- RGB —— 红、绿、蓝(值域均为
0
到255
) - HSL —— 色调(角度值)、饱和度(百分数)、亮度(百分数)
- HWB —— 色调(角度值)、白度(百分数)、黑度(百分数)
- LAB —— 亮度(百分数),A 轴(
-125
红色至125
绿色),B 轴(-125
蓝色至125
黄色) - OKLAB —— 亮度(百分数),A 轴(
-0.4
红色至0.4
绿色),B 轴(-0.4
蓝色至0.4
黄色) - LCH —— 亮度(百分数)、色度(值域
0
到230
)、色调(角度值) - OKLCH —— 亮度(百分数)、色度(值域
0
到0.5
)、色调(角度值)
上述表示方法中,大部分还支持 none
关键字来代替其中的某个参数,用于表示该参数无关紧要。例如,oklch(100% 0 0deg)
表示白色,因此色调的取值基本上没有意义。这时就可以写作 oklch(100% 0 none)
甚至是 oklch(100% none none)
。
图 11.12 对比展示了几种典型颜色在指定的几种色彩空间中的不同表示方法。对于其中个别数值做了些微调。
图 11.12 等效颜色值对比图
以上这些特定的颜色也可以通过引用它们的合法名称来进行声明(如 blue
、lavender
、lightgray
等)。CSS 中大约有 150 种类似的合法命名颜色(named colors)。有关命名颜色的完整列表,详见 MDN 官方文档 <named-color>
:https://mng.bz/NRoN。
想要熟悉 OKLCH 或者其他任何色彩空间,最好的方法莫过于付诸实践。下一节我将深入介绍 OKLCH 在页面颜色配置中的具体用法,并推荐几个在使用过程中挺有帮助的实用工具。
- 色相环(Hue Circle)是一个用于表示颜色关系的圆形图表,通常用于视觉艺术、设计和色彩理论中。色相环展示了颜色之间的相对位置和过渡,帮助理解色彩的组合和对比。
11.3 利用 OKLCH 处理颜色
Using OKLCH to work with color
接下来,我将教您如何将十六进制颜色转换为其他的写法。很多在线资源都提供了颜色提取工具,并支持不同颜色表示法之间的相互转换。但进行颜色转换最简单的去处莫过于本地浏览器的开发者工具(DevTools)了,毕竟该工具唾手可得。下面演示如何在 Chrome 浏览器中进行颜色转换。
您可以按照这套流程把颜色值转换成您想要的写法。如果担心浏览器不兼容,建议选用 HSL 表示法。而我将用 OKLCH 来进行演示,因为浏览器对它的支持正在快速增长,并且也对它未来的推广普及充满信心。无论采用哪种方式,基本流程都是一样的。
注意
截止到 2024 年年中,
Firefox
浏览器的开发者工具 DevTools 尚不支持将 CSS 颜色转为某些新推出的颜色表示方法。本节截图显示的是Chrome
浏览器中的 DevTools 工具。
页面加载后,打开浏览器的 DevTools 开发者工具(按 F12
键)。在 Elements
元素面板中单击并选中 <html>
标签,就会在 Styles
样式面板中看到对应的样式,其中包括我们设置的自定义属性(如图 11.13 所示)。
【图 11.13 页面生效的颜色在 DevTools 的样式面板下的截图。按住 Shift 键单击颜色旁边的小方块可以切换不同的颜色表示方法】
每种颜色旁边都有一个正方形的小指示器(indicator)来显示该颜色的效果。如果按住 Shift
键,然后单击这个小方块,就可以将这个十六进制颜色值转换成另一种写法。在 Firefox
和 Safari
浏览器中,每次单击都会在不同的颜色表示法之间循环;而在 Chrome
浏览器下,这样会弹出一个下拉菜单,以供用户选择。
如果还想深入研究,单击小方块就可以打开一个完整的颜色提取器对话框(如图 11.14 所示)。利用该对话框,您可以对当前颜色进行微调,或者从调色板中选取想要的颜色,或者是在不同的颜色值语法间切换。同时它还提供了一个滴管形状的颜色提取器,以便直接从页面选取目标颜色。尽管 Firefox
和 Safari
浏览器也提供了类似的颜色提取工具,但是功能没有 Chrome
这么丰富。
【图 11.14 利用颜色提取器对话框来对颜色进行微调】
当颜色提取工具处于某个广色域色彩空间时,调色板中会出现一条细长的白线。它表示 sRGB
色域标准的边界。分界线左侧的颜色属于 sRGB
色域,可在支持 sRGB
标准的显示器上精确显示;而分界线右侧更为鲜艳的色彩则超出了 sRGB
色域范围,但属于 Display P3
色域的一部分;如果使用这些区域的颜色,sRGB
显示器将按最接近的颜色渲染,但并不那么精确。
11.3.1 把样式表切换到 OKLCH 色彩空间
Switching the stylesheet to OKLCH
通常情况下颜色值按十六进制格式书写即可。但转换成 HSL 值或 OKLCH 值可以更方便地进行颜色微调,也更容易找到可以添加到网站的新颜色。下面尝试将网站颜色都转换成 OKLCH 格式,然后对页面颜色做一些深度观察。
注意
尽管颜色表示法中的很多参数值都可以写成不带单位的普通数字,但我个人更倾向于在适当的场合写上单位。比如写作
oklch(45% 0.09 165.4deg)
而不是oklch(0.45 0.09 165.4)
,因为我认为这样写能够一目了然地识别出哪个值对应哪个参数,特别是deg
单位,可以立马知道它代表色调。
将 DevTools 工具中的 OKLCH 颜色值分别复制到样式表中,替换掉之前的十六进制格式。为确保简洁,我还对部分几乎看不出区别的颜色值做了圆整处理,并加上了对应的单位,使其更加清晰明了。
代码清单 11.6 将十六进制颜色转换成 OKLCH 值
@layer theme {
:root {
/* 绿色都使用相同的色调 */
--brand-green: oklch(45% 0.09 165.4deg);
--dark-green: oklch(59% 0.12 164.1deg);
--medium-green: oklch(74% 0.15 166.4deg);
/* 部分灰色值都不是纯灰色 */
--text-color: oklch(26% 0.01 0deg);
--gray: oklch(64% 0.02 248deg);
--light-gray: oklch(96% 0.003 248deg);
--extra-light-gray: oklch(98% 0 0deg);
--white: oklch(100% 0 0deg);
}
}
把颜色都改成 OKLCH 写法后,有些事情就变得显而易见了。首先,您会发现三种绿色都拥有相同的色调值。如果不在浏览器中查看,估计很难一下子识别出 165
表示一种青绿色(teal green),但却很容易发现这三种颜色存在某些相似之处(symmetry)。这些特征在十六进制颜色值下是很难察觉的,但是转成 OKLCH 后就比较明显了。了解这些之后,在调色板中再加一种绿色就容易多了。如果需要同种颜色的浅色调版本,就可以尝试写作 oklch(85% 0.12 165deg)
,然后在浏览器的 DevTools 开发者工具中对饱和度或色度(chroma)再做微调,直到最终效果满意为止。
提示
如果要配置好几种相似的颜色,也可以将单个值(如色调的角度值)赋给一个自定义属性,然后在定义颜色时调用该属性。例如
oklch(45% 0.09 var(--brand-hue))
。
您可能也注意到了,上述颜色代码中的灰色并不是纯灰色:有几个颜色略带了些色度。单看这些颜色本身,可能还看不出这些门道,但就是这样的小细节能让我们的页面看上去更加丰富多彩。真实世界中鲜有完全不掺杂彩色的纯灰色(colorless grays),而且我们的眼睛也乐于看到一些彩色,哪怕是极其细微的彩色。
再次强调,在实际项目开发中,我们没有必要把所有颜色值都转换成 OKLCH 格式;什么时候有这样的需求了再考虑转换也不迟。通常情况下,这样的转换可以更方便地找到相关的颜色。
本书后续章节的示例都会继续采用 OKLCH 格式的颜色值来进行演示。对于实际项目,请务必在 Can I Use 官网(详见 https://caniuse.com/mdn-css_types_color_oklch)查看最新的浏览器兼容情况,并为该写法提供在低版本浏览器中的备用值,直到您对当前的兼容情况感到满意为止。
11.3.2 颜色变量的命名
Naming color variables
设计师一般会在调色板中设计多个不同的灰色。不过按照我的经验,即使准备得再充分,也免不了需要补充其他灰色值。无论是比超浅灰(extra-light gray)更浅的版本,抑或是介于灰色和浅灰色之间的某种颜色,类似的需求总是存在的。有时甚至是品牌主色也面临这样的情况。打个比方,您可能到头来会发现,除了页面上已有的三种绿色之外,还需要引入另一种绿色。这样一来,颜色变量的命名管理就成了个问题。
基于这样的原因,可以考虑使用带数值的名称来命名颜色值,例如 --gray-50
或者 --gray-80
,这里的数字大致对应亮度值。这样就能在必要的时候,在现有的颜色值之间再插入另一个新的颜色值。现如今,网站通常都会有一个长长的自定义属性列表来定义不同的颜色,例如从 --blue-10
定义到 --blue-90
,或者从 --gray-10
指定到 --gray-90
。
遇到大型网站或者更复杂的应用场景,通常会将这些 “原始”(“raw”)颜色变量依次赋给某个二级变量,例如一组带编号的前景色与背景色,如代码清单 11.7 所示(无需将其添加到本章构建的示例页中)。
代码清单 11.7 从基于用途的自定义变量中抽象出原始的颜色值
:root {
--gray-10: oklch(10% 0.01 165deg);
--gray-20: oklch(23% 0.01 165deg);
--gray-30: oklch(35% 0.01 165deg);
--gray-40: oklch(42% 0.01 165deg);
--gray-50: oklch(53% 0.01 165deg);
--gray-60: oklch(65% 0.01 165deg);
--gray-70: oklch(78% 0.01 165deg);
--gray-80: oklch(90% 0 165deg);
--gray-90: oklch(98% 0 165deg);
--background-4: var(--gray-90);
--background-3: var(--gray-80);
--background-2: var(--gray-70);
--background-1: var(--gray-60);
--background-neutral: var(--gray-50);
--foreground-1: var(--gray-40);
--foreground-2: var(--gray-30);
--foreground-3: var(--gray-20);
--foreground-4: var(--gray-10);
}
您还可以添加一些变量,专门给边框、阴影效果以及其他可复用的设计元素设计颜色。然后,在整个样式表中使用这些二级变量(secondary variables),而不是直接使用原始的颜色值变量。利用这样的间接设置,您就可以实现特定颜色值的修改与其具体用途间的分离。比如要更换通用的边框颜色,就可以在二级变量的帮助下顺利完成修改,不会对同样作为下拉菜单背景的那个灰色带来不良影响。
该方案也特别适用于浅色模式和深色模式的设置,可以在保持原始颜色值不变的情况下,实现背景色与前景色的样式反转(或许可以在容器样式查询中进行尝试,具体方法详见第 10 章)。
管理颜色变量的具体方法数不胜数,而且并没有公认的最佳方案。我也一直很喜欢浏览一些我所尊敬的开发者和设计师创建的网站,并学习上面的 CSS。每个人都有自己独到的解决方案。建议您多去尝试一些适合自己的方案,并根据自己的风格进行调整。
11.3.3 为调色板选取新的颜色
Selecting new colors for the palette
试想有这样一个场景,您需要某种颜色,而设计师刚好没有提供,比如红色的报错信息或者蓝色的信息框。有经验的设计师一般会对这种情况提供通用的解决方案,但您可能仍需要考虑将新颜色添加到调色板中。
我们的样式表已经为激活状态下的链接文本颜色预留了位置。激活状态下的链接通常为红色,这也是用户代理样式表提供的默认样式;但这种红色过于鲜艳,看起来也略显卡通(cartoony),与当前的页面风格不一致;需要换成一种不那么鲜艳的、能与页面中的绿色相搭配的颜色。
11.3.4 从页面其他颜色衍生出新颜色
Deriving colors from others on the page
为某种颜色寻找一个与之相配的颜色,最简单的方法是找到它的 互补色(complement)。某颜色的互补色位于色相环的对侧位置,例如蓝色对黄色、绿色对洋红(magenta,或者紫色 purple)、红色对青色(cyan)等等。
使用 OKLCH 或 HSL 颜色值时,互补色的计算非常简单,令色调值增加或减少 180
即可,但也要做好微调的准备。这两套颜色表示法中的色调在色相环 360 度的分布略有不同,而其他调色工具(如 Adobe
的在线调色板生成工具,详见 https://color.adobe.com/create/color-wheel)往往还会提供另外版本的互补色方案。
再以本章示例中作品牌主色的绿色值为例,按照 OKLCH 的写法,其色调值原本为 165
。加上 180
,色调值变为 345
,将得到一个颜色接近洋红(magenta)的红色;若想通过减去 180
来获取互补色,色调值则为 -15
,也就相当于 345
。故而 oklch(45% 0.09 -15deg)
和 oklch(45% 0.09 345deg)
会渲染成同一种颜色。不过建议把色调值调整到 0 ~ 360 度范围内,因为人们更熟悉这个范围内的颜色与色调的对应关系。
确定了色调,再来看看互补色的色度和亮度。页面上的通用链接颜色为中绿色(即 oklch(74% 0.15 166.4deg)
),我们就从这里入手微调互补色。在我看来,色调值取 345
过于接近洋红色,而我并不希望互补色过于抢眼,因此将其改为 10deg
。经测试,感觉又有点浅了,于是亮度又下调了 10%
,最终得到互补色 oklch(64% 0.15 10deg)
。
以上结果完全是利用浏览器的开发者工具 DevTools、并结合代码清单 11.8 中的代码经过反复调试验证得出的。当然,倘若在颜色选取上能和设计师商量着来,就再好不过了。
将该颜色添加到本地样式表中,并按照代码清单 11.18 中给出的示例,同步修改本地样式表。代码将新的互补色赋给了自定义属性 --red
,这便是前面预留的、处于激活状态下的链接色。
代码清单 11.8 添加新的红色到调色板中
@layer theme {
:root {
--brand-green: oklch(45% 0.09 165.4deg);
--dark-green: oklch(59% 0.12 164.1deg);
--medium-green: oklch(74% 0.15 166.4deg);
--text-color: oklch(26% 0.01 0deg);
--gray: oklch(64% 0.02 248deg);
--light-gray: oklch(96% 0.003 248deg);
--extra-light-gray: oklch(98% 0 0deg);
--white: oklch(100% 0 0deg);
--red: oklch(64% 0.15 10deg); /* 自定义一个代表红色的变量*/
}
}
AI写代码css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
完成以上设置后,重新加载页面查看最终效果。遗憾的是,链接的激活状态默认情况下是不显示的,要看到实际效果还得费些功夫,需要单击并按住链接才能生效,一旦松开就又变回了绿色;为此,可以通过开发者工具强制激活该链接元素。
鼠标右键单击该链接,在弹出菜单中选择 检查(Inspect) 或 检查元素(Inspect Element) 打开开发者工具 DevTools。在 Element 元素面板中右键单击 <a>
标签,并从弹出菜单中选择 Force State 下的 :active 选项(若为 Firefox
浏览器,则选中 Change Pseudo-class 下的 active 选项),如图 11.15 所示。这样就可以强制浏览器显示该元素在激活状态下的样式。
【图 11.15 DevTools 工具可以强制设置元素激活、悬停、聚焦及其他伪类状态,以方便开发者预览其渲染效果】
元素处于激活状态时,就能查看新定义的红色是什么效果了。必要时甚至可以在开发者工具中实时修改样式,并查看其对目标元素的实际影响。
选择什么样的颜色好看并没有什么标准方案,但利用极坐标系统下的色彩空间可以让这个过程简单很多。不妨尝试从页面已有颜色的互补色入手,然后在开发者工具中不断调整亮度(lightness)与色度(chroma),往往就能找到心仪的渲染效果。如果想要更深入地研究颜色的选取,也可以上网浏览颜色理论相关的文章,例如 Natalya Shelburne 撰写的这篇著名文章《Practical Color Theory for People Who Code》,详见:http://tallys.github.io/color-theory/。
11.3.4.1 使用拾色器
Using a color picker
在某些场合下,例如没有设计师参与、需要完全自主设计页面的情况,您可能需要一个比 DevTools 的内置版本功能更加强大的拾色器(color picker)。网上像这样的第三方工具还不少。如果您用的也是 OKLCH 写法,强烈推荐 OKLCH Color Picker & Converter 这款线上工具(详见:https://oklch.com)。它不仅可以提取颜色,还能生动形象地帮助您进一步熟悉 OKLCH 色彩空间的相关操作。
该拾色器工具的页面截图如图 11.16 所示。左上方为选定的色块,右边则对应亮度、色度、色调以及 Alpha 通道值的滑块设置。每个维度梯度都显示了三维色彩空间的某个二维切面,以便从三个主视角进行查看。您也可以单击这些区域或者移动滑块来调整当前颜色。此外还能几乎任何颜色值写法粘贴到左边的文本框中,然后转为对应的 OKLCH 颜色值。
该拾色器工具尤其适用于检验当前颜色是否符合 sRGB
或者 Display p3
色域标准。如果此前用过颜色提取工具,您可能会看到一个熟悉的矩形颜色梯度界面;而 OKLCH 拥有更宽广的色彩空间,其对应的色彩展示能力远非目前已知的任何硬件可以企及。因此,如果使用矩形梯度界面来描述这些可用色彩,矩形中将包含大量无法正常渲染的色彩区域。这些区域会在拾色器中留白,最终形成不规则的颜色渐变图案。如果您使用的是支持 Display p3
或 Rec2020
色域的显示器,则会在范围偏小的色域边界处出现一条白色的分界线,如图 11.17 所示。
【图 11.16 由 oklch. com 网站提供的线上拾色器工具截图】
如果选取的目标颜色刚好位于色域范围之外,页面左上方的色块则会提示像 “P3 is unavailable on this monitor
” 字样的信息(“P3
色域在该显示器上不可用”),而在它右侧则会显示一个最接近的备选色,也就是浏览器在渲染色域以外的颜色时会实际显示的颜色。
译注
感觉这里有必要补一张截图,展示颜色不在色域内的工具提示情况:
【补图 1:目标颜色 oklch (72.65% 0.1974 38) 不在色域范围内时的页面提示情况截图】
此外,开启左下角的 3 D 模型展示,还可以直观感受当前色彩空间的 3 D 模型构造,同时支持鼠标拖拽操作,效果可谓相当炸裂:
【补图 1-2:启用 3 D 色彩空间建模视图后的工具页截图(支持鼠标拖拽操作)】
注意
当定义的色域颜色超出色域很远时,例如色度值为 5 的某个颜色,浏览器可能会将其近似处理成色调或者亮度发生某种偏移(shifted)的颜色。W 3 C 协会已经定义了一种设计精妙的算法来规避这一情况,但该算法目前尚未在任何浏览器中实现。因此请务必将 OKLCH 的色度值尽量控制下
0.5
以下,以避免出现这种情况。
这款工具对于了解显示器的色域限制以及将其他颜色写法转为 OKLCH 写法非常有用。您可以复制左边生成的 oklch()
函数,并将其粘贴到您的样式表中。
【图 11.17 拾色器工具可以帮助人们在所需色域内选取颜色】
11.3.4.2 使用 color-mix () 进行颜色混合
Mixing with color-mix ()
CSS 提供了两个全新的功能来增强与颜色相关的操作。截至 2024 年年中,这两个特性功能尚未得到足够的浏览器支持,因此只能在配合备选样式的情况下使用;但它们今后很可能成为必不可少的辅助工具、。
第一个新特性为 color-mix()
函数。利用该函数,两种颜色可以有效地混合在一起,生成第三种中间色。该函数需要三个参数:插值方法(interpolation method) 和两种待混合的颜色。例如,color-mix(in oklch, blue, red)
将混合蓝色与红色,最终生成一个紫色。
第一个参数 插值方法 决定了混合颜色是使用的色彩空间,可能会对生成的颜色产生重大影响;这个 “介于” 给定两个颜色之间的颜色效果,很大程度上取决于所有颜色在空间上的排列方式。
当前可用的插值方法有:srgb
、srgb-linear
、lab
、oklab
、xyz
、hwb
、hsl
、lch
以及 oklch
。在处理颜色渐变效果时,颜色插值也是个重要议题,相关内容将在第 13 章进行更为深入的探讨。
简单来讲,我发现当需要火鹤两种不同的色调来找到某个中间色调时,使用 hsl
或者 oklch
等极坐标下的色彩空间是最佳选择;而 srgb
似乎更适合与白色或者黑色进行混合,从而产生同种颜色下的浅色或深色色调。
您也可以在一种或者两种颜色中加入一个百分数,从而调整参与混合的颜色权重。例如 color-mix(in srgb, blue 75%, white)
将混合 75% 的蓝色与 25% 的白色,最终生成相较于白色更接近蓝色的某个色调。如若两种颜色都指定了某个百分数,且二者之和小于 100%,则混合结果将呈部分透明效果。
提示
在混合颜色时,还可以使用关键字
currentColor
。currentColor
是个特殊的关键字,它始终代表当前元素的文字颜色。将其与color-mix()
一起使用,则能够基于文字颜色生成类似或透明的颜色。例如,border-color: color-mix(in srgb, currentColor, white 20%)
会让边框的颜色变为当前文字颜色的浅色版本。
使用 color-mix()
函数是生成同一色系、不同色调的绝佳方案。例如,以下代码片段通过混合不同比例的白色与黑色,分别定义了由深入浅的九种绿色效果:
:root {
--middle-green: oklch(65% 0.15 166.4deg);
--green-10: color-mix(in srgb, var(--middle-green), black 80%);
--green-20: color-mix(in srgb, var(--middle-green), black 60%);
--green-30: color-mix(in srgb, var(--middle-green), black 40%);
--green-40: color-mix(in srgb, var(--middle-green), black 20%);
--green-50: var(--middle-green);
--green-60: color-mix(in srgb, var(--middle-green), white 20%);
--green-70: color-mix(in srgb, var(--middle-green), white 40%);
--green-80: color-mix(in srgb, var(--middle-green), white 60%);
--green-90: color-mix(in srgb, var(--middle-green), white 80%);
}
AI写代码css
1
2
3
4
5
6
7
8
9
10
11
12
所有主流浏览器的最新版本均支持上述写法,因此您现在就可以小试牛刀。不过截至 2024 年年中(mid-2024),仍有不少用户用的是旧版 Chrome 和 Safari 浏览器,因此建议您不要在没有备选方案的情况下冒然引入该特性。查看浏览器最新的兼容情况,详见:https://caniuse.com/mdn-css_types_color_color-mix。
译注
这是截止到 2024 年 12 月 8 日本文发稿时 Can I Use 网站收集的关于
color-mix()
函数的浏览器最新兼容情况(当前整体覆盖率已达 91.9%):
【补图 2:color-mix 颜色混合函数在各大浏览器中的支持情况(截至 2024 年 12 月 8 日)】
11.3.4.3 定义相对颜色
Defining relative colors
另一个新增的与颜色操作相关的特性功能为 相对颜色(relative color)。相对颜色是对现有颜色表示法的有力扩展,可以基于给定的颜色对各个参数进行调整。下面通过一个具体的案例来演示其用法。利用函数 oklch(from green calc(l / 2) c h)
将得到一个绿色的深色版本。该函数先将给定的绿色分解为 l
、c
、h
三个值;然后通过引用变量 l
、c
、h
,并结合 CSS 内置的 calc()
函数实现相应的数学运算。在本例中,L 分量减半,使得最终颜色变暗。
同理,也可以在 hsl()
函数中分别操作 h
、s
、l
分量的值;或者 rgb()
函数中的 r
、g
、b
分量值,不一而足。此外,给定的基准色也无需在对应的色彩空间中定义,例如对于某个十六进制颜色值,可以直接在另一个色彩空间中(如 OKLCH)进行相关操作,使用起来相当方便。以下代码演示了重点色(accent color)为某个浅绿色的相对颜色操作,通过调整其色调值(hue)生成了某个同样鲜艳的蓝色:
--accent-green: #22cf58;
--accent-blue: oklch(from var(--accent-green) l c calc(h + 100deg));
我还常常遇到这样的情况:需要在页面上使用某种颜色,并同时拥有该颜色的半透明版本。有了相对颜色这一全新特性,实现起来就轻松多了。下面给出的相对颜色设置在保留颜色变量 --brand-green
中 l
、c
、h
分量值的基础上,添加了一个表示不透明度的 alpha
通道值:
--transparent-green: oklch(from var(--brand-green) l c h / 0.6);
截至 2024 年年中,Firefox
尚未提供相对颜色的语法支持。该特性的浏览器最新兼容情况,详见:https://caniuse.com/css-relative-colors。
译注
截至 2024 年 12 月 8 日本文发稿时,
Firefox
浏览器已经在新版本中支持了相对颜色语法。最新兼容情况如下所示:
【补图 3:相对颜色功能在当前各大浏览器中的兼容情况,目前已获得主流浏览器支持(截至 2024 年 12 月 8 日)】
11.4 思考字体颜色的对比效果
Considering contrast for font colors
您可能已经注意到了,示例页中的字体颜色都是深灰色的,而非纯黑色(true black)。从 OKLCH 颜色值可以看出,该字体颜色的亮度为 26%
,而不是 0%
。使用灰色而非纯黑色是业内通行做法。在背光式的电脑显示器上,纯白色背景下的纯黑色文本会产生强烈的对比效果,很容易在阅读时引起视觉疲劳,特别是大段的文本。而黑底白字也会面临同样的问题。在这种情况下,要么用深灰色代替黑色,要么用浅灰色代替白色,或者都替换掉。这在用户看来仍旧是黑白分明,但阅读时会感觉更舒适。
我们不想让文本产生过于强烈的对比效果,同样也不希望对比效果太差。浅灰色背景上的灰色文本同样难以阅读,甚至会让用户的视力受损。这与在强光下翻看智能手机是同一个道理。那么,我们究竟该如何实现恰当的对比效果呢?
为此,W 3 C 的 Web 内容无障碍指南(Web Content Accessibility Guidelines,即 WCAG) 提供了最低推荐对比度(也称为 level AA
,即 AA 级推荐标准),以及更为严格的加强型对比度(也称为 level AAA
,即 AAA 级推荐标准)。这两个级别都对大号文本的对比度适当放宽了限制。表 11.1 列出了推荐的对比度情况。
表 11.1 WCAG 文本对比度建议
Level AA AA 级 | Level AAA AAA 级 | |
---|---|---|
普通文本 | 4.5 : 1 |
7 : 1 |
大号文本 | 3 : 1 |
4.5 : 1 |
WCAG 规范对大号文本的定义为:未加粗的、字体大小为 18pt
(即 24px
)及以上的文本;或者加粗的、字体大小为 14pt
(即 18.667px
)及以上的文本。也就是说,正文字体应该达到或者超出普通文本推荐的对比度;标题文本则应该达到或者超出大号文本推荐的对比度。同时 WCAG 规范还就对比度和一些设计元素(如界面组件、图形等)给出了指导意见。
WCAG 甚至还提供了一个计算对比度的公式,但我从来不操心相关的数学运算。利用现成的工具来替我运算要简单得多。
线上就有很多这样的工具,检索关键词 “CSS color contrast checker”(即 “CSS 颜色对比检查工具”)即可。每个工具都各有优劣,我比较喜欢的一款是 OddContrast(详见:https://www.oddcontrast.com)。该检查工具支持所有的 CSS 颜色格式,只需输入背景色和文本颜色,就能轻松计算出相应的对比度(甚至还会将数值转换为当前选定的格式),运行结果如图 11.18 所示。
【图 11.18 利用背景颜色与文本颜色算得的对比度为 15.6:1】
需要注意的是,众多设计图中,并不见得每一处文字都得达到 AAA 级对比度标准,WCAG 给出的建议里也提到了这一点。一个比较理想的处理方案是让主体文字达到 AAA 级标准、而对于那些彩色标签或者其他修饰性文字来说,则可以适当随意一些,达到 AA 级标准即可。
此外还需注意,并不是通过了数学计算就高枕无忧了,因为某些字体的可读性可能并不那么强,尤其是在设计中引入了纤细版字体时。图 11.19 展示了同一段文字的两个不同版本。虽然两个版本都用了相同的颜色,但肉眼感知到的对比度是截然不同的。
图 11.19 即便字体颜色相同,使用纤细版字体也会导致视觉上的对比度不足
如图 11.19 所示,两个段落虽然都使用了 Helvetica Neue
字体,但左侧左侧段落设置的文字粗细为 400
(normal,即常规粗细),而右侧段落则为 100
(thin,即纤细)。此时 7:1
的对比度对于左侧文字而言已经很好了,但是对于右侧文字来讲对比度则明显不足,可能需要再调高些。
提示
只有部分字体提供了对应的纤细版本,使用时为了确保较好的可读性,一定记得设置强烈的颜色对比效果。
另外,请保留本章刚开始创建的示例页。下一章我将为您演示具体的页面构建方法,并重点关注设计元素间的精确间距设置,以及网络字体的添加方法。
11.5 本章小结 Summary
- 视觉对比可以用于吸引用户关注页面上的重要内容。
- 除了传统的十六进制、
rgb()
及hsl()
方法外,以oklch()
、oklab()
等为代表的现代颜色语法还可以定义出色域更加宽广的色彩。 - 浏览器会将超出正常色域渲染能力范围的颜色作近似处理,并归入当前硬件上最接近的颜色来进行展示。
- HSL 与 OKLCH 都是圆柱形色彩空间,可以让色彩处理更加简单、一目了然。
- 利用
color-mix()
函数可将两种颜色混合在一起,从而生成第三种中间色。 - 将颜色值赋给构建策略保持一致的自定义属性,可以更方便地在整个网站中使用这些颜色,并且可以很轻松地按需补充调色板缺少的新颜色。