NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

深色模式适配指南

  • 2021-04-09
  • 本文字数:5703 字

    阅读完需:约 19 分钟

深色模式适配指南

背景


随着 iOS 13 的发布,深色模式(Dark Mode)越来越多地出现在大众的视野中,支持深色模式已经成为现代移动应用和网站的一个潮流,前段时间更是因为微信的适配再度引起热议。深色模式不仅可以大幅减少电量的消耗,减弱强光对比,还能提供更好的可视性和沉浸感。


那针对一款 App 应用(原生 + H5)怎么进行深色模式的适配呢?今天就让我们一起来探究吧!


系统兼容


想要实现深色模式的效果,前提条件是要系统支持,目前常见系统支持情况如下:



H5 深色适配


随着深色模式的流行,越来越多的操作系统、浏览器开始支持深色模式,现在可以利用 CSS 的媒体查询方法:prefers-color-scheme  以及 CSS 变量 (CSS variables、CSS custom properties)就可以实现页面主题跟随系统自动切换深浅模式。CSS 变量除了 IE,其余各大浏览器都支持的比较好,但 prefers-color-scheme 方法还处于 W3C 草案规范,需要对不兼容浏览器做向下兼容,具体浏览器兼容性可以查询  Can I Use ,综合来说,高版本的主流浏览器都已经支持,IE 不支持。


可以通过以下两种方式来实现 Web 端的深色适配:


一、CSS 的媒体查询


prefers-color-scheme 是一种用于检测用户是否有将系统的主题色设置为亮色或者暗色的 CSS 媒体特性。利用其设置不同主题模式下的 CSS 样式,浏览器会自动根据当前系统主题加载对应的 CSS 样式。light 适配浅色主题,dark 适配深色主题,no-preference 表示获取不到主题时的适配方案。


  • CSS

@media (prefers-color-scheme: light) {   .article {      background:#fff;     color: #000;    } @media (prefers-color-scheme: dark) {   .article {      background:#000;      color: white;    } @media (prefers-color-scheme: no-preference) {   .article {      background:#fff;     color: #000;    } 
复制代码
  • link 标签

<link href="./common.css" rel="stylesheet" type="text/css" /> <link href="./light-mode-theme.css" rel="stylesheet" type="text/css" /> <link href="./dark-mode-theme.css" rel="stylesheet" type="text/css" media="(prefers-color-scheme: dark)" /> 
复制代码


来看一下效果,将系统设置为浅色外观:



然后将系统设置为深色外观:

页面已经加载了对应深色主题的样式:



二、CSS 变量 + 媒体查询


window.matchMedia 方法可以用来查询指定的媒体查询字符串解析后的结果。结合 CSS 变量和 matchMedia 的查询结果,设置对应的 CSS 主题颜色。该方法更灵活,可以单独抽离主题色进行适配。

CSS 变量的作用域与 CSS 的"层叠"规则一致,优先级最高的声明生效。所以当 body 上存在 "dark" 类名时,:root .dark 会生效,否则 :root 生效。


.article {   color: var(--text-color, #eee);   background: var(--text-background, #fff); :root {   --text-color: #000;   --text-background: #fff; :root .dark {   --text-color: #fff;   --text-background: #000; 
复制代码


使用 matchMedia 匹配主题媒体,深色模式匹配 (prefers-color-scheme: dark) ,浅色模式匹配 (prefers-color-scheme: light) 。


监听主题模式,深色模式时为 body 添加类名 dark,根据 CSS 变量的响应式布局特点,自动生效 dark 类名下的 CSS。


const darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)'); // 判断是否匹配深色模式 if (darkMode && darkMode.matches) {   document.body.classList.add('dark'); // 监听主题切换事件 darkMode && darkMode.addEventListener('change', e => {   if (e.matches) {     document.body.classList.add('dark');   } else {     document.body.classList.remove('dark');    } });  
复制代码




那么,针对不支持 CSS 变量的 IE 浏览器怎么办呢?不做兼容性处理的话那页面可能就是一团糟了。所以我们需要针对不兼容的浏览器做一些兜底处理,这里我们可以在 webpack 等构建工具中借助 post-css 的 postcss-css-variables 插件来自动解析 CSS 变量对应的色值,并在原始 CSS 定义之上添加一条新的 CSS 样式,做到对不支持 CSS 变量浏览器的兼容。


用法如下:


// 根目录 postcss.config.js module.exports = {   plugins: {     "postcss-css-variables": {       preserve: true, // 保留 var() 定义       preserveInjectedVariables: false, // 去除其他模块的重复变量       variables: require("./page.json"), // CSS 变量,可以支持多个     }   } }; 
复制代码



项目实践


现在的 Web、App 项目大都引用第三方开源组件库,组件库一般会使用 Sass、Less 等 CSS 预处理器定义颜色变量作为组件的基础色值,并单独抽离为配置文件。所以,项目使用组件库时可以根据修改基础色值来自定义主题。那么针对项目的深色模式适配方案也一样,主要分为三步:一、组件库深浅色主题 适配;二、项目中深浅色的颜色适配;三、 完成 CSS 变量到页面的注入。


组件库样式、自定义样式适配


如果第三方组件本身支持多主题或者深色模式,可以直接按说明给组件设置对应主题模式;如果第三方组件库不支持的话,只能用覆盖的方式。这里以 Less 为例进行简单实例说明:


修改前:

// index.less@white: #fff; // 颜色预定义@background-color: @white;// 组件样式 panel.less.panel-background-color {  background-color: @background-color; // 组件中使用 less 变量定义颜色样式}
复制代码


新增两个 js 或者 JSON 文件,分别定义深浅模式下的 CSS 变量,并命名为 light-theme1.js、dark-theme1.js 他们并不会影响组件的样式,只是便于后期注入到全局 style 中。


修改后:

// 浅色主题文件 light-theme1.js const bgColor = '#fff';// 颜色预定义 module.exports = {   "--background-color": bgColor; // 深色主题文件 dark-theme1.js const bgColor = '#000';// 颜色预定义 module.exports = {   "--background-color": bgColor; 
复制代码


// 组件样式 panel.less .panel-background-color {   background-color: var(--background-color); //组件中颜色样式 
复制代码


CSS 变量支持第二参数,当变量不存在或者未注册成功时,可以为其设置默认值,优化如下:


// 组件样式 panel.less .panel-background-color {   background-color: var(--background-color, @background-color); // 组件中颜色样式,其中 @background-color 代表修改前组件的背景颜色变量,这里设其为默认值,在适配不成功情况下,可以保持适配前的样式。 
复制代码


项目才是真正使用组件的地方,并且项目本身也有很多自定义 CSS 的颜色样式,需要做与组件库类似的处理,结果也会得到两个 js/json 文件,分别命名为 light-theme2.js、dark-theme2.js。


CSS 注入


在页面渲染前,需要把定义深浅样式的 CSS 变量注入到页面。


以上两步得到了四个文件,合并浅色样式文件 light-theme1.js 和 light-theme2.js  得到 light-theme.js,合并深色样式文件 dark-theme1.js 和 dark-theme2.js 得到 dark-theme.js,最后把 light-theme.js、dark-theme.js 两个文件注入到页面中,注入脚本如下:


import lightTheme from './light-theme'; import darkTheme from './dark-theme'; // 创建一个 style 元素,用于插入 css 定义 const createStyle = (content) => {   const style = document.createElement('style');    style.type = 'text/css';   style.innerHTML = content;    document.getElementsByTagName("script")[0].parentNode.appendChild(style); // 在 body 标签中定义 css 变量 const createCssStyle = () => {   const lightThemeStr = Object.keys(lightTheme).map(key => key + ':' +       lightTheme[key]).join(';');   const darkThemeStr = Object.keys(darkTheme).map(key => key + ':' + darkTheme[key]).join(';');   const lightContent = `body{${lightThemeStr}}`; // 浅色模式 CSS 变量定义   const darkContent = `body.dark{${darkThemeStr}}`; // 深色模式 CSS 变量定义   createStyle(lightContent);   createStyle(darkContent);   isDarkSchemePreference(); }; 
复制代码


注入完成后,项目页面中就有了 CSS 变量定义,包括浅色模式 CSS 变量定义和深色模式 CSS 变量定义,具体哪一个生效,就可以根据上面提到的两种适配方案给 body 添加 class 来控制。默认时浅色模式生效,添加 dark 类名时,深色模式会生效。至此就实现了一套完整的深色模式适配方案。


native 深色适配

iOS


在 iOS 系统中,开发者从颜色和图片两个方面来进行适配,我们不需要关心切换模式后该怎么操作,因为这些都由系统帮我们实现。颜色的适配,需要使用系统提供的 API,在回调用中不同的模式下分别设置颜色,而图片的适配,需要在 XCode 的 工具栏中 Appearances 下选择 Any,Dark,在同一名称资源的配置下分别添加图片资源。当切换深色模式时,系统会根据适配的颜色和图片资源进行查找和自动切换对应模式下的颜色和资源文件。


Android


安卓在 Android 10(API 级别 29)及更高版本中提供深色主题背景,可以通过以下三种方法启用深色主题背景:


  • 使用系统设置(Settings -> Display -> Theme)启用深色主题背景

  • 使用"快捷设置"图块,从通知托盘中切换主题背景(启用后)

  • 在 Pixel 设备上,选择"省电模式"将同时启用深色主题背景,其他原始设备制造商 (OEM) 不一定支持这种行为


在应用中支持深色主题背景


如要支持深色主题背景,必须将应用的主题背景(通常可在 res/values/styles.xml 中找到)设置为继承 DayNight 主题背景:

<style name="AppTheme" parent="Theme.AppCompat.DayNight"> 
复制代码

还可以使用 MaterialComponent  的深色主题背景:


<style name="AppTheme" parent="Theme.MaterialComponents.DayNight"> 
复制代码

这会将应用的主要主题背景与系统控制的夜间模式标记相关联,并将应用的默认主题背景设置为深色主题背景(如果已启用)。


主题背景和样式


主题背景和样式应避免使用旨在于浅色主题背景下使用的硬编码颜色或图标,您应改用主题背景属性(首选)或适合在夜间使用的资源,以下是需要了解的两个最重要的主题背景属性:


  • ?android:attr/textColorPrimary 这是一种通用型文本颜色,它在浅色主题背景下接近于黑色,在深色主题背景下接近于白色,该颜色包含一个停用状态。

  • ?attr/colorControlNormal 一种通用图标颜色,该颜色包含一个停用状态。


Flutter


这里以 Flutter 为例,简单介绍下跨平台开发框架如何适配深色模式。Flutter 定义主题有两种方式:全局主题或使用 Theme 来定义应用程序局部的颜色和字体样式。


全局主题


全局主题就是由应用程序根 MaterialAPP 创建的 Theme。为了在整个应用程序中共享包含颜色和字体样式的主题,我们可以提供 ThemeData 给 Material 的构造函数。Theme 指定的是浅色模式,darkTheme 指定的是深色模式,程序会根据系统设定的暗黑模式自动匹配模式。

new MaterialApp(   title: title,   theme: new ThemeData(      brightness: Brightness.light,      primaryColor: Colors.lightBlue[800],      accentColor: Colors.cyan[600] ,   ),   darkTheme: new ThemeData(      brightness: Brightness.dark,      primaryColor: Colors.lightGreen[800] ,      accentColor: Colors.cyan[200],   ), ); 
复制代码

局部主题


如果我们想在应用程序的一部分中覆盖应用程序的全局的主题,我们可以将要覆盖的部分封装在一个 Theme 的 Widget 中,有 2 种方法可解决:创建特有的 ThemeData 或扩展父主题。

创建特有的 ThemeData

如果我们不想继承任何应用程序的颜色或字体样式,我们可以通过 new ThemeData() 创建一个实例并将其传递给 Theme Widget。

// Create a unique theme with "new ThemeData" new Theme(   data: new ThemeData(     accentColor: Colors.yellow,   ),   child: new FloatingActionButton(     onPressed: () {},     child: new Icon(Icons.add),   ), ); 
复制代码


扩展父主题


扩展父主题时无需覆盖所有的主题属性,我们可以通过使用 copyWith 方法来实现。

// Find and Extend the parent theme using "copyWith". Please see the next section for more info on `Theme.of`. new Theme(   data: Theme.of(context).copyWith(accentColor: Colors.yellow),   child: new FloatingActionButton(     onPressed: null,     child: new Icon(Icons.add),   ), ); 
复制代码

使用主题

我们可以在 Widget 的 build 方法中通过 Theme.of(context) 函数使用自定义的主题。

new Container(   color: Theme.of(context).accentColor,   child: new Text(     'Text with a background color',     style: Theme.of(context).textTheme.title,   ), ); 
复制代码

渲染效果 如下 :




总结


以上分别介绍了在 App 应用中对 H5 页面和客户端的深色模式适配方案,当然其中 H5 的方案页同样适应于 PC 端。使用前一定要确保你的系统和浏览器是兼容深色模式的,不然就没有效果了呢。本篇只简单介绍了几种方案,欢迎有更好想法的小伙伴一起讨论~


头图:Unsplash

作者:玲玲/参宿

原文:https://mp.weixin.qq.com/s/XVckb7sw2_YVmhd4986qng

原文:深色模式适配指南

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-04-09 21:064413

评论

发布
暂无评论
发现更多内容

Android 横向ListView实现

android 程序员 移动开发

Android 程序员不得不收藏的 90+ 个人博客(持续更新

android 程序员 移动开发

Android 开发中,与屏幕有关的三个小众知识

android 程序员 移动开发

Android 性能优化:手把手带你全面了解内存泄露

android 程序员 移动开发

基于 KubeVela 的 GitOps 交付

阿里巴巴云原生

阿里云 Kubernetes 云原生 gitops KubeVela

Android 设置默认Launcher

android 程序员 移动开发

Android 调用相机拍照,适配到Android 10

android 程序员 移动开发

android 图表基本属性方法设置

android 程序员 移动开发

Android 多渠道打包配置

android 程序员 移动开发

Android 序列化(Serializable和Parcelable)

android 程序员 移动开发

Android 开发,你遇上 Emoji 头疼吗?

android 程序员 移动开发

Android 自定义软键盘实现 数字九宫格

android 程序员 移动开发

Android 图像处理

android 程序员 移动开发

android 定位到输入的地址

android 程序员 移动开发

Android 开发规范(完结版)

android 程序员 移动开发

Android 原生控件ViewFlipper实现淘宝头条垂直滚动广告条

android 程序员 移动开发

Android 抖音爆红的口红挑战爬坑总结

android 程序员 移动开发

android 实现 搜索保存历史记录功能

android 程序员 移动开发

Android 开发艺术探索笔记(1)

android 程序员 移动开发

Android 开发艺术探索笔记(21)

android 程序员 移动开发

Android 开发面试中,面试过最喜欢问那些问题?

android 程序员 移动开发

android 方式实现imageview圆角

android 程序员 移动开发

Android 开发者想咸鱼翻身并不是难事,只要你掌握这些……

android 程序员 移动开发

Android 拍照:如何使用已有相机应用捕获图片(Taking Photos Simply)

android 程序员 移动开发

Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架

android 程序员 移动开发

Android 自定义软键盘实现 数字九宫格(1)

android 程序员 移动开发

Android 这座山,我一定要爬到顶

android 程序员 移动开发

Android 性能监控系列一(原理篇)

android 程序员 移动开发

Android 实现系统深度休眠笔记

android 程序员 移动开发

Android 获取设备信息

android 程序员 移动开发

Android 使用讯飞语音SDK

android 程序员 移动开发

深色模式适配指南_语言 & 开发_政采云前端团队_InfoQ精选文章