css变量+vue3实现跟随浏览器主题和用户设置切换网页主题

前言

目前的公司只有我一个应届前端,学习起来很慢,所以打算写一些文章来记录一下,通过记录的过程增强一下自己的理解以及方便后期的总结复盘。如果文章哪里写的有问题希望看到的大佬能指点一下。

一. css变量和:root选择器

:root 这个 CSS 伪类匹配文档树的根元素,也就是html元素,但是他的优先级比html选择器更高,在:root下可以声明全局css变量,在每个css样式中都可以使用,。例如:

css 复制代码
:root {
  --main-color: hotpink;
  --pane-padding: 5px 42px;
}

我们这里定义了两个css变量,定义css变量时需要在变量名前添加--,值可以是任何css属性值。在使用时配合var()来使用,例如padding:var(--pane-padding);

二. 设置主题样式

我们可以使用属性选择器来实现明亮和黑暗两套主题的切换,使用时只需要给html元素添加"data-theme"属性来表示当前主题的值。

css 复制代码
:root[data-theme="light"] {
  --backgroundcolor: pink;
  --fontcolor: #726b6b;
}
:root[data-theme="dark"] {
  --my-backgroundcolor: black;
  --my-fontcolor: #fff;
}

可以将通用的样式放在:root选择器下

css 复制代码
:root {
  --my-fontsize: 20px;
}

在vue项目中,创建一个theme.css文件,将上面的css规则放到这个文件中,在main.js中引入css文件

三. 切换主题的javascript代码

在vue项目中创建一个theme.js文件存放我们的js代码

1.matchMedia() 方法

来看一下MDN文档的描述:WindowmatchMedia() 方法返回一个新的 MediaQueryList 对象,表示指定的媒体查询 (en-US)字符串解析后的结果。返回的 MediaQueryList 可被用于判定 Document 是否匹配媒体查询,或者监控一个 document 来判定它匹配了或者停止匹配了此媒体查询。

要实现跟随浏览器当前主题进行主题的变化,我们需要利用这个方法来查询浏览器当前主题,通过查询'(prefers-color-scheme: light)'这个媒体查询字符串来查询当前主题是否是light主题。
let matchMedia = window.matchMedia('(prefers-color-scheme: light)')

返回的matchMedia对象有一个matches属性,值是bool类型,表示查询的匹配结果,同时matchMedia对象可以用addEventListener和removeListener方法来监听和移除媒体查询结果的变化。

基于此我们可以封装一个查询浏览器当前主题的函数

js 复制代码
function queryBroswerTheme() {
  let matchMedia = window.matchMedia('(prefers-color-scheme: light)')//isLight
  return matchMedia.matches ? "light" : "dark"
}

2.跟随浏览器选择主题

首先创建一个查询浏览器主题的媒体查询对象,同时创建一个监听和移除监听的函数来实现与用户浏览器主题的同步改变,在回调函数中判断当前主题来进行切换

js 复制代码
let matchMedia = window.matchMedia('(prefers-color-scheme: light)')
//停止监听
function stopListenBroswerTheme() {
  console.log("停止监听浏览器主题变换");
  matchMedia.removeEventListener("change", themeChange)
}
//开始监听
function beginListenBroswerTheme() {
  console.log("开始监听浏览器主题变换");
  matchMedia.addEventListener("change", themeChange)
}
//监听回调函数
function themeChange(e) {
  if (e.matches) {
    document.documentElement.setAttribute("data-theme", "light")
  } else {
    document.documentElement.setAttribute("data-theme", "dark")
  }
}

这时候我们可以创建一个函数,来实现跟随浏览器主题的切换

js 复制代码
function setWithBroswer() {
  if (matchMedia.matches) {
    document.documentElement.setAttribute("data-theme", "light")
  } else {
    document.documentElement.setAttribute("data-theme", "dark")
  }
  beginListenBroswerTheme()
}

3. 根据用户偏好选择主题

如果用户没有偏好的主题我们让页面的主题跟随浏览器变化,如果用户选择一种自己偏好的主题,那我们需要存储用户的偏好,在下次打开页面时根据偏好来进行主题的显示。

所以我们需要使用localStorage来持久化存储一个字段保存用户的偏好,字段名为"preferTheme",我这里采取的方案是:

  1. 如果用户首次进入,我们根据浏览器主题来显示,"preferTheme"的值为"auto"
  2. 如果用户进行过主题选择,那么"preferTheme"的值为"light"或者"dark"
  3. 如果用户选择的主题和浏览器当前主题相同,"preferTheme"的值也为"auto"

封装俩个存取localStorage的函数

js 复制代码
//设置loaclstorage
export function setPreferTheme(val) {
  localStorage.setItem("preferTheme", JSON.stringify(val))
}
//读取loaclstorage
export function getPreferTheme() {
  const res = JSON.parse(localStorage.getItem("preferTheme"))
  if (!res) return false
  return res
}

4. 初始化主题

我们可以写一个完整的初始化页面主题的函数,在初始化主题函数中做判断,如果不存在"preferTheme"字段,说明用户是首次访问网页,将"preferTheme"值设为"auto",跟随浏览器主题切换,如果存在"preferTheme"字段,那么直接设置主题。

js 复制代码
export function useInitTheme() {
  if (!getPreferTheme()) {
    setWithBroswer()
    setPreferTheme("auto")
  } else {
    const preferTheme = getPreferTheme()
    if (preferTheme == "auto") {
      setWithBroswer()
    } else {
      document.documentElement.setAttribute("data-theme", preferTheme)
    }
  }
}

5. 开关切换处理

新建一个vue文件,在这里编译页面以及业务代码,这里我写了一个简单的页面,进行主题切换。开关直接使用了element-plus组件库中的开关,给按钮绑定v-model="theme",并将按钮激活的和关闭的值设为"dark"和"light"

html 复制代码
<template>
  <div class="common-layout">
    <el-container>
      <el-header>
        <div class="header">
          <span>这是一个标题</span>
          <el-switch v-model="theme" class="switch" active-value="dark" inactive-value="light" />
        </div>
      </el-header>
      <el-container>
        <el-aside width="200px" class="aside">这是侧边栏</el-aside>
        <el-main class="main"><el-button @click="show">显示弹窗</el-button></el-main>
      </el-container>
    </el-container>
  </div>
</template>
<style scoped lang="less">
.common-layout {
  .header {
    position: relative;
    font-size: var(--my-fontsize);
    background-color: var(--my-backgroundcolor);
    color: var(--my-fontcolor);
    .switch {
      position: absolute;
      right: 0;
      top: 50%;
      transform: translateY(-50%);
      --el-switch-on-color: #2f2f2f;
    }
  }
  .aside {
    font-size: var(--my-fontsize);
    background-color: var(--my-backgroundcolor);
    color: var(--my-fontcolor);
  }
}
</style>

具体的业务代码也写在theme.js中,最后通过import导入到所需要的vue组件中使用即可。

js 复制代码
import { ref, watch } from "vue"
let theme=null //为什么将theme定义在全局,一会会解释的
export function handleSwitch() {
  //初始化按钮
  theme = ref("light")
  const broswerTheme = queryBroswerTheme()
  const preferTheme = getPreferTheme()
  if (preferTheme === "auto") {
     theme.value = broswerTheme
   }
  else if (preferTheme === "dark") {
     theme.value = "dark"
  }
  //监听变化
  //xxxxxxxxxxxxx
  return theme
}

在这个函数中我们要初始化theme变量,根据存入localStorage的"preferTheme"值来初始化theme的值

还需要用watch来监听开关的切换,当按钮切换时修改主题和"preferTheme"的值,这里关键是如何让设置"preferTheme"的值,刚才其实已经说过了,如果用户选择主题和浏览器当前主题相同,"preferTheme"的值为"auto",不同则为"light"或者"dark"

js 复制代码
watch(theme, (newval, oldval) => {
    document.documentElement.setAttribute("data-theme", newval)
    newval == queryBroswerTheme() ? setPreferTheme("auto") : setPreferTheme(newval)
})

别忘了,如果"preferTheme"的值是"auto",需要监听浏览器主题的变化,如果不是"auto",需要移除监听, 所以我们需要修改一下watch函数

js 复制代码
//开关改变时
watch(theme, (newval, oldval) => {
  document.documentElement.setAttribute("data-theme", newval)
  newval == queryBroswerTheme() ? isAuto() : notAuto(newval)
})
//是auto自动增加监听器
function isAuto() {
  setPreferTheme("auto")
  beginListenBroswerTheme()
}
//不是auto自动取消监听器
function notAuto(val) {
  setPreferTheme(val)
  stopListenBroswerTheme()
}

现在解释一下theme为什么要定义在全局,因为在监听浏览器主题和移除监听的回调函数中我们还需要修改开关的状态,也就是theme的值,所以我们还需要修改一下回调函数

js 复制代码
function themeChange(e) {
  if (e.matches) {
    document.documentElement.setAttribute("data-theme", "light")
    theme.value = "light"
  } else {
    document.documentElement.setAttribute("data-theme", "dark")
    theme.value = "dark"
  }
}

6.完结

到此所有的代码都写完了,将useInitTheme函数导入到main.js文件中调用,将handleSwitch函数导入到vue组件中使用即可。

js 复制代码
import { useInitTheme } from "@/utils/theme.js"
useInitTheme()
js 复制代码
import { handleSwitch } from "@/utils/theme.js"
let theme = handleSwitch()
相关推荐
Mintopia12 分钟前
计算机图形学环境贴图(Environment Mapping)教学指南
前端·javascript·计算机图形学
码农之王14 分钟前
(二)TypeScript前置编译配置
前端·后端·typescript
spmcor15 分钟前
css 之 Flexbox 的一生
前端·css
Mintopia18 分钟前
Three.js 高级纹理(Advanced Textures):超越基础,打造沉浸式 3D 世界
前端·javascript·three.js
玄玄子19 分钟前
JS Promise
前端·javascript·程序员
GIS之路30 分钟前
OpenLayers 获取地图状态
前端·javascript·html
FogLetter1 小时前
深入理解Flex布局:grow、shrink和basis的计算艺术
前端·css
remember_me1 小时前
前端打印实现-全网最简单实现方法
前端·javascript·react.js
前端小巷子1 小时前
IndexedDB:浏览器端的强大数据库
前端·javascript·面试
Whbbit19991 小时前
如何使用 Vue Router 的类型化路由
前端·vue.js