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

article/2025/7/12 11:44:10

前端瓶子君,关注公众号

回复算法,加入前端编程面试算法每日一题群

前言

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

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

8582927574d38befca092dc0f0fcd017.png
skin.gif

CSS 样式覆盖实现

核心

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

  • 在组件中保留不变的样式,将需要变化的样式进行抽离

  • 提供多种样式,给不同的主题定义一个对应的 CSS 选择器

  • 根据不同主题设置不同的样式

实现

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

import { createStore } from 'vuex'// 创建一个新的 store 实例
const store = createStore({state () {return {theme: 'light'}},mutations: {setTheme (state, payload) {state.theme = payloaddocument.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 两个文件:

dd8b918d06ba36df219953f0318e4ea0.png
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 = hrefhead.appendChild(link)} else {link.href = href}
}
复制代码

缺点

  • 需要重复 CV 多份样式文件进行单独修改

  • 没有单独提取出可变的样式部分

  • 需要提前知道打包后的文件路径,否则可能导致主题样式引入错误

  • ...

CSS 变量实现

核心

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

cd7afb9359a78dbfdaa4f1b98108710e.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>
复制代码

缺点

缺点就是兼容性不好

ac345b457d6e1423782677f5cec20861.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  });
}
复制代码

主题图片切换

a9306e979e7404d11549bea9e4aba214.png1cddc1d002016308b022970c0e4864da.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>
复制代码

效果如下

317ce09001870f90cb65d53bdef651bc.png
skin.gif

最后

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

关于本文

作者:熊的猫

https://juejin.cn/post/7063010855167721486

最后

欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿

回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!

回复「交流」,吹吹水、聊聊技术、吐吐槽!

回复「阅读」,每日刷刷高质量好文!

如果这篇文章对你有帮助,「在看」是最大的支持

 》》面试官也在看的算法资料《《

“在看和转发”就是最大的支持


http://chatgpt.dhexx.cn/article/2XU8qJUT.shtml

相关文章

Python装逼指南——五行代码实现批量抠图

你是否曾经想将某张照片中的人物抠出来&#xff0c;然后拼接到其他图片上去&#xff0c;从而可以即使你在天涯海角&#xff0c;我也可以到此一游&#xff1f; 专业点的人使用 PhotoShop 的“魔棒”工具可以抠图&#xff0c;非专业人士可以使用各种美图 APP 来实现&#xff0c;但…

iter()函数联队*、zip()实现序列“定长”拆分——基于iterator特性拆解繁复的单行“装逼代码”,搞明白序列定长拆分“秘法”

Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… My CSDN主页、My HOT博、My Python 学习个人备忘录好文力荐、 老齐教室 自学并不是什么神秘的东西&#xff0c;一个人一…

如何在你朋友面前装逼4(程序代码)

大家好&#xff0c;我是不讲武德。今天我来教大家在电脑上画画&#xff0c;非常简单 --------------------------------------------------------------------------------------------------------------------- 第一步&#xff1a;新建一个文本文档 第二步&#xff1a;打开…

dtree树形菜单带搜索复选框单选框插件

dtree树形菜单带搜索复选框单选框插件 用于选择部门或者选择部门下人员 效果如下图 dtree树形菜单带搜索不带单选框复选框 dtree选择部门或者选择部门下人员复选框单选框插件 带搜索 https://download.csdn.net/download/qq_27559331/9885259

OA与帆软BI跨系统用户、部门、岗位同步总结

目录 前言&#xff1a; 一、初衷 1、需要准备啥&#xff1f; 2、同步接口或者数据集 3、建立服务器数据集 代码&#xff1a; 效果&#xff1a; 二、正式同步 1、建立服务器数据集 2、建立服务器树状数据集 3、选择对应关系进行同步 4、等待与设置同步频率 5、注意事项 …

JAVA 对接钉钉API(人员、部门、官方智能工作流)20210527

前言 应公司要求&#xff0c;公司人事HR系统需要对接钉钉考勤数据&#xff0c;所以需要获取钉钉的打卡记录、出差、外出、请假、调岗的数据&#xff0c;然后转换成HR系统数据。 对接前准备 创建应用 1、首先需要管理员登录钉钉开放平台&#xff0c;创建应用。 说明 只有管理…

Element使用级联选择器

Element使用级联选择器 element的级联选择器和select不一样,下拉框我们可以手动定义label和value,只需要将查出来的值循环一下即可 但是级联选择器的视图层是这样的 只有 :options=“options” 这个属性让我们绑定值,没办法绑定他的label和value element官网给的数据结构是这…

研发部的人员素质要求及自我培养

IT行业发展已经走的很远了&#xff0c;纵观世界经济的发展&#xff0c;经济全球化进程明显加快&#xff0c;信息化已成为全球化的迫切需要和必要保证。世界范围的产业结构调整和信息技术进步&#xff0c;必将对中国信息产业的发展产生深刻影响&#xff0c;所以IT行业的前景还是…

2-Springboot集成Flowable之 选择人员的界面自定义开发

目录 效果实现思路1、新建自己的 my-assignment-popup.html2、修改properties-assignment-controller.js 效果 演示地址 代码地址 前端代码地址 实现思路 部门是基于bootstrap-treeview插件实现的&#xff0c;表格是自己用div css写的。其实自己了解angular修改这里应该没有…

用户选择框设计思路

最近新项目要用到选择人员&#xff0c;于是重新在新项目中设计了一版选人框。 效果图如下&#xff1a; 功能部分 一个选人框主要有以下几个展示部分&#xff1a; 人员组织树已选节点信息操作工具栏 这三大部分再细分下各自应有的基础功能&#xff1a; 人员展示部分&…

解读华为的流程与 IT 管理部门

公众号回复&#xff1a;干货&#xff0c;领取价值58元/套IT管理体系文档 公众号回复&#xff1a;ITIL教材&#xff0c;领取最新ITIL4中文教材 更多专业文档请访问 www.itilzj.com 华为&#xff0c;其流程与IT管理部是国内IT部门的发展标杆&#xff0c;负责的是华为各个部门和跨…

人员选择树,搜索自动筛选功能

要实现的功能截图&#xff1a; 要求&#xff1a; 1、点击收件人输入框可以根据拼音自动筛选数据&#xff0c;并且标记已经选择的数据&#xff0c;没有结果的时候提示&#xff0c;相应的更新左边树节点状态 2、勾选树右侧树的节点左侧输入框出现一一对应的节点名称 用到的…

级联选择器el-cascader处理复杂数据(四层、五层数据),回显部门以及部门下的人员

注意&#xff1a;参考第五层的数据处理比较nice 当级联选择器需要绑定的数组不再是简易数据&#xff0c;props涉及的字段不再是一个&#xff0c;而是列表里面套列表 比如&#xff0c;我想要获取部门以及下面的员工&#xff0c;如何显示&#xff1f;如下图1所示&#xff0c;后…

一个简单的联系人及组织架构选择人员的实现

前言&#xff1a;技术实现&#xff1a;Vitevue3tsvant。 本次主要是因为本人说了一个类似的功能&#xff0c;前期遇到了很多坑&#xff0c;导致 进度缓慢。虽然可以实现&#xff0c;但是都基于多个数组操作的情况&#xff0c;当涉及功能修改或优化&#xff0c;就很难实现了。 本…

jQuery仿钉钉组织架构的选择部门功能,移动端完美树形图

在网上找了好久&#xff0c;实在没见到有类似的只能自己写一个一、功能如图所示&#xff0c;点击选择无限下级功能 二、css <style>body{margin:0;padding:0;background:#ffffff}.spaceBetween {display: flex;justify-content: space-between;align-items: center;}.su…

vue的el-tree实现部门人员的tree展示选择,包括根据已有id进行默认选中设置

根据部门和人员&#xff0c;生成部门人员选择树&#xff0c;用的是Vue的el-tree生产树。 java部分------------------------------------------------------------------------------- 1&#xff1a;中间实体dto&#xff08;就是前端要的字段&#xff0c;让从数据库中查询的时…

Java+zTree审批人员选择

实现一个类似于钉钉审批人员选择的功能。 这里使用zTree实现组织架构树。 实体类需要有id,pId实现上下级关系。 company.java getter/setter略。 private int company_id;//公司idprivate String company_name;//公司名称private String remark;//备注信息private String f…

element-tree 实现部门-人员选择(支持ID相同)

使用element-tree实现id相同的选择 相同人员可在不同部门出现, 当勾选其中一个人员时,其它部门的相同人员也要勾选上右侧可进行删除已勾选人员, 并且树状图勾选状态取消若有勾选,进入时候默认选中 效果如下: 例如: 点击勾选总经办的王五,技术部的王五也要勾选上,右侧删除王五…

【Mobile Org】适用于移动端/H5的组织部门/角色/人员选择组件

Mobile Org Introduction 移动端组织架构数据选择方案&#xff0c;包括组织机构、角色以及人员等分类&#xff0c;支持单选、多选、关键字段自定义以及多种事件及插槽等&#xff0c;适用于大部分组织选人场景。 支持懒加载回调&#xff0c;点击获取当前组织下的子组织及人员…