web主题适配方案指北
前置知识
在这里了解实现网页主题切换的相关知识。
CSS 变量
要实现主题切换需要了解一点 css 自定义属性。当然,本文还提供了其他实现方式,为了不给您接下来的阅读带来阻碍,先了解它。
变量的声明
声明变量时,变量名前要加上 --
,例如 --example: 20px
即是一个 css 声明语句。意思是将 20px 赋值给 --example
变量。所以 css 变量又叫做 css 自定义属性。在 css 的任何选择器中都可以声明 css 变量,通常将所有 css 变量声明在 :root
选择器中,以便在后文引用。
:root
选择器匹配文档树的根元素。对于 HTML 文档来说,:root
表示<html>
元素,除了优先级更高之外,与 html 选择器相同。
这里有一个例子:
:root {
--example: 20px
}
等价于:
html {
--example: 20px
}
var()
函数
通过
var()
函数读取变量。例如:var(--example)
会返回--example
所对应的值。var()
函数还可以使用第二个参数,表示变量的默认值。即var()
从左向右读取值,如果第一个变量不存在,就读取第二个。例如:var(--example, 40px)
, 如果--example
不存在,将返回 40px。当然第二个参数同样可以使用 css 自定义属性而不是具体的值,例如:var(--example1, --example2)
。
试着写一个简单的例子:
<body>
<section id="container">
<div class="item1"></div>
<div class="item2"></div>
<div class="item3"></div>
<div class="item4"></div>
</section>
</body>
#container {
width: 400px;
height: 150px;
background-color: #ffeead;
border: 1px solid #666;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
#container > div {
width: 70px;
height: 50px;
}
#container div:nth-child(2n) {
background-color: lightgreen;
}
#container div:nth-child(2n+1) {
background-color: lightpink;
}
接下来使用 css 变量,修改部分代码:
+ :root {
+ --green: lightgreen;
+ --lightpink: lightpink;
+ }
#container {
width: 400px;
height: 150px;
background-color: #ffeead;
border: 1px solid #666;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
#container > div {
width: 70px;
height: 50px;
}
#container div:nth-child(2n) {
- background-color: lightgreen;
+ background-color: var(--green);
}
#container div:nth-child(2n+1) {
- background-color: lightpink;
+ background-color: var(--lightpink);
}
在上面的代码片段中,使用 css 变量替换原来的颜色值,效果依然相同。css 变量还有许多其他相关知识,本文只介绍这些内容,只需要掌握这些,就能实现完整的暗色模式了。
兼容性
由图可见,如果不需要兼容 IE 浏览器,可以放心使用它。要兼容 IE 也有办法,postcss-css-variables 插件将 CSS 自定义属性 (CSS 变量) 语法转换为静态表示形式,具体使用方式本文不展开了,点我 查看详细的官方使用教程。
跟随系统设置
使用 css 媒体查询匹配系统设置。此处简单将 prefers-color-scheme
CSS 媒体特性作介绍,参考MDN。prefers-color-scheme
用于检测用户是否有将系统的主题色设置为亮色或者暗色。
// 表示系统未得知用户在这方面的选项
@media (prefers-color-scheme: light) { }
// 用户选择选择使用浅色主题的系统界面
@media (prefers-color-scheme: dark) { }
// 用户选择选择使用暗色主题的系统界面
@media (prefers-color-scheme: no-preference) { }
使用 matchedMedia()
匹配系统设置。
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
if (prefersDarkScheme.matches) {
document.body.classList.add('dark-theme');
} else {
document.body.classList.remove('dark-theme');
}
使用这种方式有一个缺点:当这段 JavaScript 在 CSS 之后执行时,可能会出现一个闪屏的现象。
实现方式
有多种方式实现暗色模式,逐一介绍。
使用 css 变量
利用 var()
函数的特性实现浅色模式和暗色模式之间的切换。看下面的代码片段:
:root{
--default-color: #555,
--color: var(--dark-var, --default-var)
}
body{
color: var(--color)
}
body 最终得到的 color 为 #555
,如果声明了 —-dark-var
变量,body 得到的 color 的值将为 —-dark-var
的值,可以通过 JavaScript 将变量 —-dark-var
插入到 css 。
结合媒体查询实现跟随系统切换到深色模式:
@media (prefers-color-scheme: dark) {
:root {
--dark-color: #fff
}
}
使用 html 属性和 css 变量
给 html 标签动态添加 theme 属性,像这样 <html theme="dark">
。
:root{
--color: #555
}
:root[theme="dark"]{
--color: #fff
}
body {
color: var(--color)
}
当 html 的属性 theme 的值不为 dark 时,var()
函数读取的是 :root{}
内的自定义属性,反之,则读取的是 :root[theme="dark"]{}
里的自定义属性。
同样也可以结合媒体查询,实现跟随系统的效果:
@media (prefers-color-scheme: dark) {
:root {
--color: #fff
}
}
这种方式的好处是扩展性更强,代码量更少,代码维护也更加方便。不仅仅可以切换到深色模式,还可以切换到其他主题。例如给 html 的 theme 属性设置其他值 <html theme="pink">
,只需要添加下面这段 css:
:root[theme="pink"]{
--color: pink
// ...
}
通过 JavaScript 给 html 的 theme 属性赋值 pink ,就能切换到该主题了。
使用 class 和 css 变量
类似的思路我们可以给 html 添加一个 class 而不是添加 theme 属性实现。
::root{
--color: #222;
}
:root.dark{
--color: #eee;
}
const btn = document.querySelector('.toggle');
btn.addEventListener('click', function() {
document.html.classList.toggle('dark');
})
用户设置深色模式的操作系统并不意味着他们希望将深色模式应用到网站上。结合媒体查询还可以实现:不管系统设置如何,覆盖深色模式。
body {
--color: #222;
}
body.dark{
--color: #eee;
}
@media (prefers-color-scheme: dark) {
body {
--color: #eee;
}
body.light {*
*--color: #222;
}
}
使用 class 作为标识
通过 JavaScript 改变 body 上的 class 来决定网站使用的主题。
<body class="dark || light">
const btn = document.querySelector('.toggle');
btn.addEventListener('click', function() {
document.body.classList.toggle('dark');
})
body {
color: #222;
background: #fff;
}
body.dark{
color: #eee;
background: #121212;
}
使用这种方式时,我推荐使用 css 预处理器比如 scss,不然需要做很多重复的劳动。
使用单独的 css 文件
light-theme.css
body {
color: #222;
background: #fff;
}
dark-theme.css
body {
color: #eee;
background: #121212;
}
这时候你可能会有疑问了,如何通过点击切换主题呢?不用担心,非常简单。在引入 css 时这样做:
<head>
<link href="light-theme.css" rel="stylesheet" id="theme-link">
</head>
给link
一个标签一个 ID, 就可以通过 JavaScript 选择它了。
const btn = document.querySelector(".toggle");
const theme = document.querySelector("#theme-link");
btn.addEventListener("click", function() {
if (theme.getAttribute("href") == "light-theme.css") {
theme.href = "dark-theme.css";
} else {
theme.href = "light-theme.css";
}
});
使用 Darkmode.js
GitHub 上有一个开源项目,Darkmode.js,通过mix-blend-mode:difference
达到切换夜间模式的效果,当您的应用或网站比较简单时或许可以尝试它。
npm install darkmode-js
const options = {
bottom: '64px',
right: 'unset',
left: '32px',
time: '0.5s',
mixColor: '#fff',
backgroundColor: '#fff',
buttonColorDark: '#100f2c',
buttonColorLight: '#fff',
saveInCookies: false,
label: '',
autoMatchOsTheme: true
}
const darkmode = new Darkmode(options);
darkmode.showWidget();
具体的原理我已经写了另一篇文章 Darkmode.js 源码解析 介绍它。
使用服务端脚本
以 PHP 为例:
<?php
$themeClass = '';
if (isset($_GET['theme']) && $_GET['theme'] == 'dark') {
$themeClass = 'dark-theme';
}
$themeToggle = ($themeClass == 'dark-theme') ? 'light' : 'dark';
?>
<!DOCTYPE html>
<html lang="en">
<!-- etc. -->
<body class="<?php echo $themeClass; ?>">
<a href="?theme=<?php echo $themeToggle; ?>">Toggle Dark Mode</a>
<!-- etc. -->
</body>
</html>
实现切换单独的 css 文件:
<?php
$themeStyleSheet = 'light-theme.css';
if (isset($_GET['theme']) && $_GET['theme'] == 'dark') {
$themeStyleSheet = 'dark-theme.css';
}
$themeToggle = ($themeStyleSheet == 'dark-theme.css') ? 'light' : 'dark';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<!-- etc. -->
<link href="<?php echo $themeStyleSheet; ?>" rel="stylesheet">
</head>
<body>
<a href="?theme=<?php echo $themeToggle; ?>">Toggle Dark Mode</a>
<!-- etc. -->
</body>
</html>
这种方法有一个明显的缺点:需要刷新页面才能进行切换。但是,像这样的服务器端解决方案对于跨页面重新加载持久化用户的主题选择非常有用。
选择哪种方法
如果不需要兼容 IE,我推荐您使用 html 属性和 css 变量。或者,将不同方式结合使用也未尝不可。总之,应该根据您的项目需求选用更加合适的方法。
细节处理
图片
大多数网站不仅仅只有文本,还有图片。使用 filter 来处理图片。
img.dark{
filter: brightness(.8) contrast(1.2);
}
brightness 使图像看起来更亮或更暗
contrast 调整图像的对比度
阴影
不要忽略暗色模式下的阴影(box-shadow),浅色模式的阴影在深色模式下可能无法显现。应该将这些细节也抽分,即应该为深色模式也提供一套阴影。
css 变量粒度
在抽离或者定义 css 变量时应该尽可能掌控粒度。粒度太大不好掌控细节,太小会增加代码量,不易维护。总之,视情况而定还有您的经验。
过渡效果
不要使用 transition
从暗色模式切换到浅色模式,或者从浅色模式切换到暗色模式,需要一个过渡动效,这能改善体验。您或许立即想到了transition
,千万不要这么做。下面来看看这样做时有什么不足。
如果利用 css transition
属性,在您切换模式时应该给顶层元素一个 class,例如 mode-change
,在切换完成之后再将它移除。scss 代码大至如下:
.mode-change {
selector1,
selector2,
// ......{
transition: all 0.3s cubic-bezier(1, 0.05, 0.29, 0.99);
}
}
这是因为:使用 transition
,您需要给所有元素添加过渡效果。这将带来巨大的 CPU 开销,我已经测试过了。
障眼法
如果您常写 css 特效,障眼法真是个巧妙的方法,利用它实现很多难以实现的效果。现在,甚至用它来优化性能。不妨回归最初,目的是什么?给用户一个过渡效果,看起来不那么生硬。思路:使用 css 伪元素创建一个带有过渡效果的蒙层,看看如和实现它吧。
在切换时给 body 添加一个 class,在浅色切换到深色时 class 为 light-to-dark
,反之,为 dark-to-light
。
代码较长,点击展开
$mode: () !default;
$mode: map-merge(
(
light-bg: #fff,
dark-bg: #252528,
),
$mode
);
$light-bg: map-get($mode, light-bg);
$dark-bg: map-get($mode, dark-bg);
.dark-to-light:after {
content: '';
width: 100vw;
height: 100vh;
position: fixed;
z-index: 99999;
left: 0;
top: 0;
margin-left: 0;
background-color: $dark-bg;
opacity: 0.7;
animation: toLight 1s linear 0s forwards;
// pointer-events: none;
}
.light-to-dark:after {
content: '';
width: 100vw;
height: 100vh;
position: fixed;
z-index: 99999;
left: 0;
top: 0;
margin-left: 0;
background-color: $light-bg;
opacity: 0.7;
animation: toDark 1s linear 0s forwards;
// pointer-events: none;
}
@keyframes toLight {
0% {
background-color: $dark-bg;
opacity: 0.7;
}
100% {
background-color: $light-bg;
opacity: 0;
}
}
@keyframes toDark {
0% {
background-color: $light-bg;
opacity: 0.7;
}
100% {
background-color: $dark-bg;
opacity: 0;
}
}
在切换模式时,将会在页面顶层展示带有对应过渡效果的蒙层。在过渡效果显示时,用户的鼠标无法点击页面的元素,这样做实现了类似防抖的效果。如果想移除这个效果,只需给蒙层加上 pointer-events: none;
。最终的效果就是:即使频繁切换模式,也听不见 CPU 狂飙。
储存状态
仅仅通过点击按钮切换主题还不够,应该将主题保存起来。否则,用户刷新页面或者再此进入页面将回到初始主题。在切换主题或者初始化时都应该使用状态储存。
JavaScript LocalStorage
在切换皮肤时应该将当前主题存到 LocalStorage。
localStorage.setItem("theme", "dark" || "light");
localStorage.getItem("theme");
PHP Cookie
$_COOKIE['theme'] == 'dark'
暗色模式设计最佳实践
我对设计和色彩不够专业,我找了一篇优秀的文章,您可以点此查看。
参考资料:
web主题适配方案指北的更多相关文章
- 移动web开发适配方案之Rem
移动端为什么要做适配 移动端相对PC端来说大部分浏览器内核都是基于Webkit的,所以大部分都支持CSS3的最新语法.但是由于手机的屏幕尺寸和分辨率都不太一样(尤其是安卓),所以不得不对不同分辨率的手 ...
- 移动web屏幕适配方案
刚进部门就被拉去趟移动端Web的浑水,视觉稿是按照640px设计的.那如何做屏幕适配呢?当然想到的第一方法就是问前辈了,问他们之前怎么做的,前辈说直接按视觉稿来,我说640太大了,他说除以2啊,按32 ...
- 移动端Web页面适配方案
概念理解 viewport视口 visual viewport 可见视口,设备屏幕的宽度 windw.innerWidth/Height layout viewport 布局视口,DOM宽度 doc ...
- 移动端 移动web屏幕适配方案 随不同宽度的屏幕而改变
链接地址1:http://www.cnblogs.com/zjzhome/p/4802157.html 链接地址2:http://www.html-js.com/article/Mobile-term ...
- 再谈移动端Web屏幕适配
一个多月前水了一篇移动web屏幕适配方案,当时噼里啪啦的写了一通,自我感觉甚是良好.不过最近又有一些新的想法,和之前的有一些不同. 先说一下淘宝的方案,感觉现在好多的适配方案都是受了它的影响,上周六看 ...
- Web 端屏幕适配方案
基础知识 像素相关 1.像素 :像素是屏幕显示最小的单位. 2.设备像素 :设备像素又称物理像素(physical pixel),设备能控制显示的最小单位,我们可以把这些像素看作成显示器上一个个的点. ...
- WEB安全漏洞挖掘向入坑指北
这个指北不会给出太多的网站和方向建议,因为博主相信读者能够从一个点从而了解全局,初期的时候就丢一大堆安全网址导航只会浇灭人的热情,而且我也不适合传道授业解惑hhh 安全论坛: 先知社区 freebuf ...
- [转] iOS开发者的Weex伪最佳实践指北
[From] http://www.cocoachina.com/ios/20170601/19404.html 引子 这篇文章是笔者近期关于Weex在iOS端的一些研究和实践心得,和大家一起分享分享 ...
- webapp:移动端高清、多屏适配方案(zz)
来源: http://sentsin.com/web/1212.html 移动端高清.多屏适配方案 背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉 ...
随机推荐
- Java基础加强笔记——测试、反射、注解
目录 1. Junit单元测试 2. 反射 3. 注解 Junit单元测试: 测试分类: 1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值. 2. 白盒测试:需要写代码的.关注程序具 ...
- 【转载】 jmeter 命令行模式(非GUI)运行脚本,察看结果树结果为空,解决办法
转载地址:https://www.cnblogs.com/canglongdao/p/12636403.html jmeter 命令行模式(非GUI)运行脚本,察看结果树结果为空,解决办法: jmet ...
- 02_HTML01
学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"软件测试"获取视频和教程资料! b站在线视频 HTML ...
- Alink漫谈(十五) :多层感知机 之 迭代优化
Alink漫谈(十五) :多层感知机 之 迭代优化 目录 Alink漫谈(十五) :多层感知机 之 迭代优化 0x00 摘要 0x01 前文回顾 1.1 基本概念 1.2 误差反向传播算法 1.3 总 ...
- PHP dechex() 函数
实例 把十进制转换为十六进制: <?phpecho dechex("30") . "<br>";echo dechex("10&qu ...
- luogu 3158 [CQOI2011]放棋子
时隔多日 我又来挑战这道dp. 几个月前给写自闭了.几个月后再来. 首先一个我们能列出来的状态 是以行为转移的 f[i]表示前i行...但是会发现此时列我们控制不了 且棋子的颜色,个数我们也要放到状态 ...
- 解决 IntelliJ IDEA占用C盘过大空间问题
原文地址:https://blog.csdn.net/weixin_44449518/article/details/103334235 问题描述: 在保证其他软件缓存不影响C盘可用空间的基础上,当我 ...
- CSS-flex|gird 布局
网页布局.css *{ box-sizing: border-box; } /* flex弹性布局 */ #flex-container { display: flex; flex-direction ...
- Chrome划词翻译-Saladict
Saladict 沙拉查词是一款专业划词翻译扩展,为交叉阅读而生.大量权威词典涵盖中英日韩法德西语,支持复杂的 划词操作.网页翻译.生词本.PDF,以及 Vimium 全键盘操作 . 迄今为止最好用的 ...
- jQuery 综合练习ToDoList
<div class="header"> <section> <label for="">ToDoList</labe ...