web深色模式适配指南

5,536 阅读5分钟

什么是深色模式?

深色模式(Dark Mode)也被叫做暗黑模式,顾名思义,它给人最直观的感受,就是黑。 但「深色模式」要实现理想的视觉体验,绝不是将底色变黑,将文字变白这么简单。

Google 在 Material Design 的设计指导中对于深色模式中列出的设计规范中,第一条就是「不要使用 100% 的纯黑」。

UI 设计师 Ilke Verrelst 曾指出,不要在纯白背景上显示纯黑文字,反之亦然,这是基本的设计规则。

为什么呢?因为纯白色会反射所有波长的光线,而纯黑色会吸收所有光线,这是对比度最大的两种颜色,白底黑字时,文字过于刺眼,使眼睛过于疲劳,而黑底白字时,文字又可能难以辨认。

如何做一个引起极度舒适的「深色模式」呢?

当中涉及的设计和开发量,甚至不比重新开发一遍少。

以下是我的实战经验,希望对大家兼容深色模式有一些帮助和思考!

第一步:开发过程中,如何切换 深色和浅色模式?实时观察自己写的是否生效呢?

我们以google浏览器为例:

打开google浏览器 --- H5调试 --- 选择console

image.png

切换为深色模式

(command + shift + p) --- 搜索框输入 dark --- 选择 Emulate CSS prefers-color-scheme: dark

注: 因为google h5页未支持深色模式 所以没有变色!

image.png

切换为浅色模式

(command + shift + p) --- 搜索框输入 light --- 选择 Emulate CSS prefers-color-scheme: light

image.png

第二步:设计给出色值对照表

// 左侧:浅色模式 右侧:深色模式
[ ['#F3F7FE', '#303D53'],
 ['#E1ECFC', '#35496D'],
 ['#3B81F5', '#4171C0'],
 ['#1266EF', '#4B8FFF'],
]

第三步: 开始适配

1、声明 color-scheme

1.1 meta

在head中声明<meta name="color-scheme" content="light dark">,声明当前页面支持 light 和 dark 两种模式,系统切换到深色模式时,浏览器默认样式也会切换到深色;

1.2 CSS 下面的 css 同样可以实现上面 meta 声明的效果

:root { color-scheme: light dark; }

注:此声明并非为页面做自动适配,只影响浏览器默认样式,更多信息可查阅 W3C 文档 《CSS Color Adjustment Module Level 1》

2、媒体查询

CSS 媒体特性用于检测用户是否有将系统的主题色设置为深色模式或浅色模式。

色值/背景图

.bottom {
   color: #F3F7FE; // 浅色模式下 字体色值
   width: 3.75rem;
   height: 3.16rem;
   padding: 0;
   background-image: url('../assets/footer.png'); // 浅色模式下的背景图
   background-repeat: no-repeat;
   background-size: 100% 100%;
 }
@media (prefers-color-scheme: dark) {
   .bottom {
     color: #303D53;  // 深色模式下 字体色值
     background-image: url('../assets/footer_dark.png'); // 深色模式下的背景图
     background-repeat: no-repeat;
     background-size: 100% 100%;
   }
}

当我们的色值越来越多,涉及的元素越来越多。代码重复率高,耦合度高,不好批量维护等一系列的问题!

这时候我们就要考虑用css相关的插件来解决这个问题!

在这里我推荐 postcss-darkmode 具体的使用说明如下:

1)安装

$ npm install postcss-darkmode --save-dev

2)配置

  • ratio (number) :亮度调整的百分比,默认为 10

  • assignColors (array): 颜色替换表,如

    assignColors: [
        	["#D6AB56"], // 保持颜色不变
        	["#ff6022", "#f25b20"], // #ff6022 替换为 #f25b20
        ]
    
  • ignoreExistingDarkMediaQuery (boolean): 不处理 css 文件中已有的 darmkmode Media Query 中的颜色规则, 默认为 true

  • ignoreFiles(array): 不需要深色转化的文件,支持正则匹配, 如 ignoreFiles: ["aaa.css", /bbb.css/],

3)注释

  • /* darkmode: off */ Disable all Darkmode translations for the whole block both before and after the comment. 这个注释所在的 css 规则块都不进行「深色模式」转化
  • /* darkmode: ignore next */ Disable Darkmode translations only for the next property. 只忽略紧跟这个注释后的一条
  • /* darkmode: {#f00} */ Replace the next property with #f00. 使用 #f00 替换紧跟这个注释后的规则的值

如果发现未生效,可能是你所用的打包程序在 postcss-darkmode 处理之前已经将注释过滤掉了,你可以在注释上添加叹号 ! ,如 /*! darmkmode: off */ 来规避注释被过滤。

4)使用

use webpack

const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
    mode: "development",
    entry: "./src/index.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "index.js",
    },
    plugins: [
        new MiniCssExtractPlugin({
                filename: "css/style.min.css",
                chunkFilename: "css/[id].min.css",
                ignoreOrder: false,
        }),
    ],
    module: {
        rules: [
            {
                test: /\.s[ac]ss$/i,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                                hmr: process.env.NODE_ENV !== "production",
                        },
                    },
                    "css-loader",
                    "sass-loader",
                    {
                        loader: "postcss-loader",
                        options: {
                            ident: "postcss",
                            plugins: [
                                require("postcss-darkmode")({
                                    ratio: 10, // 亮度调整的百分比,默认为 10%
                                    assignColors: [
                                        ["#D6AB56"], // 保持颜色不变
                                        ["#ff6022", "#f25b20"], // #ff6022 替换为 #f25b20
                                        ["#ff00a0", "#e60090"],
                                        ["#ff3333", "#e62e2e"],
                                        ["#a1a1a1", "rgba(233, 237, 243, 0.1)"],
                                        ["rgba(244, 20, 20, 0.3)", "#E91E63"],
                                    ],
                                    ignoreExistingDarkMediaQuery: true,
                                    ignoreFiles: ["style.scss"], // 不需要深色转化的文件,支持正则匹配
                                    splitFiles: {
                                        enable: false, // 是否将深色样式分离为一个新的css文件
                                        suffix: ".darkmode", //深色css文件名后缀,比如 filename.css 的分离出深色文件: filename.darkmode.css
                                        destDir: "../../dist/css", //文件输出目录(相对当前要处理的css文件所在目录)
                                    },
                                    inject: {
                                        enable: false, // 是否不使用媒体查询模式,而通过类名切换深色样式
                                        injectSelector: ".__darkmode__", // 切换深色样式的类名
                                        baseSelector: "html", // 这个类名要添加在哪个选择器上
                                        keepMediaQuery: false, // 是否保留 media query 部分的代码,满足某些两种代码都需要的需求
                                    },
                                }),
                            ],
                        },
                    },
                ],
            },
        ],
    },
};

use gulp

const gulp = require("gulp");
const postcss = require("gulp-postcss");
const darkmode = require("postcss-darkmode");
gulp.task("css", () => {
    return gulp
        .src("*.css")
        .pipe(
            postcss([
                darkmode({
                    ratio: 10, // 亮度调整的百分比,默认为 10%, 如果为0,再不自动调整颜色亮度
                    assignColors: [
                        // 颜色替换表,不在此表的颜色将按照 ratio 进行亮度调整
                        ["#D6AB56"], // 保持颜色不变
                        ["#ff6022", "#f25b20"], // #ff6022 替换为 #f25b20
                        ["#ff00a0", "#e60090"],
                        ["#ff3333", "#e62e2e"],
                        ["#a1a1a1", "rgba(233, 237, 243, 0.1)"],
                        ["rgba(244, 20, 20, 0.3)", "#E91E63"],
                    ],
                    ignoreExistingDarkMediaQuery: true, //不处理 css 文件中已有的 darmkmode Media Query 中的颜色规则, 默认为 true
                    ignoreFiles: ["style.scss"], // 不需要深色转化的文件,支持正则匹配
                    splitFiles: {
                        enable: false, // 是否将深色样式分离为一个新的css文件
                        suffix: ".darkmode", //深色css文件名后缀,比如 filename.css 的分离出深色文件: filename.darkmode.css
                        destDir: "../../dist/css", //文件输出目录(相对当前要处理的css文件所在目录)
                    },
                    inject: {
                        enable: false, // 是否不使用媒体查询模式,而通过类名切换深色样式
                        injectSelector: ".__darkmode__", // 切换深色样式的类名
                        baseSelector: "html", // 这个类名要添加在哪个选择器上
                        keepMediaQuery: false, // 是否保留 media query 部分的代码,满足某些两种代码都需要的需求
                    },
            }),
        ])
    )
    .pipe(gulp.dest("./dist/css"));
});

3、图片适配

利用picture+source标签,设置不同模式下的图片 url。

<picture>
    <!-- 深色模式下的图片 -->
    <source srcset="dark.png" media="(prefers-color-scheme: dark)" />
    <!-- 浅色模式下的图片 -->
    <img src="light.png"/>
</picture>

4、js监听判断当前模式

通过this.isDarkMode 来做具体的操作处理

<template>
  <div>
   <img src="image" />
  <div/>
<template/>

<script>
data () {
  return {
    isDarkMode: false,  
  }
},
 beforeMount() {
    const darkModeMediaQuery = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)');
    this.isDarkMode = darkModeMediaQuery.matches;
    darkModeMediaQuery.addListener(() => {
      this.isDarkMode = darkModeMediaQuery.matches;
    });
},

computed: {
   image: isDarkMode ? 'dark.png' : 'ligth.png'
},
<script/>