背景
偶然的一天,我想做一个vue3的工程,图方便(我以前都是创建空目录然后一个个加内容的),使用了pnpm快速创建vue工程,使用以下命令
pnpm create vue
经过一系列选择后工程就创建好了,执行pnpm install和pnpm dev即可开启工程
大概通读过新工程代码后,发现这个工程内置了几个比较有意思的点,比如媒体查询 正常展示
缩小展示
比如深色模式 白天模式
深色模式
那么,我们就来深扒一下,vue是怎么完成这些功能的,并且我们用一个空工程来自己实现这些功能
创建空工程
因为vite提供的模板会比较简洁,我们使用vite的模板来开始我们的功能搭建,使用以下命令创建工程
pnpm create vite
两个工程对比
这是vue模板的目录
这是vite模板的目录,由此可见vite模板会更简洁,侵入性会更低
执行pnpm install和pnpm dev后,就能运行vite工程了
深色模式
我们先从深色模式开始入手
额外知识
win11开启深色模式的方式:在桌面右键,选择个性化,个性化里选择颜色,颜色里有个选择模式,点开选择深色,即可打开深色模式
win10操作方法类似,也是个性化-颜色-选择模式
mac我没有你们可以试下(羡慕的眼光
先来看调成深色模式后两个系统界面的区别,以下我会对两个基础工程分别称为vite/vue
vite版本
vue版本 可以看到,在深色模式下,vue版本的界面是有处理的,而vite版本的没有
样式文件
vue版本的样式文件放在src/assets/bass.css中
/* color palette from <https://github.com/vuejs/theme> */:root { --vt-c-white: #ffffff; --vt-c-white-soft: #f8f8f8; --vt-c-white-mute: #f2f2f2; --vt-c-black: #181818; --vt-c-black-soft: #222222; --vt-c-black-mute: #282828; --vt-c-indigo: #2c3e50; --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); --vt-c-text-light-1: var(--vt-c-indigo); --vt-c-text-light-2: rgba(60, 60, 60, 0.66); --vt-c-text-dark-1: var(--vt-c-white); --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);}/* semantic color variables for this project */:root { --color-background: var(--vt-c-white); --color-background-soft: var(--vt-c-white-soft); --color-background-mute: var(--vt-c-white-mute); --color-border: var(--vt-c-divider-light-2); --color-border-hover: var(--vt-c-divider-light-1); --color-heading: var(--vt-c-text-light-1); --color-text: var(--vt-c-text-light-1); --section-gap: 160px;}@media (prefers-color-scheme: dark) { :root { --color-background: var(--vt-c-black); --color-background-soft: var(--vt-c-black-soft); --color-background-mute: var(--vt-c-black-mute); --color-border: var(--vt-c-divider-dark-2); --color-border-hover: var(--vt-c-divider-dark-1); --color-heading: var(--vt-c-text-dark-1); --color-text: var(--vt-c-text-dark-2); }}*,*::before,*::after { box-sizing: border-box; margin: 0; position: relative; font-weight: normal;}body { min-height: 100vh; color: var(--color-text); background: var(--color-background); 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;} 可以看到,vue版本将需要全局共享的样式,放在了:root下,下面是MDN对:root的解释
:root 这个 CSS 伪类匹配文档树的根元素。对于 HTML 来说,:root 表示元素,除了优先级更高之外,与 html 选择器相同。
也就是说,将css变量,存放到html中,这样就能在工程的哪个文件都能用到这个css变量
实践
我们先对背景色进行改造
在assets目录下创建base.css
添加需要的背景颜色
:root { --dmd-white: #ffffff; ---dmd-dark: #181818;}:root { --color-background: var(--dmd-white);}@media (prefers-color-scheme: dark) { :root { --color-background: var(---dmd-dark); }}*,*::before,*::after { box-sizing: border-box; margin: 0; position: relative; font-weight: normal;}body { min-height: 100vh; background: var(--color-background); transition: background-color .5s;} 在App.vue中引入样式文件
<script setup lang="ts">// This starter template is using Vue 3 <script setup> SFCs// Check out https://vuejs.org/api/sfc-script-setup.html#script-setupimport HelloWorld from "./components/HelloWorld.vue";</script><template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" /></template><style>/* 这里引入样式 */@import "./assets/base.css";#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px;}</style>这样背景的深色模式就做好啦!
通过更改系统颜色,背景色也会跟着变(这里就不贴图了)
但这样还是不够完善的,我们需要对字体也进行深色模式适配
:root { --dmd-white: #ffffff; ---dmd-dark: #181818; --dmd-text-white: #2c3e50; --dmd-text-dark: rgba(235, 235, 235, 0.64);}:root { --color-background: var(--dmd-white); --color-text: var(--dmd-text-white);}/* 重点 */@media (prefers-color-scheme: dark) { :root { --color-background: var(---dmd-dark); --color-text: var(--dmd-text-dark); }}*,*::before,*::after { box-sizing: border-box; margin: 0; position: relative; font-weight: normal;}body { min-height: 100vh; /* 将App.vue中的color移到这里,更好的管理字体和背景色 */ color: var(--color-text); background: var(--color-background); transition: color .5s, background-color .5s;} 我将App.vue中的color样式移到base.css了,这样能在一个文件就管理
<style>/* 这里引入样式 */@import "./assets/base.css";#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; /* color: #2c3e50; */ margin-top: 60px;}</style>这样,根据系统色进行模式切换已经完成了
核心内容
注意看base.css中的一个媒体查询
/* 重点 */@media (prefers-color-scheme: dark) { :root { --color-background: var(---dmd-dark); --color-text: var(--dmd-text-dark); }}我们先来看下MDN对这个用法的解释
prefers-color-scheme CSS 媒体特性用于检测用户是否有将系统的主题色设置为亮色或者暗色。
也就是说,当用户系统主题色为暗色时,就会触发这个media,然后使用里面的css样式,这样就能做到根据系统颜色进行亮暗切换
自定义深色模式事件
以上用法都是根据系统色进行主题切换的,那我办法自定义方法来控制亮暗模式吗?
我们可以使用vueuse中的useDark方法来控制
初探vueuse
vueuse官网有一个useDarkhook,我们可以用这个完成基本的深色模式 点击这里可以看useDark介绍,下面是官方github提供的demo
<script setup lang="ts">import { useToggle } from '@vueuse/shared'import { isDark } from '../../.vitepress/theme/composables/dark'// const isDark = useDark()const toggleDark = useToggle(isDark)</script><template> <button @click="toggleDark()"> <i inline-block align-middle i="dark:carbon-moon carbon-sun" /> <span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span> </button></template>下面是官方介绍中提供的基本用法
import { useDark, useToggle } from '@vueuse/core'const isDark = useDark()const toggleDark = useToggle(isDark)使用vueuse
下面我们来改造页面,在App.vue中加个按钮进行主题切换
pnpm create vite0
我们添加了一个button,添加click事件,然后按钮文字根据isDark标志位控制
可以看到,点击按钮后,在html中添加了dark这个class,这时候我们只需要添加.dark样式就可以了,修改base.css
:root { --dmd-white: #ffffff; ---dmd-dark: #181818; --dmd-text-white: #2c3e50; --dmd-text-dark: rgba(235, 235, 235, 0.64);}:root { --color-background: var(--dmd-white); --color-text: var(--dmd-text-white);}/* 按钮控制样式 */:root.dark { --color-background: var(---dmd-dark); --color-text: var(--dmd-text-dark); color-scheme: dark;}/* 重点 */@media (prefers-color-scheme: dark) { :root { --color-background: var(---dmd-dark); --color-text: var(--dmd-text-dark); }} 这样写我们就需要维护两份深色模式样式,分别是系统控制的和用户控制的,会比较麻烦,我们可以结合less对这部分进行一个抽取然后混入,将base.css文件名改成base.less
pnpm create vite2
vite对.less文件有天然的支持,所以不需要安装诸如webpack中的less-loader之类的插件,但还是需要安装less的依赖,点击这里看官方说明
pnpm create vite3
修改App.vue中对样式文件的引入,注意,这里一定要加上lang,不然会报错
pnpm create vite4
这样在点击按钮的时候,就能看到深色效果啦
bug发现
当系统是亮色模式的时候,应用点击按钮切换主题是正常的,但当系统是深色模式的时候,点击切换按钮无法切换应用样式,一直是深色模式
思路排查
应该是@media (prefers-color-scheme: dark)这块在系统为深色模式的时候,一直占据着应用样式,所以不论html中是否有.dark,都一直展示深色模式
解决方法
把base.less文件中的@media (prefers-color-scheme: dark)这段深色模式样式去掉即可 也就是从
pnpm create vite5
变成
pnpm create vite6
这样,在系统深色模式下,也能进行主题切换了
原因分析
我们可以从useDark源码开始入手
pnpm create vite7
通读代码,得知useDark这个hook,是通过useColorMode和usePreferredDark两个hook实现的
通过useColorModehook控制当前的主题类型,在useDark中我们只需要用到dark主题类型,如果还需要别的主题类型,可以直接使用useColorModehook,然后传入自己需要的主题类型,比如dark, light, coffee, green等等。
切换主题时,useColorMode会将当前主题类型,存放到你指定的标签,默认是根元素html,所以在切换的时候能看到在html标签中多了个.dark。
useColorModehook也会将当前主题类型,存放到localStorage中,默认键名是vueuse-color-scheme,可以通过storageKey选项修改键名
通过usePreferredDarkhook查询当前系统主题类型,也就是以下伪代码
pnpm create vite8
因为useDarkhook已经对系统主题切换做了检测了,所以我们自己再加上@media就重复控制了,就导致系统主题为深色的时候我们无法用按钮控制主题,所以把@media那段控制去掉即可
媒体查询
上面深色模式中,用到了一个媒体查询@media (prefers-color-scheme: dark)来控制深色模式的样式,媒体查询其实还有很多适配的场景,摘抄MDN的介绍
@media CSS @规则 可用于基于一个或多个 媒体查询 的结果来应用样式表的一部分。 使用它,您可以指定一个媒体查询和一个CSS块,当且仅当该媒体查询与正在使用其内容的设备匹配时,该CSS块才能应用于该文档。
点击这里可以查看媒体查询支持的场景,我们这次使用@media (min-width)来模拟vue版本工程的响应式布局
正常展示
缩小展示
具体步骤
我们先创建一个子组件MediaItem.vue,内容不多,就一个红色块
pnpm create vite9
在App.vue中使用组件
/* color palette from <https://github.com/vuejs/theme> */:root { --vt-c-white: #ffffff; --vt-c-white-soft: #f8f8f8; --vt-c-white-mute: #f2f2f2; --vt-c-black: #181818; --vt-c-black-soft: #222222; --vt-c-black-mute: #282828; --vt-c-indigo: #2c3e50; --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); --vt-c-text-light-1: var(--vt-c-indigo); --vt-c-text-light-2: rgba(60, 60, 60, 0.66); --vt-c-text-dark-1: var(--vt-c-white); --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);}/* semantic color variables for this project */:root { --color-background: var(--vt-c-white); --color-background-soft: var(--vt-c-white-soft); --color-background-mute: var(--vt-c-white-mute); --color-border: var(--vt-c-divider-light-2); --color-border-hover: var(--vt-c-divider-light-1); --color-heading: var(--vt-c-text-light-1); --color-text: var(--vt-c-text-light-1); --section-gap: 160px;}@media (prefers-color-scheme: dark) { :root { --color-background: var(--vt-c-black); --color-background-soft: var(--vt-c-black-soft); --color-background-mute: var(--vt-c-black-mute); --color-border: var(--vt-c-divider-dark-2); --color-border-hover: var(--vt-c-divider-dark-1); --color-heading: var(--vt-c-text-dark-1); --color-text: var(--vt-c-text-dark-2); }}*,*::before,*::after { box-sizing: border-box; margin: 0; position: relative; font-weight: normal;}body { min-height: 100vh; color: var(--color-text); background: var(--color-background); 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;}0 注意,我这里把原来的元素,用.main-wrap包裹了起来,这样在#app下就有两个元素,一个是.main-wrap,一个是MediaItem
在base.less中添加媒体查询控制
/* color palette from <https://github.com/vuejs/theme> */:root { --vt-c-white: #ffffff; --vt-c-white-soft: #f8f8f8; --vt-c-white-mute: #f2f2f2; --vt-c-black: #181818; --vt-c-black-soft: #222222; --vt-c-black-mute: #282828; --vt-c-indigo: #2c3e50; --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); --vt-c-text-light-1: var(--vt-c-indigo); --vt-c-text-light-2: rgba(60, 60, 60, 0.66); --vt-c-text-dark-1: var(--vt-c-white); --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);}/* semantic color variables for this project */:root { --color-background: var(--vt-c-white); --color-background-soft: var(--vt-c-white-soft); --color-background-mute: var(--vt-c-white-mute); --color-border: var(--vt-c-divider-light-2); --color-border-hover: var(--vt-c-divider-light-1); --color-heading: var(--vt-c-text-light-1); --color-text: var(--vt-c-text-light-1); --section-gap: 160px;}@media (prefers-color-scheme: dark) { :root { --color-background: var(--vt-c-black); --color-background-soft: var(--vt-c-black-soft); --color-background-mute: var(--vt-c-black-mute); --color-border: var(--vt-c-divider-dark-2); --color-border-hover: var(--vt-c-divider-dark-1); --color-heading: var(--vt-c-text-dark-1); --color-text: var(--vt-c-text-dark-2); }}*,*::before,*::after { box-sizing: border-box; margin: 0; position: relative; font-weight: normal;}body { min-height: 100vh; color: var(--color-text); background: var(--color-background); 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;}1 意思是,在宽度大于1024px时,#app就使用flex布局,并且main-wrap和media-item均等分布
当页面宽度小于1024px时,就恢复原来的正常布局流
原文:https://juejin.cn/post/7098547719232290852