首页>>前端>>Vue->跟着vue学习深色模式和媒体查询

跟着vue学习深色模式和媒体查询

时间:2023-11-30 本站 点击:0

背景

偶然的一天,我想做一个vue3的工程,图方便(我以前都是创建空目录然后一个个加内容的),使用了pnpm快速创建vue工程,使用以下命令

pnpm create vue

经过一系列选择后工程就创建好了,执行pnpm installpnpm dev即可开启工程

大概通读过新工程代码后,发现这个工程内置了几个比较有意思的点,比如媒体查询 正常展示

缩小展示

比如深色模式 白天模式

深色模式

那么,我们就来深扒一下,vue是怎么完成这些功能的,并且我们用一个空工程来自己实现这些功能

创建空工程

因为vite提供的模板会比较简洁,我们使用vite的模板来开始我们的功能搭建,使用以下命令创建工程

pnpm create vite

两个工程对比

这是vue模板的目录

这是vite模板的目录,由此可见vite模板会更简洁,侵入性会更低

执行pnpm installpnpm 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,是通过useColorModeusePreferredDark两个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-wrapmedia-item均等分布

当页面宽度小于1024px时,就恢复原来的正常布局流

原文:https://juejin.cn/post/7098547719232290852


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/3759.html