
sunilwang
2022/09/14阅读:281主题:全栈蓝
前端换肤方案介绍和使用
前言
在项目开发过程中,前端开发人员经常会遇到切换不同主题风格的需求,接下来我们一起来讨论什么是换肤,以及常用的几种换肤方案。
一.什么是前端换肤
前端换肤最直观的就是颜色的切换,不同主题下,页面的主色调进行改变。
常见换肤需求分为两种:
-
静态换肤:提供可选择的主题样式或者颜色进行切换,主题选项比较固定; -
动态换肤:可以支持自定义色值,比如通过取色板颜色或者后端接口返回,选择范围不固定,比较广泛;
动态换肤缓存用户选择的主题,可以考虑以下几种方式:
-
利用路由进行标记; -
利用缓存:cookie 标记、localstorage、native 缓存的方法等; -
保存到后端服务器,接口获取;

二.换肤方案介绍
1. 利用 class 设置不同的命名空间
这种方法比较好理解,原理比较简单,但是维护不同的 class 代码容易混乱。手动设置不同的 class 类名就不再详细说,介绍一个工具glup-css-wrap
,可以为我们主题的外层包一个 class 来做命名空间,使用如下:
// 得到以.springtime-theme为命名空间的主题
var path = require('path')
var gulp = require('gulp')
var cleanCSS = require('gulp-clean-css');
var cssWrap = require('gulp-css-wrap');
var springtimeThemeName='.springtime-theme'
gulp.task('css-wrap', function() {
return gulp.src( path.resolve('./theme/index.css'))
.pipe(cssWrap({selector:springtimeThemeName}))
.pipe(cleanCSS())
.pipe(gulp.dest('dist'));
});
gulp.task('default',['css-wrap','move-font']);
2.加载不同的 css 文件进行换肤
定义不同的 css 文件,根据不同主题引入不同的 css 文件。
-
优点:比较好理解,一个 css 文件就是一个主题,实现比较简单; -
缺点:需要手动重写样式表,扩展性不高,不利于支持用户自定义,切换样式需要加载 css 文件,有一定的加载时间成本;
// theme-epipelagic.css
// theme-springtime.css
// theme-default.css
// js动态处理加载不同的css文件
function changeTheme(themeClass) {
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = '/css/theme-'+ themeClass +'.css';
document.getElementsByTagName("head")[0].appendChild(link);
}
const theme = window.localStorage.getItem(theme);
theme = theme ? theme[1] : "default";
changeTheme(theme);
3. 利用 css 预处理器生成多套换肤方案
利用 Less,stylus 或 sass 的变量代替颜色值,配置多个主题颜色配置, 使用 webpack 打包多个主题样式文件,根据主题,动态下载引入不同 css 文件进行换肤。
-
优点:扩展性强,不用在手写多套样式; -
缺点:难理解,配置繁琐,生成冗余代码,切换样式需要时间重新加载 css 文件,切换不及时,编译速度依赖客户端性能;
// scss文件 theme.scss
$color-springtime: {
primary-color: #b7f3ff;
}
$color-epipelagic: {
primary-color: #7bcfff;
}
@mixin back-color($key) {
background-color: map-get($color-springtime, $key);
[data-theme='epipelagic'] & {
background-color: map-get($color-epipelagic, $key);
}
}
// 页面中进行使用
@import 'theme.scss';
.page-loading {
background-size: 100% 100%;
@include text-color(primary-color)
}
参考文档:https://juejin.cn/post/6844903596992135182
4. css 自定义变量「推荐」
使用 CSS3 内置 variable,设置颜色,简单便捷,支持 JS 修改动态修改变量,使系统颜色变化。
-
优点:代码简单易懂,只需要一套 CSS 就可自动适配多种主题色,而且页面不会刷新,可自动配置所有的颜色,换肤不需要延时等待; -
缺点:兼容性不好(IE 完全不兼容),无法支持复杂表达式;
可以通过安装
css-vars-ponyfill
解决兼容性问题,css-vars-ponyfill
的概念解释(自行翻译): A ponyfill that provides client-side support for CSS custom properties (aka "CSS variables") in legacy and modern browsers

代码示例:
-
css 变量生成不同的 css 文件
html[data-theme='epipelagic']:root {
--primary-color: #7bcfff;
--second-color: #00a2ff;
}
html[data-theme='springtime']:root {
--primary-color: #35BB9A;
--second-color: #096951;
}
-
主题进行切换, 封装 theme-manage.js
:
import cssVars from 'css-vars-ponyfill';
let currentTheme = '';
const onThemeChange = (themeClass) => {
const rootElement = document.documentElement;
const currenntThemeClass = themeClass || currentTheme;
rootElement.setAttribute('data-theme', currenntThemeClass);
};
const applyTheme = async (tempThemeName: string) => {
let theme = tempThemeName;
// 通过url的参数获取当前theme,也可以通过缓存、请求等方式
theme = GetQueryString('theme');
if (currentTheme !== theme) {
currentTheme = theme;
onThemeChange();
}
};
// 引入css-vars-ponyfill插件做兼容性问题处理
cssVars({
rootElement: document,
shadowDOM: false,
onlyLegacy: true,
// it will use mutation observer to watch changes, because it supports safari(6-latest) and chrome(27-latest)
// so we needn't import the polyfill for mutation observer currently
watch: true,
// onBeforeSend(xhr, elm, url) {
// },
// onWarning(message) {
// },
// onError(message, elm, xhr, url) {
// },
// onSuccess(cssText, elm, url) {
// },
// onComplete(cssText, styleElms, cssVariables, benchmark) {
// }
});
-
页面初始化的时候引入 css 文件,并且调用初始化主题方法
import '@/assets/styles/theme/epipelagic.css';
import '@/assets/styles/theme/springtime.css';
import themeManager from '@/service/theme-manager';
themeManager.applyTheme();
// 页面样式中用css变量标记样式
.order-detail-bg {
background-color: var(--primary-color);
background-size: 100% 100%;
}
.busiunessInfo-content p {
color: var(--second-color);
}
页面效果:


5. element-ui 中的动态换肤方案
ElementUI 方案, 开启暴力模式,生成一套主题,将主题配色配置写在 js 中,脚本替换颜色变量,需要使用主色,计算辅色,浏览器中用 js 动态修改 style 标签覆盖原有的 css,暴力修改应用。
-
实现的大致思路(来自 ElementUI 官网:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue):
-
先把默认主题文件中涉及到颜色的 CSS 值替换成关键词:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L250-L274; -
根据用户选择的主题色生成一系列对应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/formula.json; -
把关键词再换回刚刚生成的相应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/color.js; -
直接在页面上加 style 标签,把生成的样式填进去:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L198-L211;
-
优点:通过定义函数的形式自动替换,操作性比较强; -
缺点:灵活性不够,粗暴简单,实现难度比较高,需要有统一打包出来的 index.css;
-
-
ElementUI 官网上也为我们提供了自定义的主题方案:https://element.eleme.io/#/zh-CN/component/custom-theme 具体的实现:
-
安装主题生成工具
npm i element-theme -g
-
安装 element-UI 的白垩主题
/* 从 npm */
npm i element-theme-chalk -D
/* 从 GitHub */
npm i https://github.com/ElementUI/theme-chalk -D
-
初始化变量文件,主题生成工具安装成功后,可以全局安装的可以在命令行中通过 et 调用工具,如果是当前目录安装可以通过 node_modules/.bin/et 访问到命令。初始化变量文件,默认会输出 element-variables.scss,也可以指定参数,指定文件输出的目录:
// 或者node_modules/.bin/et -i
// 如果这一步遇到报错ReferenceError: primordials is not defined,解决办法:npm i element-themex -g
et -i [可以自定义变量文件]
✔ Generator variables file
-
element-variables.scss 文件内容:
/* Element Chalk Variables */
// Special comment for theme configurator
// type|skipAutoTranslation|Category|Order
// skipAutoTranslation 1
/* Transition
-------------------------- */
$--all-transition: all .3s cubic-bezier(.645,.045,.355,1) !default;
$--fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--fade-linear-transition: opacity 200ms linear !default;
$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--border-transition-base: border-color .2s cubic-bezier(.645,.045,.355,1) !default;
$--color-transition-base: color .2s cubic-bezier(.645,.045,.355,1) !default;
/* Color
-------------------------- */
/// color|1|Brand Color|0
$--color-primary: #409eff !default;
/// color|1|Background Color|4
$--color-white: #FFFFFF !default;
/// color|1|Background Color|4
$--color-black: #000000 !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */
...
// 太多就不放在这里展示了
-
直接编辑 element-variables.scss 文件,比如修改$--color-primary 主题颜色 -
编译主题,命令行里执行 et 编译主题
/*
-w 开启watch模式
-c 指定自定义变量参数
-o 指定打包目录
*/
et
✔ build theme font
✔ build element theme
6. ant-design 中换肤方案
ant.design 官网中切换主题,是在 html 标签加里 color-scheme 和在 body 里添加自定义标签 data-theme="dark",和:root 配合改变。
CSS 属性允许元素指示它可以轻松呈现的配色方案,操作系统配色方案的常见选择是“亮”和“暗”,或者是“白天模式”和“夜间模式”,antd 的样式使用了 Less 作为开发语言,并定义了一系列全局/组件的样式变量,我么根据需求动态修改 css 变量实现换肤。
所有的 antdesign 的样式变量:
https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less

-
静态主题的方式
// 1. 在webpack中定制主题
// webpack.config.js
module.exports = {
rules: [{
test: /\.less$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader', // translates CSS into CommonJS
}, {
loader: 'less-loader', // compiles Less to CSS
+ options: {
+ lessOptions: { // 如果使用less-loader@5,请移除 lessOptions 这一级直接配置选项。
+ modifyVars: {
+ 'primary-color': '#1DA57A',
+ 'link-color': '#1DA57A',
+ 'border-radius-base': '2px',
+ },
+ javascriptEnabled: true,
+ },
+ },
}],
// ...other rules
}],
// ...other config
}
// 2. 配置less变量文件进行覆盖
@import '~antd/es/style/themes/default.less';
@import '~antd/dist/antd.less'; // 引入官方提供的 less 样式入口文件
@import 'your-theme-file.less'; // 覆盖上面定义的变量
-
动态主题的方式
// 替换引入 antd.variable.min.css
-- import 'antd/dist/antd.min.css';
++ import 'antd/dist/antd.variable.min.css';
// 调用 ConfigProvider 配置方法设置主题色:
import { ConfigProvider } from 'antd';
ConfigProvider.config({
theme: {
primaryColor: '#25b864',
},
});
三.附加:其他换肤方案
-
我们在 app 中会看到页面整体会变为灰色的情况,可以通过这个属性可以实现: html { filter:grayscale(1) }


-
小程序如何进行换肤 因为小程序技术的特殊性,所以传统的前端换肤方案没法在小程序中使用,这里提供几种小程序的换肤方案供参考:
-
如果没有线上存在多套皮肤的需求,可以抽取颜色变量通过线下编译修改主题色; -
如果有线上多套皮肤的需求,则采用传统前端的多套 CSS 皮肤方案加更改类名的方式; -
针对动态换肤,后端接口返回色值字段,前端通过内联方式对页面元素进行色值设置;
参考文档:https://zhuanlan.zhihu.com/p/407434343?utm_source=wechat_session&utm_medium=social&utm_oi=1350733647192981504
作者简介
❝屈会敏:出发永远是最有意义的事情,去做就对了
❞
作者介绍
