为博客适配深色模式。
<封面摄于江苏·南京的总统府,小潘同学就是在这被猫猫挠了。>
滤镜反色 最偷懒的方式就是用 CSS3
的滤镜对整个页面进行反色,只需一行代码。
html [theme='dark-mode' ] { filter : invert (1 ) hue-rotate (180deg ); }
使用CSS3滤镜进行反色
这时候会发现有个小问题,就是图片也会被反色,形成类似胶卷底片的效果。那么只需将图片再反色回来即可。
html [theme='dark-mode' ] img { filter : invert (1 ) hue-rotate (180deg ); }
适配图片
分分钟搞定,但是似乎非黑即白,还不够细腻。
一行代码使用CSS的黑暗模式
媒体查询和样式变量 媒体查询 (@media) 中的 prefers-color-scheme
用于检测用户是否有将系统的主题色设置为浅色或者深色,配合 CSS Variable
我们可以为浅色或深色模式单独匹配样式,实现更细腻的深色模式。
prefers-color-scheme
有以下三个值:
light: 检测出系统处于 浅色 模式
dark: 检测出系统处于 深色 模式
no-preference: 并未检测出系统所处的颜色模式,可能是出于系统不支持或者被隐私保护拦截等因素
使用方法如下:
<div class ="background" > <span class ="text" > </span > </div >
@media (prefers-color-scheme : dark) { .background { background : #333333 ; } .text { color : #ffffff ; } }@media (prefers-color-scheme : light) { .background { background : #ffffff ; } .text { color : #333333 ; } }
兼容性如下,不过对于不支持该属性的浏览器也能忽略该属性从而向下兼容。
浏览器兼容性
通过该属性,我们即可检测出系统当前所处的颜色模式,并对样式进行单独配置。
MDN: prefers-color-scheme
CSS Variable 通过 prefers-color-scheme
匹配颜色模式,但是为所有元素都单独定制两套颜色样式显然很麻烦,后期也难以维护。
一个页面内的颜色方案通常比较统一,也就那么几种颜色,所以我们可以通过 CSS变量 (CSS Variable) 为颜色进行规整,快速切换颜色模式。
:root { --color -background : #ffffff ; --color -text: #33333d ; }@media (prefers-color-scheme : dark) { :root { --color -background : #1e2128 ; --color -text: #dddddd ; } }.background { background : var (--color-background); }.text { color : var (--color-text); }
手动切换 通过媒体查询和样式变量我们可以跟随系统设置,实现自动切换颜色模式。但是这样还不够友好,有以下场景:
浏览器不支持 prefers-color-scheme
,无法自动切换颜色模式
系统处于深色模式状态,但是我又想让该网页单独显示浅色模式
所以还需要添加一个按钮,让用户手动切换颜色模式。
HTML Attribute 实现用户手动切换颜色模式,首先需要一个“全局变量”来保存当前颜色模式,并且让 CSS
识别该“变量”,匹配颜色模式。我们可以直接在 html
标签(根元素)设定一个属性 color-mode
,属性值有 light
和 dark
,可以通过 CSS
的属性选择器直接匹配,用户点击切换按钮时可以通过 JS
直接修改该属性。
<html color-mode ="dark" > <div class ="background" > <span class ="text" > </span > </div > </html >
:root { --color -background : #ffffff ; --color -text: #33333d ; }[color-mode='dark' ] { :root { --color -background : #1e2128 ; --color -text: #dddddd ; } }.background { background : var (--color-background); }.text { color : var (--color-text); }
Stylus hexo-theme-zhaoo
主题使用了 Stylus
预处理器,基于 变量 、函数 等特性可以进一步抽离样式,便于维护。
$color -background = unquote(hexo-config('color .background ') || #ffffff ) $color -background -secondary = unquote(hexo-config('color .background-secondary ') || #f6f8fa ) $color -background -rgb = 255 , 255 , 255 $color -text = unquote(hexo-config('color .text ') || #33333d ) $color -text-secondary = unquote(hexo-config('color .text-secondary ') || #4e4e4e ) $color -text-third = unquote(hexo-config('color .text-third ') || #999999 ) $color -background -dark = unquote(hexo-config('color .background-dark ') || #1e2128 ) $color -background -secondary-dark = unquote(hexo-config('color .background-secondary-dark ') || #1a1d22 ) $color -background -rgb-dark = 30 , 33 , 40 $color -text-dark = unquote(hexo-config('color .text-dark ') || #dddddd ) $color -text-secondary-dark = unquote(hexo-config('color .text-secondary-dark ') || #9899ab ) $color -text-third-dark = unquote(hexo-config('color .text-third-dark ') || #7d8594 )
:root --color -background $color -background --color -background -secondary $color -background -secondary --color -background -rgb $color -background -rgb --color -text $color -text --color -text-secondary $color -text-secondary --color -text-third $color -text-third dark() --color -background $color -background -dark --color -background -secondary $color -background -secondary-dark --color -background -rgb $color -background -rgb-dark --color -text $color -text-dark --color -text-secondary $color -text-secondary-dark --color -text-third $color -text-third-dark@media (prefers-color-scheme dark) :root :not ([color-mode]) dark() [color-mode='dark' ] dark()
触发器 触发器就是一个按钮,点击后修改 html
标签的 color-mode
属性,切换颜色模式。比较简单,直接上代码了:
<i class ="iconfont iconmoono" id ="color-toggle" color-toggle ="light" > </i >
var switchColorMode = function ( ) { if (!document .getElementById('color-toggle' )) return ; document .getElementById('color-toggle' ).addEventListener('click' , function ( ) { var mode = this .getAttribute('color-toggle' ) === 'light' ? 'dark' : 'light' ; document .documentElement.setAttribute(htmlAttribute, mode); }); } switchColorMode();
至此,用户就可以点击按钮手动切换颜色模式了。
缓存状态 该方案还存在问题,跳转页面或刷新页面,颜色模式就会切回到默认。(用户:你***逗我玩呢?)所以我们需要让浏览器缓存用户手动切换的颜色模式,之后加载页面时默认以该模式渲染。背面经环节:前端缓存方案有 cookies
、localStorage
、sessionStorage
、Web SQL
、IndexedDB
……
用最方便的 localStorage
储存用户切换到颜色模式即可,在用户点击按钮后将更新的颜色模式通过 localStorage.setItem
存储,再在页面渲染时通过 localStorage.getItem
获取颜色模式并渲染即可,key
为 color-mode
。
我们还需要解决一个问题,系统自动配置 (媒体查询) 与 用户手动配置 (按钮切换) 之间的同步和冲突问题。例如:1. 在固定时段(晚上或白天),页面渲染时按用户切换的颜色模式加载。2. 在时段改变后(白天变为晚上),页面渲染时按系统颜色模式渲染。
我们只需要再添加一组 key
为 color-mode-media-query
的 localStorage
,缓存媒体查询的颜色模式。渲染时判断 当前媒体查询 与 缓存 是否相等,相等说明处于同一时段,不等说明时段已改变,从而决定渲染方式。
最后,这段 JS
需要添加到 </body>
标签前面加载,不然会闪屏。
完整 JS
代码如下:
!function (window , document ) { var rootElement = document .documentElement; var toggleElement = document .getElementById('color-toggle' ); var highlightElement = document .getElementsByName('highlight-style' ); var modeStorageKey = 'color-mode' ; var mediaQueryStorageKey = 'color-mode-media-query' ; var htmlAttribute = 'color-mode' ; var toggleAttribute = 'color-toggle' ; var getMediaQuery = function ( ) { return window .matchMedia('(prefers-color-scheme: dark)' ).matches ? 'dark' : 'light' ; } var getModeStorage = function ( ) { return localStorage .getItem(modeStorageKey); } var setModeStorage = function (mode ) { localStorage .setItem(modeStorageKey, mode); } var getMediaQueryStorage = function ( ) { return localStorage .getItem(mediaQueryStorageKey); } var setMediaQueryStorage = function (mode ) { localStorage .setItem(mediaQueryStorageKey, mode); } var setColorMode = function (mode ) { rootElement.setAttribute(htmlAttribute, mode); setModeStorage(mode); } var setIcon = function (mode ) { if (!toggleElement) return ; var addIconName = mode === 'light' ? 'iconmoono' : 'iconsuno' ; var removeIconName = mode === 'light' ? 'iconsuno' : 'iconmoono' ; toggleElement.classList.remove(removeIconName); toggleElement.classList.add(addIconName); toggleElement.setAttribute(toggleAttribute, mode); } var setHighlightStyle = function (mode ) { highlightElement.forEach(function (item ) { item.disabled = !(item.getAttribute('mode' ) === mode); }); } var loadColorMode = function (mode ) { var mode = mode || getModeStorage() || getMediaQuery(); if (getMediaQuery() === getMediaQueryStorage()) { mode = getModeStorage(); } else { mode = getMediaQuery(); setMediaQueryStorage(mode); } setColorMode(mode); setIcon(mode); setHighlightStyle(mode); } var switchColorMode = function ( ) { if (!toggleElement) return ; toggleElement.addEventListener('click' , function ( ) { var mode = this .getAttribute(toggleAttribute) === 'light' ? 'dark' : 'light' ; setColorMode(mode); setIcon(mode); setHighlightStyle(mode); }); } loadColorMode(); switchColorMode(); }(window , document );
[你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持] (https://blog.skk.moe/post/hello-darkmode-my-old-friend/ )
适配特殊样式 大部分元素可以通过 CSS
属性直接匹配样式,但是仍有一部分元素需要通过“特殊”方法进行处理。
PNG 适配首屏云朵,其实就是张 PNG
图片,要是 PNG
能用 CSS
控制颜色就好了。
适配PNG
从张鑫旭大佬的博客找到了解决方案,可以用 CSS3
滤镜中的投影 (filter: drop-shadow) 进行上色。但是需要做一些处理,将原图隐藏而阴影显示,其实只要将原图偏移出视口,再将阴影偏移回正确位置即可。
img { position : absolute; width : 100vw ; left : -100vw ; filter : drop-shadow (var (--color-background) 100vw 0px ); }
PNG格式小图标的CSS任意颜色赋色技术
SVG 接下来适配首屏波浪,小图标等 SVG
内容。
适配SVG
首先 SVG
也是可以用上面提到的 filter: drop-shadow
进行上色的。
另外,也可以用 SVG
标签中的 fill
属性进行赋色,如下:
<svg class ="preview-waves" xmlns ="http://www.w3.org/2000/svg" xmlns:xlink ="http://www.w3.org/1999/xlink" viewBox ="0 24 150 28" preserveAspectRatio ="none" shape-rendering ="auto" > <defs > <path id ="gentle-wave" d ="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" /> </defs > <g class ="preview-parallax" > <use xlink:href ="#gentle-wave" x ="48" y ="0" fill ="rgba(var(--color-background-rgb), 0.7" /> //通过 fill 属性进行赋色 <use xlink:href ="#gentle-wave" x ="48" y ="3" fill ="rgba(var(--color-background-rgb), 0.5)" /> //通过 fill 属性进行赋色 <use xlink:href ="#gentle-wave" x ="48" y ="5" fill ="rgba(var(--color-background-rgb), 0.3)" /> //通过 fill 属性进行赋色 <use xlink:href ="#gentle-wave" x ="48" y ="7" fill ="rgb(var(--color-background-rgb))" /> //通过 fill 属性进行赋色 </g > </svg >
rgba 对应含有透明通道的颜色 (rgba),如果在 rgba()
中包裹 CSS变量,stylus
会解析出错,这应该是 stylus
的一个 bug
。我们可以用 stylus
中的 @css
指令解决,被包裹在 @css
中的内容将不会被 stylus
解析,而是直接以 CSS
的形式输出。如下:
@css { .menu { background-color : rgba (var (--color-background-rgb), 0.7 ); } .navbar { background-color : rgba (var (--color-background-rgb), 0.8 )s; } }
highlight 代码高亮也需要做适配,可以引入浅色和深色两套代码高亮样式,默认用 disabled
属性禁用,然后在页面渲染时根据颜色模式开启对应的代码高亮样式。如下:
<% if(theme.highlight.enable){ %> <% if(theme.vendors.highlight_css){ %> <% for (i in theme.highlight.style) { %> <% style = theme.highlight.style[i].toLowerCase().replace(/(?<!([0-9]))\s(?!([0-9]))/g, '-').replace(/\s/g, '') %> //引入多套样式 <%- css({href: theme.vendors.highlight_css + style + '.min.css', name: 'highlight-style', mode: i}) %> <% } %> <% }else{ %> <%- css('lib/highlight/a11y-dark.css')%> <% }} %>
var setHighlightStyle = function (mode ) { highlightElement.forEach(function (item ) { item.disabled = !(item.getAttribute('mode' ) === mode); }); }