问题解决|使用Less变量和媒体查询实现深浅色模式适配
使用Less变量简化css写法,并不影响css变量和媒体查询在深浅模式切换时的效果
分析问题
首先要清楚问题的来源是什么,使用媒体查询api可以根据深浅色为css变量赋予不同的值,而Less(或是scss)不是原生被浏览器所支持,运行时需要先转为为原生css,这一步也就是编译,有点类似ts和js的关系。这里采用的是vite创建的Vue3项目
而Less/Scss在编译的过程中,进行的是完全的字段替换,这也就意味着在运行之前css已经被固定,无法在运行的过程中动态修改,当然也不支持媒体查询的所有属性,但一些还是支持的哈,比如设备类型,最大宽度什么的,这里深浅色默认是不支持的。
问题出现
这里首先我是想办法完全使用Less的,也就是媒体查询+正常定义变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @primary: #1890ff; @bg: #ffffff; @font: #333333; @warning: #faad14; @success: #52c41a; @error: #f5222d; @info: #1890ff; @disabled: #bfbfbf; @link: #1890ff; @hover: #001764;
@media (prefers-color-scheme: dark) { :root { @primary: #718dff; @bg: #1d1e21; @font: #ffffff; @warning: #faad14; @success: #a8ff7d; @error: #f5222d; @info: #80c1ff; @disabled: #4e4e4e; @link: #a0b5ff; @hover: #d5e8ff; } }
|
但是实际测试中发现所编译出的css根本没有@media的字样,所以又是inline编译了,导致完全写死,不可能在运行中改变
问题解决
因为测试Less写法一直有问题,而原生css是可以完美解决的,为了既保留Less的简便的变量写法,又解决变量无法修改的问题,因此利用字段替换编译的特性,我们可以将Less变量赋值为css变量,这样当编译之后所生成的css,依然是变量形式储存内容,具体代码可以参考:
colors.less
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| :root { --primary: #1890ff; --bg: #ffffff; --font: #333333; --warning: #faad14; --success: #52c41a; --error: #f5222d; --info: #1890ff; --disabled: #bfbfbf; --link: #1890ff; --hover: #001764; }
@primary: var(--primary); @bg: var(--bg); @font: var(--font); @warning: var(--warning); @success: var(--success); @error: var(--error); @info: var(--info); @disabled: var(--disabled); @link: var(--link); @hover: var(--hover);
@media (prefers-color-scheme: dark) { :root { --primary: #718dff; --bg: #1d1e21; --font: #ffffff; --warning: #faad14; --success: #a8ff7d; --error: #f5222d; --info: #80c1ff; --disabled: #4e4e4e; --link: #a0b5ff; --hover: #d5e8ff; } }
|
global.less
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| @import "@/styles/colors";
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
a { color: @link; text-decoration: none; }
a:hover { text-decoration: underline; color: @hover; }
ul { list-style: none; }
body { min-height: 100vh; color: @font; background: @bg; transition: color 0.5s, background-color 0.5s; line-height: 1.6; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 15px; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
#app { margin: 0 auto; font-weight: normal; }
|
随后正常在main.js引入global.less即可:
1
| import '@/styles/global.less'
|
问题延伸
问题到这里解决了吗?当然,但是还有个需求场景,某些网站会提供用户手动切换深色浅色模式的功能,而css变量可在运行中动态修改的优势就显现了出来
这里就需要js在运行中更改变量啦,具体的实现逻辑如下:
这里采用触发事件的方式进行,在Vue3中,你可以使用mitt进行事件的监听,
可以直接注册在全局app上,也可以单独延续Vue2的习惯创建Vue实例挂载到上面:
这里的举例是创建Vue实例挂载到上面,新建一个eventBus.js
1 2 3 4 5 6 7 8 9 10
| import { createApp } from 'vue'; import mitt from 'mitt';
const emitter = mitt();
const bus = createApp({});
bus.config.globalProperties.$bus = emitter;
export default bus;
|
这样就可以全局触发和监听事件
接下来创建一个工具函数,在挂载时候添加监听,新建util/useColorScheme.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import { onMounted, onUnmounted } from 'vue'; import bus from "@/eventBus.js";
export function useColorScheme() { const themeChange = (theme) => { console.log('themeChange', theme) if (theme) { document.documentElement.style.setProperty('--primary', '#718dff'); document.documentElement.style.setProperty('--bg', '#1d1e21'); document.documentElement.style.setProperty('--font', '#ffffff'); document.documentElement.style.setProperty('--warning', '#faad14'); document.documentElement.style.setProperty('--success', '#a8ff7d'); document.documentElement.style.setProperty('--error', '#f5222d'); document.documentElement.style.setProperty('--info', '#80c1ff'); document.documentElement.style.setProperty('--disabled', '#4e4e4e'); document.documentElement.style.setProperty('--link', '#a0b5ff'); document.documentElement.style.setProperty('--hover', '#d5e8ff'); } else { document.documentElement.style.setProperty('--primary', '#1890ff'); document.documentElement.style.setProperty('--bg', '#ffffff'); document.documentElement.style.setProperty('--font', '#333333'); document.documentElement.style.setProperty('--warning', '#faad14'); document.documentElement.style.setProperty('--success', '#52c41a'); document.documentElement.style.setProperty('--error', '#f5222d'); document.documentElement.style.setProperty('--info', '#1890ff'); document.documentElement.style.setProperty('--disabled', '#bfbfbf'); document.documentElement.style.setProperty('--link', '#1890ff'); document.documentElement.style.setProperty('--hover', '#001764'); } };
onMounted(() => { bus.config.globalProperties.$bus.on('themeChange', themeChange); });
onUnmounted(() => { bus.config.globalProperties.$bus.off('themeChange', themeChange); }); }
|
这个就是在当触发themeChange事件,切换页面的颜色主题,在onMounted,onUnmounted生命周期钩子函数中设置,工具都准备好啦,就可以在任意地方使用啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import {ref, watchEffect} from 'vue'; import {useColorScheme} from "@/util/useColorScheme.js"; import bus from "@/eventBus.js";
const isDark = ref(false);
const handleThemeButton = () => { isDark.value = !isDark.value; };
watchEffect(() => { bus.config.globalProperties.$bus.emit('themeChange', isDark.value); });
useColorScheme();
|
总结一下
其实有点复杂不太优雅,不过在大的Vue项目中,一定会用到pinia,mitt等工具来进行状态管理,全局事件总线监听的,折腾一下利于积累经验()
回到最开始的问题,原生css变量可用于动态修改,给了实时切换的可能,而less/scss的便捷写法又能大大简化开发,好啦,就到这里呀
效果演示

条评论