DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud渠道和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站:devui.design
Ng组件库:ng-devui(欢迎Star)
官方沟通群:添加 DevUI小帮手0 $ B 6 n T W(微信号:devui_o@ + U 9 J o xfficial)进群

导语

深色方法(Dark Mode)在iOS13 引进该特性后各大运用和网站都开始支撑了深色方法。在这之前,深色方法更常见于程序IDE开发界面和视频网站界面。前者通过下降屏幕亮度,使得运用人员长时间盯着屏幕眼睛没; Q Q ? j有那么疲倦;后者通过深色方法来降噪,从而突出主体内容部分。快速开发一个深色方A r ( ] 1法难吗? 在支撑css自定义特色(又称css变量,css variables)的– j 8 r现代阅读器里,可以说是恰当的简略。乃至可以在运转时实时新增主题,脱节传统css主6 o 0 T题文件加载方法下Y W F 3 S的主题需求预编译内置不能随时批改的弊端。下面我们来看一下如何运用css自定义特a F – + n s w g点来结束深色方法和主题化的开发。

主题切换器开发

首要我们需求打通一套支撑css自定义特色的开发方法。

CSS自定义特色运用

这儿简略介绍一下CSS自定义特色,有时分也被称作CS5 . g w cS变量或许级联变量。它包含的值可以在整个文档中重复运用。自定义特色运用 --变量名s g : 8 c M E g:k | K s量值. ; X A w / A W f来定义,用var(--变量名[,默许值]) 函数来获取值。举一, p J X l个简略比如:

<!--html-->
<div>&: ~ 3 b a | K Alt;p>text</p></div>
/* css */
div {5 ; o --my-color: red; border: 1px solid var(--my-color); }
p { color: var(--my-color); }

这时分div的边框和内部的p元素就能运用这个定义的$ d ] & ^ T p 变量来设置自己的色彩。

一般CSS自定义特色需求定义在元素内,通过在:root伪类上设置自定义特色,可以在整个文档需求的当地运用。CSS变量是可以继承的,也就是说我们可以: ` z ) K通过CSS继承创建一些部分主题,这儿就不打开部分主题的议论,我们只需求运用好:root伪类就能对整站实施主题化了。

如何切换主题呢,我们在运转的时分给头部刺进一段<style>:root{--变量1: 色值1;--变量2: 色值2 ;……}</style>,并通过id或许引证的方法坚持对该style2 0 ; o ^ V u E T元素的引证,通过批改style元素in~ 2 E n c knerText为 :root{--变量1: 色值3; --m E I T E J k _ h变量2: 色值4;……}就可以成功替换_ y * d x变量色彩了。

由于主题数据或许是从接口等其他当地获取的,我们可以在运用的当地给它先加上默许值,防止主题数据到达之前呈现没有F @ % ^ R 6 { g色彩的现象,比如 p { color: var(--变量1# { +, 色值1);}这样,就运用上了css自界o } s说特色来在运转时动态加载G e 4 ?不同的主题色彩值。

Sass/LeC } G T t ~ % Gss支撑

假设直接在开发css中运用css变量很简略由于书写问J | /题,定义问题终究导致变量许多,处理困难,改) : s 7 | ~ {变默许色值替换成本高档问题。在大型网站的开发中一般会用sass/less来预定义一些色彩变量来进行色彩处理。

在运用sass和less的时分可以改动本来的传递色值S ; v O /方法改为传递css自定义特色和默许值。color定义文件:

before
after
// sV M yass
$brand-primary: #5e7ce0;
// less
@brand-primary: #5e7ce0;
// sass
$brand-primary: var(–brand-primary, #5e7ce0);
// less
@brand-primary: var(–brand-primary, #50 O / [ qeg T F z N s A7ce0);

B $ P h k v T E儿有个副效果就是,G Q o p y 1 H Zv + u [ { )旦色值被定义m = | f T为var变量,则这个vah 0 n e hr表达式就无法再被less/sassY N 7 d B ;的色彩核算函数所核算运用,这块我们在后面的章节再进行议论。

定义完对应的变量之后, 运用的当地就可以直接运用运用这些变量,方便共同处理。

运用媒体查询

prefer-color-scheme是阅读器获取系统上用户对色彩主题的倾向性的css apie I x S l 0,运用该api我们就可以轻松使得网站的主题跟从系统的色彩设置展现不同的色彩了。

css的API如下:

// css
@media (prefers-color-sch:  meme: light) {
:root{--变量1: 色值1;--变量2: 色值2; ……}
}
@media (prefers-color-scheme: dark) {
:root{--变量1: 色值3; --变量2: 色值4; ……}
}

脚本方面也有对应的媒体查询方案,js的API如下:

// js
function isDarkSchemePre, I v g 2 4 4 j zference(){
return window.matchMedia('screen and (prefers-color-scheme: dark)').matches;
}

M 4 } ; H 题切换服务

终究我们需求写一个主题服务,首要意图就是支撑在切换主题的时分运用不同的css变量数据,假定我们的css变量的数据存储在一个目标里,key值为css变量名,value值为css变量在该主题下的值,那么我们的主题切换服务的要害中心函数如下:

// theme.ts
export class Theme {
id: ThemeId;
name: string;
data: {
[cssVarName: string]: string
};
}
// theme-service.ts
class ThemeService {
contentx x # Q @ DElement;
eventBus;
//. H ] G G ……
applyTheme(theme: Theme) {
this.currentThemT O 2 G be = theme;
if (!t V ?this.conteY , -ntElement) {
const styleEld k g ( 9ement = document.getElementB. . A / 9 { U ] TyId('devuiThemeVariables'N E 5 [ / 4);
if ( styleElemenM l J % f at) {
this v % j Q 0 I g.contentElemeF G R B rnt = <HTMLStyleElement>styleElement;
} else {
this.contentE/ h s 1 2lement = document.createElement('style');
this.contentElement/ D p.id = 'devuiThem: x G 1 XeVariables';
document.head.appendChild(this.contentElemZ q ]ent);
}
}
this.contentElement.innerZ ] + !Text = ':root[ m U x } | { ' +w ] k i N this.formatCSSVariablesV J 9 4 Y . / p n(theme.data) + ' }';
dB i V t Mocument.body.setAttribute(| F x'ui-theme', this.cur1 s 6 1 q .rentTheme.id);
// 通知外部主题改动
this.notify(theme, 'themeChanged');
}
formatCSSVariables(themeData: Theme['data']) {
return Object.keys(l / } gthemeDataM ~ S T 9 % c 4).map(
cssVar => ('--' + cssVar + ':' + themeData[cssVar])
).joinI + j(';');
}
private notify(them Q 1 # t Q % 1e: Theme, eventType: string) {
if (!this.eventBus) { return; }
this.eventBus.trigger(eventType, theme);
}
}

其中applyTheme函数会创建一个style元素,假设现已创建好了则直接改动style的内容。假设要支撑跟从系统还需求一些额定函数的判别,这儿就不打开了,可以参阅链接,原理是通过动画结束工作监听媒体查询改动,对应能| d & U ( : u `够运用enquirejs库。

至此我们打通了主题服务和css变量值在开发中的运用,下面就可以开发一个深色方法了。

深色方法开发

语义化O @ 6 6 : 6色彩变量

深色方法触及到了很多网站视觉的“反色”,在已有的网站当中,应该好好排查和收拾网站的色彩,把色彩归一和捆绑到必定的p ] r @ H 3 R Q变量规模和数量里,并给色彩的不同运用场景一个不同的语义变量名,这样能获得场景分别的效果。

从文本色彩上我们举个简略的比如:

一般的网站里都会有正文(首要文本),协助提示信息(非M t Q I [ | C有必要文本),文本占位符。这儿我们可以运用三个变量来描绘这些文本text-color-primary,text-colorS V [ % J-secondary,text-color-tertiary,也可以运用9 X q ntext-color-normal,text-color-$ C $ q o /help-info,ta c 0 b O ? u 9ext-color-plX i g 9aceholder来描绘这这些色彩值。

这儿强烈建议运用更有语义的变量而不是色值本身的描绘,比如:过错布风光,应该运用background-Y C ! ! tcolor-danger而不是background-coloro % U } ! ~-red,由于关于不同的主题色彩值或许是不一样的。

Web界面深色形式和主题化开发

图1 语义* w @ U D m化变量暗示

运用共同语义变量控制组件体现

需求定义多少的变量才恰当,这个取决于网站的色彩空间束I 1 E 7缚规模和运用a j d l A 3 Q场景的定义粒度。当定义了一套变量之后我们就可以对组件/网M z [站的不同组成部分进行变量共同。

比如搜索框和V n 9 c } D L P下拉框,运用相同的变量控X f 8 H 5 .制相同部分的体现,使得组件在主题改动的可以运用相同的色彩规矩W ? )

Web界面深色形式和主题化开发

图2 运用变量对组件进行规约

供应暗黑主题色值

结束了上面重要的两步,我们就可以通过给变量供应一套新的色值来到达主题的改动了。

Web界面深色形式和主题化开发

图3 通过色} Y w 2值的切换完结深色主题切换

图片的处理

图片的处理并不能像文字一样地去反转色彩或许反转) 4 5 &亮度,这样或许照成不适。一般假设有准备亮色和暗色两套图片,可以采用变量化图片地址在不同主题下切黑图片。假设图片来自用户输入,其他当地的截图,这时Y ( M 4 (分需求稍微处理一些[ ) X 8 6 g r | 6下降亮度。图片简化地c – ( A获取当时的主题状况可以在body上增加一个ui主题是否是深色方法的特色。

深色方案一:图片增加透明度。适用场景:简略文章图片和纯色布景。

// css
body[ui-theme-mode='dark'] img {
opacity: 0.8;
}

深色方案二:带图片的方位叠加一个灰色半透明的层,适用场景:布景图,非纯色布景等。

/B f D # z a/ css
body[ui-theme-9 W ~ B bmode='dark'] .darkB ~ d /-mode-image-K C j o Loverlay {
position: relative;
}
body[ui-theme-mode='dark'] .dark-md Y ]ode-image-overlay::before {
content: '';
display: block;
position: absolute;
top: 0;
left:8 7 A W % O N 0;
right: 0;
bottom: 0;
background: rgba(+ i ;50, 50, 50,# ^ ) $ p 0 0.5);
}

前者不适R g S *用与带有布景图片的层处理,也不适宜通过叠加图片遮挡来呈现效果的处理,可是用在文章博客中的刺进图片非常简略有用,图片{ ~ p可以自然地叠加到纯色深色的布风光上。i j %后者给了另一种方案结束布景层的叠加,但对代码有必定的侵犯。

供应主题改动订阅应对第三方组件场景

通过以上几个基本的过程就能在编码的过程中通过运用变量指定色彩值,获得主题的才能。可是面对很多第三方组件,有自己的主题,也或许有自己的深色主题,这块再去侵犯式地批改成自定义的变量工作量不小且并不必定适宜。

这时分需求供应主题订阅,在主题发生改动的时分,获得通知,然后给第三方组件设置必定对1 c / X q k j 0应的改动。

我们需求一个简略的eventbus,完结9 ; 0 Q ~ h方法不限。这儿给出一个简略版本的接口如~ E d 8 l c [ l下:

// theme/interface.ts
export interface IEventBus {
on(eventName: striI V D & ] gng, callbacks: Function): void;
off(eventName: string, callbacks: Function): void;
trigger(e- % ] Q u 8 B ; bventName: string, data: any): void;
}

切换主题的时分发S f 3 w h N k出themeChanged工作d Y 1 $ K U o a,运用on监听就y 6 ^可以获得当时主题改动工作,通过判别主题,给第三方的组件套上对应的主题,或许批改js色彩变量等等。

降级支撑和运用脚本腻子

降级PostCSS插值脚本

一旦运用了var之后,那些不支撑var的老阅读器会显现为无色彩,这儿y ~ O u我们运用postcs_ 0 b ,s插件处理终究一个阶段的css。

// postcss-plugin-add-} e 9var-value.js
var postcss = require('postc! E ` . u ] $ bss');
var cssVv l (arReg = new Re` ` % Y # z )gExp('var! t R D\\(\\-\\-(?:.*?),(.*?)\\)} p . P 8 w ', 'g');
module.expor7 n + = % 3 , lts = postcss.plugin('poso 8 ) 2 P 4 Htcss-plugin-add-origin-cssT t f f q - k E ,-var-value', () => {
return (root2  T %) => {
root.walkDecls(dec- s , y Q ul =&g[ ? m nt; {
if (decl.type !== 'coe A F Y A  X mment' && decl.value && decl. $ 9 i v.value.match(cssVarReg)) {
decl.cloneBefore({value: decl.value.replace(cssVarReg, (match, item) =&g n ] $ X u Zt; item) });
}
}% { 3 Y &);
}
});0 w U - z I } p U

该postcss插件通过遍历css规矩里的带有var(--变量名, 变量值)在该行的上一行刺进了一行替换为直接变量值的值,兼容不支撑css vs % O F e Zar的阅读器。

before
after
color:c * U var(–brand-primary, #5e7o } u c Z T ice0);
color: O m m #5e7ce0;W Z 5 ? v D ( [
color: var(–brand-primary, #5e7ce0);

css-vars-ponyfill 使 IE9+ 和 Edge 12+支撑上主题切换

css-vars-ponyfi] c U O # y Gll 这个npm包可以使得ie9+/edge12+支撑上css自定义特色,它是一个带有选项的兼容方案,大概原理就是通过监听style里带有v4 0 7ar自定义W c m & J J : @ w特色的值,替换为原值并刺进。该兼容方案现在不兼容直接挂在在元素上的部分的css, } D 4自定义特色定义。该方案还供应了实时监听style刺进的选项,支撑var链式的取值。简略地参与polyfill就可以运用了。

// polyfill.ts
import cssVars from 'css-vars-p [ 2onyfill';
cssVars({ watch9 , n L ( b (: true, silent:/ N X o true});

一些问题的探讨

什么网站需求开发深色方法?

深色方c r 9 s % y K E法适宜长时间阅览、长时间沉溺式阅读的网站,包括新闻、博客、知识库等文章$ , V g阅读) U ; d Y k和视频网站,开发IDE界面等沉溺式交互。这些网站e M 0 ] # } g运用深色方法可以通过下降亮度削减对眼睛的影响,削减长5 e 7期阅读的疲倦和晕眩的感觉。

深色方法不适宜一些非深色风_ Z ? :格产品的展现,深重的布风光会影响产品风格呈现、传递的情感和用户观看时分的心境,不P W 7 l W k A g恰当的色彩分配简略引起反感。像一些电商网站深色方法要慎重处理,深色或许会使得产品图V j 2 0 4 L A #片呈现的活跃风格遭到必定程度的按捺,色彩或许会影响用户的购物愿望。一些主题推行宣. W _ a u传类的网站也是,* A 3 ^ u色彩或许会削弱主题的表达– t 7 [ l W

有没有更简略的深色方法映射切换?比如运用HSL代替RG, ) ~ u [B色值。

HSL色值的表达方法是通过色相、饱满度、亮度,已然深色方法是调整亮度和饱满5 ^度,那是否可以通过hsl色值来自动核算呢? 这种自动出暗色版a 8 8 u E D c I (本的色值还有待探求中,首要N . 1 & 8 8 5有两个原因:1)深色方法的舒适度不是线性亮度和饱满度映射能结束的,色彩的函数核算深色映射显得相对单调。2)实际情况是一个色彩或许会映射到多个暗黑场景6 u C V V w – .的色彩。

针对第一点,现在有一些UI会推出非线性反色的算法,也1 A /是为了解决色彩一起调整亮度之后变得看不清、色彩反色后冲击过大的问题。这类的算法还有许多优化空间。在淡色分配情况下或许很美观的色彩,放到深色下或许就会引起不舒适:不恰当的对比度会引起视觉上看不明晰;不恰当的色彩碰撞会X : 3 O I 1 V引起反感;不恰当的饱满度、亮度会显得UI有点脏。

针对第二点,可以举以下的场景来阐明:相同是白色,有色布景下的白 * ? W .色,在深色方法下或许仍是坚持白色;而作为布风光的白色在深色场景下会对应调整为深色。

Web界面深色形式和主题化开发

图4 一种白色的存在切换主题的多种映射

此刻,自动通过2 ` = B u . K T K色值核算就需求区分, j h % 9 + 1 }色彩的周边色彩或许底层叠加色彩来核算,这无疑加大了核算难度。

所以这块自动核算并不太简略,还需求一些的探求。

Sass/Less运用var变量后变成字符7 % J 9 ^ r 4串处理,无法对色彩进行转换核算?

本身sass/less的变量和css自定义特色就不是一套变量系统,sass/less的是一种编译型变量(编译f * 3 o } O d W时承认值,编译后不存在),而css是一个运转. u k R时变量(即运转时承认值)。用sass/less去处理css变量时为了处理css变量防止定义失误,但运用了Sass或Less之后替换成var之后会发现,sass和less是一些比如lightenfadeoutrgba等等的函数都无法运u a & a @ [ X用了,由于对` G _ $ G与sass和les$ s bs来说,var(--xxx, #xxx)是一个字符串不是色彩值。3 p a & X = : { d这块现在也没有比较好的方法, 有一 b + 6 g些文章也议论了一些解法,如 链接,大体的思路是拆分色彩的表达为hsl方法,然后对色彩的维度进行操作处理,实际上仍是不能无感知地运用内建的色彩转换函y q P & ^ j数。另一个解法/方案是:把触及v # J ] Q色彩转换的当地共同处理然后再赋予新的css变量名,不再在mixin等函数里对色彩进行转换而是对变量名进行规矩改动。假设读者有其他较好的思路也可以在议论里共享。

总结

本文介绍了运用CSS自定义特色能/ { 5 { c ( } (够给css定义一些色彩变量,轻松地完结深色主题的开发乃至支撑更多的主题化。通过色彩变量定义,运用变量,处理图片和处理三方组件支撑完结整站的深色方法的规约和完善。进一步介绍了降@ h E H | b 4 | I+ # N c j支撑的方法,并对深色方法的适用规模和一些其他方法完结进行了议论。

参与我们

我们是DevUI团队,欢迎s 9 x E T g 0 3来这儿和我们一起打造典雅高效的人机D 5 z } 4 *设计/研发系统。招聘邮箱:muyang2@huaweiG x # ^ ].com* y % e

文/DevUI rhlin

往期文章引荐

《浅谈前端中的圈N ) U T 6 /复杂度》

《手把手教你b & J ) G搭建一个灰度发布环境》

《手把手教你运用8 # T ? g Vue/React/Angular三大框架开发Pagination分页组件》