实战网站模式和主题切换,实现跟随系统模式

306 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情

主题和模式的定义

想要实现网站换肤的功能,有很多的方案可以选择,但其核心逻辑是不变的,我觉得在实现之前先明确主题和模式概念比较好:

模式一般情况下有两种,高亮模式和暗黑模式,主要受影响的页面属性是背景颜色、字体颜色; 主题则是网站的主题色、辅助色这类的配色方案,主要用在突出显示的内容,如激活的菜单、按钮颜色等。

线上预览

code.juejin.cn/pen/7173223…

页面设计

demo的页面设计以常见的官网为原型,页面效果如下:

image

最终效果图

分为三个部分:

  • 通栏菜单,包含模式切换
  • 大图banner
  • 页面主体,也分两部分:第一块是主题色背景内,嵌套标题以及跟随跟随背景色的小块;第二部分是左文右图;这样的页面结构非常常见,比如掘金插件宣传页: image 图片备注

页面代码如下:

<template>
  <!-- 导航菜单 -->
  <header>
    <a href="javascript:;" :class="{'theme-bg': active === index}" v-for="item, index in menu" :key="item" @click="active = index">{{item}}</a>

    高亮模式<input type="radio" value="light" name="mode" />
    暗黑模式<input type="radio" value="dart" name="mode"/>
  </header>

  <!-- 网站主题内容案例 -->
  <main>
    <img class="banner" src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0511edfa-4fd3-481d-8712-a5c2b826268d/ce9a993a-40bb-4dd2-94a7-68173cc1e7e0.jpeg"/>

    <section>
      <div class="theme-bg content-1">
        <h3>标题标题标题标题</h3>
        <div class="content-1-list">
          <div class="mode-bg">内容块</div>
          <div class="mode-bg">内容块</div>
          <div class="mode-bg">内容块</div>
        </div>
      </div>

      <div class="content-2">
        <h3 class="theme-text">标题标题标题标题</h3>
        <div class="content-2-block theme-bg">内容块</div>
      </div>
    </section>
  </main>
</template>
<script setup lang="ts">
const menu = ['首页', '文章', '代码', '资源']
const active = ref(0)
</script>

<style lang="scss">

// 页面布局
header {
  text-align: right;
  height: 50px;
  line-height: 50px;
  padding: 0 15px;

  a {
    padding: 0 10px;
    display: inline-block;
    height: 100%;
    text-decoration: none;
  }
}

.banner {
  width: 100%;
  height: 40vh;
  object-fit: cover;
  margin-bottom: 10px;
}

.theme-block {
  display: inline-block;
  width: 30px;
  height: 30px;
  line-height: 30px;
  text-align: center;
  border-radius: 5px;
  margin: 10px;
  position: relative;

  &.active::before {
    content: '√';
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
}

h2 {
  padding: 0 15px;
}

section {
  padding: 0 10px;

  .content-1 {
    padding: 15px;
    text-align: center;

    .content-1-list {
      display: flex;
      justify-content: space-between;
      padding-top: 15px;

      &>div {
        height: 100px;
        width: 25%;
        border-radius: 4px;
        text-align: center;
        line-height: 100px;
      }
    }
  }

  .content-2 {
    padding: 30px;
    display: flex;
    justify-content: center;
    align-items: cneter;
    text-align: center;

    h3 {
      flex: 1;
    }

    .content-2-block {
      margin-left: 50px;
      height: 100px;
      flex: 1;
      display: flex;
      justify-content: center;
      align-items: cneter;
      line-height: 100px;
    }
  }
}
</style>

变量预设

我们需要先确定模式和主题的颜色,我这里选用颜色值为

  const light = '#fefefe' // 高亮模式背景色
  const dart = '#302D42' // 暗黑模式背景色
  // 不同模式下,字体颜色和背景颜色相反
  
  // 页面主题色
  const themeList = ['#90E39A','#7D5BA6','#A49966','#CE4760']

为了后续的开发更方便兼容不同的模式和主题,我们要做两件事:

  • 定义主题和模式相关的css变量 如果模式和主题还涉及到边框、阴影等,继续加变量即可
:root {
    --theme-color: #3497FC; // 主题颜色
    --mode-bg: #fefefe; // 模式背景颜色
    --mode-color: #302D42; // 模式的字体颜色
}
  • 修改初始化css样式 开发项目时为了不同浏览器中样式统一,通常会重写网页的预设样式,我们在此基础上去增加模式和主题相关的预设样式

    其中最重要的预设是设置body的背景颜色和字体颜色,后续开发尽量不要单独设置元素的这两个属性,让元素继承body的颜色。

body {
    margin: 0;
    padding: 0;
    background-color: var(--mode-bg);
    color: var(--mode-color);
	
	// a标签模式是丑陋的蓝色,这里将它设置为跟随模式的字体颜色,也就是黑/白
    a {
        color: var(--mode-color);	
    }
}
  • 定义通用的主题、模式class
// 定义一系列关于主题和模式的样式,用到背景颜色和主题色的统一使用这几个类
.theme-text {
	color: var(--theme-color);
}

.theme-bg {
	background-color: var(--theme-color);
}

.mode-bg {
	background-color: var(--mode-bg);
}

.mode-text {
	color: var(--mode-color);
}

切换模式

切换模式要做的事情有两件:

  • 修改对应的css变量
  • 把模式的值存储到本地
// 设置模式
function setMode(_mode) {
    switch (_mode) {
      case "dart":
        document.body.style.setProperty("--mode-bg", dart);
        document.body.style.setProperty("--mode-color", light);
        break;
      case "light":
        document.body.style.setProperty("--mode-bg", light);
        document.body.style.setProperty("--mode-color", dart);
        break;
    }
    
    // 保存到本地以及赋值给变量
    localStorage.setItem('pages-mode', _mode)
    mode.value = _mode
}

// 获取模式值
function getMode() {
    return localStorage.getItem('pages-mode') || 'light'
}

// 进入页面先获取本地存储的模式,并进行修改
setMode(getMode())

切换主题

切换主题和切换模式类似,只是修改的css变量不同。

  // 设置主题色
  function setTheme(_theme) {
    document.body.style.setProperty("--theme-color", _theme);
    theme.value = _theme
    localStorage.setItem('pages-theme', _theme)
  }

  // 设置主题色
  function getTheme(_theme) {
    return localStorage.getItem('pages-theme') || themeList[0]
  }

  // 进入页面先获取本地存储的主题色,并进行修改
  setTheme(getTheme())

模式跟随系统

模式的切换我们可以做到更智能一点:跟随用户系统的模式设置。

通过 window.matchMedia 的api可以判断用户当前系统是否设置了暗黑模式,并且监听到系统模式的改变:

const media =  window.matchMedia('(prefers-color-scheme: dark)')

function changeMedia() {
    setMode(media.matches ? 'dark' : 'light')
}
media.addEventListener('change', changeMedia)

// 进入页面时跟随系统模式
// changeMedia()

当然,手动修改模式和自动跟随系统模式是冲突的,一般还要增加一个开关设置:是否跟随系统模式。