126

你的网站可以一键变色吗? - 知乎专栏

 6 years ago
source link: https://zhuanlan.zhihu.com/p/29610065
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

你的网站可以一键变色吗?

Write cool things.

若干年前写过一个叫「网站换色精灵」的小工具,原理是调整网站所有图片的色相、饱和度和亮度。然而并没有什么人用……或许是因为做得不好,又或许这本身就是一种伪需求。

得益于 Web 标准的发展和设计风格的变化,前端开发者从通过切图还原设计逐渐变为通过代码还原设计。CSS 预处理器也在一定程度上弥补了 CSS 本身表达能力的不足,许多 UI 框架(比如 Element)将基础的颜色值作为配置项供使用者定制,其余的颜色则在它们的基础上调整亮度/饱和度,或者与其他颜色混合而成。虽说做不到一键变色,但是通过重新构建来改变整个网站的配色是没有问题的。

除了可定制,这样做还可以让代码变得更容易维护。相比较充斥着各种颜色值的 CSS 代码,甚至可以表达出一些配色思路(或者让不善于设计的我写出至少配色上还过得去的 UI)。

配色思路?

下面这段样式是从七牛的管理控制台中摘抄的:

.btn.btn-primary {
    color: #1989fa;
    background-color: rgba(25,137,250,.04);
    border-color: rgba(25,137,250,.4)
}

心算一下(Shift + 左击),就能发现上面的三个颜色是同一种颜色,只是透明度不同。

v2-e82ef2a913ac0f79c0ed8da8c4cf4046_b.jpg

由于页面的背景是纯白的,因此调整颜色透明度可以看成是在调整颜色的亮度。按钮虽然只用了「一种颜色」,但是看起来还是比较和谐的。

从中可以看出对主按钮常规状态的设计思路是:

  1. 使用 #1989fa 作为基础颜色;
  2. 文字颜色使用基础颜色;
  3. 将基础颜色调亮 96% 作为背景色;
  4. 将基础颜色调亮 60% 作为边框的颜色。

预处理器?

使用像 Sass 这样的预处理器很容易实现上面的需求:

$primary-color: #1989fa;

$text-color: $primary-color;
$background-color: scale-color($primary-color, $lightness: 96%);
$border-color: scale-color($primary-color, $lightness: 60%);

.btn-primary {
  color: $text-color;
  background-color: $background-color;
  border-color: $border-color;
}

Sass 会在编译期间计算出表达式的值,生成这样的 CSS 代码:

.btn-primary {
  color: #1989fa;
  background-color: #f6faff;
  border-color: #a3d0fd;
}

不过,使用预编译器就意味着需要构建——总有一些人不喜欢「构建」过程,或者倾向于使用更「原生」的解决方案。

那么,使用纯 CSS 可以在一定程度上实现这样的效果吗?答案是肯定的,七牛管理控制台的例子中就用了透明度来实现提升亮度的效果。问题在于,其中的颜色值出现了多次,可维护性还是不高。

CSS 变量

CSS 变量是一项实验中的技术,不过现代浏览器大多都已经支持了,所以如果你的网站面向的用户使用的基本都是现代浏览器,可以考虑使用这项技术。后文尝试使用这项技术来描述 UI 的配色,编写更容易维护的纯 CSS。

我不打算详细介绍 CSS 变量,如有兴趣可以查阅 MDN 和相关规范。不过不必担心,即便对 CSS 变量了解不多也没关系,后文在用到 CSS 变量时会有一些简单的解释。

我打算写一个页面作为例子。

好吧,作为一个不会设计的前端工程师,我准备找一个现成的颜色主题。在 Adobe Color CC 上最受欢迎的颜色主题里挑了个顺眼的,就可以开始配色了。有了颜色主题,配色会容易一些,只需要选 3 ~ 4 种颜色,就可以配出一个不错的 UI 了。

背景色和文字颜色

为了确保可读性,只要选出反差和亮度差最大的两种颜色即可。在这个颜色主题里,自然是前两个偏黑白的 #323a40 和 #e5eef4 了。我想做一个暗色的配色,因此选择前者为背景色,后者为文字颜色。

:root {
  --background-color: #323a40;
  --text-color: #e5eef4;
}

CSS 变量以两个连字符开头,定义 CSS 变量与设置属性类似。上面这段代码定义了 --background-color 和 --text-color 这两个 CSS 变量。:root 选择器会选择根节点(也就是 <html>),与 html 的区别在于优先级更高,适合用于定义全局 CSS 变量。

html {
  background: var(--background-color);
  color: var(--text-color);
}

要引用定义的 CSS 变量也很简单,只需要使用 var 函数即可。这样,页面的背景色和文字颜色就设置好了。

在 JSFiddle 上 DIY

然后,选择一个主色。主色通常被用在超链接、主按钮、logo 上。为了它们更突出,应该选择一个与背景色和文字颜色都有一定反差的颜色。这里,我选择颜色主题中的第三个颜色 #37b0c0。

:root {
  --background-color: #323a40;
  --text-color: #e5eef4;
  --primary-color: #37b0c0;

  --input-size: 30px;
  --input-padding-horizontal: 10px;
  --button-border-radius: 4px;
}

button {
  background: none;
  border: 1px solid var(--primary-color);
  border-radius: var(--button-border-radius);
  color: var(--primary-color);
  height: var(--input-size);
  padding-left: var(--input-padding-horizontal);
  padding-right: var(--input-padding-horizontal);
  transition: all .15s ease;
}

button:hover {
  background: var(--primary-color);
  color: var(--background-color);
  cursor: pointer;
}

CSS 变量不仅可以定义颜色值,上面的代码还用 CSS 变量定义了按钮的大小、内边距和边框的半径。

在 JSFiddle 上 DIY

CSS 里并没有像 Sass 里 darken、lighten 那样的颜色函数,可以考虑使用透明度在一定程度上实现加深或者减淡的效果。不幸的是,CSS 里同样也没有操作颜色透明度的函数。我们只能把颜色的三个分量拆开定义:

:root {
  --background-color-r: 51;
  --background-color-g: 59;
  --background-color-b: 64;
  --background-color: rgb(
    var(--background-color-r),
    var(--background-color-g),
    var(--background-color-b)
  );

  --text-color-r: 229;
  --text-color-g: 238;
  --text-color-b: 244;
  --text-color: rgb(
    var(--text-color-r),
    var(--text-color-g),
    var(--text-color-b)
  );

  --primary-color-r: 62;
  --primary-color-g: 176;
  --primary-color-b: 190;
  --primary-color: rgb(
    var(--primary-color-r),
    var(--primary-color-g),
    var(--primary-color-b)
  );

  --input-size: 30px;
  --input-padding-horizontal: 10px;
  --button-border-radius: 4px;
}

是的,这么定义很麻烦。不过,每个颜色值还是只会出现一次。

input, button {
  --border-color: rgba(
    var(--text-color-r),
    var(--text-color-g),
    var(--text-color-b),
    var(--border-color-alpha, .3)
  );
  border: 1px solid var(--border-color);
}

其中,var(--border-color-alpha, .3) 表示引用 --border-color-alpha 变量的值,如果变量没有定义或者无效,则回退到 .3。这样一来,input 和 button 的边框颜色会变成背景色混合 30% 的文本颜色。

input:focus {
  --border-color-alpha: .6;
}

当焦点在 input 上时,--border-color-alpha 的值将变为 .6,此时边框颜色会变成背景色混合 60% 的文本颜色。

我使用同样的方法写了一个友好的 header。

在 JSFiddle 上 DIY

产品经理找到我说,大多数程序员都觉得我做的页面很友好,但是少数非夜猫子程序员觉得这个主题在白天太刺眼了,希望能有一个「白天主题」。

好在 JavaScript 可以设置 CSS 变量的值,而白天主题只需要把背景颜色和文字颜色互换就可以了。

const themes = [
  {
    name: 'dark',
    scheme: {
      '--background-color-r': 51,
      '--background-color-g': 59,
      '--background-color-b': 64, 
      '--text-color-r': 229,
      '--text-color-g': 238,
      '--text-color-b': 244
    }
  },
  {
    name: 'light',
    scheme: {
      '--background-color-r': 229,
      '--background-color-g': 238,
      '--background-color-b': 244, 
      '--text-color-r': 51,
      '--text-color-g': 59,
      '--text-color-b': 64
    }
  }
];

let currentTheme = 0;
window.nextTheme = function () {
  currentTheme = (currentTheme + 1) % themes.length;
  const theme = themes[currentTheme];
  Object.keys(theme.scheme).forEach(name => {
    const value = theme.scheme[name];
    document.documentElement.style.setProperty(name, value);
  });
}
v2-fe949555784d00423ce1a86c9ff9f2a9_b.jpg

在 JSFiddle 上 DIY

透明度不能解决所有问题,如果需要和另一种颜色混合(单纯与黑白混合可以考虑使用 HSL 模型),或者需要渐变,就只能使用一些「黑科技」了。

比如说,想把背景颜色设置为 50% 文字颜色 + 50% 主色:

... {
  --base-color: var(--text-color);
  --mix-color: rgba(
    var(--primary-color-r),
    var(--primary-color-g),
    var(--primary-color-b),
    .5
  );
  background-color: var(--base-color);
  background-image: linear-gradient(
    to bottom,
    var(--mix-color),
    var(--mix-color)
  );
}

除了用起来不如 CSS 预处理器方便之外,Safari 在某些情况下无法工作。比如说

:root {
  --r: 255;
  --g: 0;
  --b: 0;
}

.foo {
  border: 10px solid
    rgba(var(--r), var(--g), var(--b), .5);
}

在 Safari 下边框会被渲染为 currentColor 而不是半透明的红色。

解决方法很简单,在内部多定义一个 CSS 变量即可。

在 JSFiddle 上 DIY(请对比在 Chrome 中和 Safari 中的表现)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK