背景
偶然的一天,我想做一个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官网有一个useDark
hook,我们可以用这个完成基本的深色模式 点击这里可以看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实现的
通过useColorMode
hook控制当前的主题类型,在useDark
中我们只需要用到dark
主题类型,如果还需要别的主题类型,可以直接使用useColorMode
hook,然后传入自己需要的主题类型,比如dark
, light
, coffee
, green
等等。
切换主题时,useColorMode
会将当前主题类型,存放到你指定的标签,默认是根元素html,所以在切换的时候能看到在html标签中多了个.dark
。
useColorMode
hook也会将当前主题类型,存放到localStorage
中,默认键名是vueuse-color-scheme
,可以通过storageKey
选项修改键名
通过usePreferredDark
hook查询当前系统主题类型,也就是以下伪代码
pnpm create vite8
因为useDark
hook已经对系统主题切换做了检测了,所以我们自己再加上@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