前端 “一键换肤“ 的几种方案

简介: 前端 “一键换肤“ 的几种方案

image.png

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新!

现在越来越多的网站都提供了拥有换肤(切换主题)功能,如 ElementUI,既是为了迎合用户需求,或是为了凸显自己特点,因此提供了个性化定制的功能.

其实之前就想了解和实现 “一键换肤” 功能,但是由于种种原因一直拖到了现在.

image.png

CSS 样式覆盖实现

核心

通过切换 css 选择器的方式实现主题样式的切换.

  • 在组件中保留不变的样式,将需要变化的样式进行抽离
  • 提供多种样式,给不同的主题定义一个对应的 CSS 选择器
  • 根据不同主题设置不同的样式

实现

下面通过 vuex 存储和控制全局的主题色,其代码如下:

import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
  state () {
    return {
      theme: 'light'
    }
  },
  mutations: {
    setTheme (state, payload) {
      state.theme = payload
      document.querySelector('body').className = payload
    }
  }
})
export default store
复制代码

template 模板中通过 vuex 中的主题设置对应类名,如头部代码如下:

<template>
  <div :class="['header', store.state.theme]">
    <span>{{title}}</span>
    <input v-model="checked" type="checkbox" class="switch" @change="changeTheme" />
  </div>
</template>
复制代码

下面 theme.css 中通过 .light.dark 两个类选择器来区分明亮主题和暗黑主题,并且事先准备了它们对应的样式,如下:

/* light 默认主题*/
body.light {
  background-color: #fff;
}
.header.light {
  background-color: #fff;
  border-bottom: 1px solid #d6d6d6;
  color: rgb(51, 50, 50);
}
.list.light .title {
  color: rgb(51, 50, 50);
}
.list.light .describe{
  color: rgb(158, 158, 158);
}
.list.light .left{
  border: 1px solid rgb(51, 50, 50);
}
/* dark 暗黑主题 */
body.dark {
  background-color: rgb(51, 50, 50);
}
.header.dark {
  background-color: rgb(51, 50, 50);
  border-bottom: 1px solid #fff;
  color: #fff;
}
.list.dark .title {
  color: #fff;
}
.list.dark .describe{
  color: rgb(201, 201, 201);
}
.list.dark .left{
  border: 1px solid #fff;
  background-color: #fff;
}
复制代码

缺点

  • 多种主题样式都要引入,导致代码量增大
  • 样式不易管理
  • 查找样式复杂
  • 开发效率低
  • 拓展性差
  • ...

实现多套 CSS 主题样式

核心

实现多套 CSS 主题样式,根据用户切换操作,通过 link 标签动态加载不同的主题样式,主要解决了多个主题色被编译到一个文件中导致单个文件过大.

实现

css 部分直接拆分成 ligth.cssdark.css 两个文件:

image.png

设置主题部分的 setTheme.js 代码如下:

export default function setTheme(theme = 'ligth') {
  let link = document.querySelector('#theme-link')
  let href = "/theme/" + theme + ".css"
  if (!link) {
    let head = document.querySelector('head')
    link = document.createElement('link')
    link.id = '#theme-link'
    link.rel = "stylesheet"
    link.href = href
    head.appendChild(link)
  } else {
    link.href = href
  }
}
复制代码

缺点

  • 需要重复 CV 多份样式文件进行单独修改
  • 没有单独提取出可变的样式部分
  • 需要提前知道打包后的文件路径,否则可能导致主题样式引入错误
  • ...

CSS 变量实现

核心

通过 body.style.setProperty(key, value) 动态修改 body 上的 CSS 变量,使得页面上的其他部分可以应用最新的 CSS 变量对应的样式.

image.png

实现

theme.css 中负责定义全局的 CSS 变量,代码如下:

/* 实现方式一 */
:root {
  --theme-bg: initial; // 背景色
  --theme-color: initial; // 字体色
  --theme-boder-color: initial; // 边框色
}
====================================================
/* 实现方式二 */
/* 默认值:light */
:root {
  --theme-bg: #fff;
  --theme-color: rgb(51, 50, 50);
  --theme-img-bg: #fff;
  --theme-boder-color: #d6d6d6;
}
/* 暗黑:dark */
[data-theme='dark'] {
  --theme-bg: rgb(51, 50, 50);
  --theme-color: #fff;
  --theme-boder-color: #fff;
}
复制代码

themeUtil.js 中负责获取当前对应样式值,以及设置 body 上的  CSS 变量值,如下:

const darkTheme = 'rgb(51, 50, 50)'
const lightTheme = '#fff'
const lightBorderTheme = '#d6d6d6'
// 获取对应的主题色值
export const getThemeMap = (isLight) => {
  return {
    'theme-bg': isLight ? lightTheme : darkTheme,
    'theme-color': isLight ? darkTheme : lightTheme,
    'theme-boder-color': isLight ? lightBorderTheme : lightTheme,
  }
}
// 设置主题色值
export const setTheme = (isLight = true) => {
  const themeMap = getThemeMap(isLight)
  const body = document.body
  /* 实现方式一 */
  Object.keys(themeMap).forEach(key => {
    body.style.setProperty(`--${key}`, themeMap[key])
  })
  /* 实现方式二 */
  // body.style.setProperty('data-theme', isLight ? 'light' : 'dark')
}
复制代码

通过 var() 在组件中应用对应 CSS 变量,比如在头部中的使用:

<style scoped>
.header {
  ...省略
  color: var(--theme-color);
  border-bottom: 1px solid var(--theme-boder-color);
  background-color: var(--theme-bg);
}
...省略
</style>
复制代码

缺点

缺点就是兼容性不好

image.png

兼容

通过 css-vars-ponyfill 对 CSS 变量进行兼容处理,themeUtil.js 中代码改变如下:

import cssVars from "css-vars-ponyfill";
const darkTheme = 'rgb(51, 50, 50)'
const lightTheme = '#fff'
const lightBorderTheme = '#d6d6d6'
// 这里定义的 键/值 对,是为了给 cssVars 传参
export const getThemeMap = (isLight) => {
  return {
    '--theme-bg': isLight ? lightTheme : darkTheme,
    '--theme-img-bg': lightTheme,
    '--theme-color': isLight ? darkTheme : lightTheme,
    '--theme-boder-color': isLight ? lightBorderTheme : lightTheme,
  }
}
export const setTheme = (isLight = true) => {
  const themeMap = getThemeMap(isLight)
  const body = document.body
  /* 实现方式一 */
  Object.keys(themeMap).forEach(key => {
    body.style.setProperty(key, themeMap[key])
  })
  /* 实现方式二 */
  // body.style.setProperty('data-theme', isLight ? 'light' : 'dark')
  // 实现兼容方案
  cssVars({
    watch: true, // 添加、删除、修改 <link> 或 <style> 元素的禁用或 href 属性时,ponyfill 将自行调用    
    variables: themeMap, // variables 自定义属性名/值对的集合
    onlyLegacy: false, // false  默认将 css 变量编译为浏览器识别的 css 样式 ;true 当浏览器不支持css变量的时候将css变量编译为识别的css  
  });
}
复制代码

主题图片切换

image.png

实现了前面的内容之后,现在给分别给 lightdark 主题添加一个 logo,这一部分其实很简单了,下面的示例代码是基于 Vue3 进行实现的

// Header.vue
<script setup>
import { ref } from 'vue'
import { setTheme } from '../style/themeUtil'
defineProps({
  title: String
})
const checked = ref(false)
const logoUrl = ref('')
const loadImg = async () => {
  let name = !checked.value ? 'light' : 'dark'
  let ext = !checked.value ? 'png' : 'jpg'
  let res = await import(`../assets/logo-${name}.${ext}`)
  logoUrl.value = res.default
}
loadImg()
const changeTheme = (event) => {
  setTheme(!checked.value)
  loadImg()
}
</script>
<template>
  <div class="header">
    <img class="logo" :src="logoUrl" />
    <span>{{ title }}</span>
    <input v-model="checked" type="checkbox" class="switch" @change="changeTheme" />
  </div>
</template>
复制代码

效果如下

image.png

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新!

以上就是目前了解到一些的换肤方案,以上全部基于 css 去实现的,不过知道了原理就可以结合 lesssass 进行更好的实现。如果有更好的方案,欢迎贴在评论区进行分享!!!


目录
相关文章
|
Web App开发 缓存 前端开发
前端性能优化方案有哪些?
前端性能优化方案有哪些?
360 1
|
前端开发 JavaScript
常见的8个前端防御性编程方案
常见的8个前端防御性编程方案
268 0
|
3月前
|
存储 前端开发 安全
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
220 5
|
前端开发 数据可视化 搜索推荐
深入剖析极态云优雅的前端框架设计方案(上)
最近在体验极态云,这款低代码软件开发产品,发现其前端框架设计方案很优雅很强大! 在接下来的学习过程中,我将持续输出自己对极态云前端框架设计方案的深入理解,包括具体的使用技巧、优势分析以及可能的应用场景等方面的内容,希望能为大家提供有价值的参考。
|
存储 缓存 前端开发
前端快照实现方案详解
前端快照实现方案详解
1006 1
|
Web App开发 前端开发 JavaScript
Web前端项目的跨平台桌面客户端打包方案之——CEF框架
Chromium Embedded Framework (CEF) 是一个基于 Google Chromium 项目的开源 Web 浏览器控件,旨在为第三方应用提供嵌入式浏览器支持。CEF 隔离了底层 Chromium 和 Blink 的复杂性,提供了稳定的产品级 API。它支持 Windows、Linux 和 Mac 平台,不仅限于 C/C++ 接口,还支持多种语言。CEF 功能强大,性能优异,广泛应用于桌面端开发,如 QQ、微信、网易云音乐等。CEF 开源且采用 BSD 授权,商业友好,装机量已超 1 亿。此外,GitHub 项目 CefDetector 可帮助检测电脑中使用 CEF
2852 3
|
缓存 前端开发 UED
前端 8 种图片加载优化方案梳理
本文首发于微信公众号“前端徐徐”,详细探讨了现代网页设计中图片加载速度优化的重要性及方法。内容涵盖图片格式选择(如JPEG、PNG、WebP等)、图片压缩技术、响应式图片、延迟加载、CDN使用、缓存控制、图像裁剪与缩放、Base64编码等前端图片优化策略,旨在帮助开发者提升网页性能和用户体验。
3089 0
|
缓存 前端开发 JavaScript
前端性能优化方案
【8月更文挑战第15天】前端性能优化方案
668 2
|
缓存 前端开发 JavaScript
前端常见的性能优化方案?
【7月更文挑战第14天】前端性能优化包括代码压缩、资源合并、图片优化、缓存策略和DOM操作改进。例如,压缩CSS、JS,懒加载图片,使用CDN,事件委托,启用HTTP/2,及利用性能工具进行评估和优化。目标是加快加载速度,提升用户体验和服务器效率。
438 2
|
编解码 前端开发 JavaScript
前端移动端适配方案
【9月更文挑战第8天】前端移动端适配方案
710 0

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    597
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    249
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    233
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    181
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    293
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    419
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    181
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    123
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    196
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    270