sunilwang

V1

2022/09/14阅读:281主题:全栈蓝

前端换肤方案介绍和使用

前言

在项目开发过程中,前端开发人员经常会遇到切换不同主题风格的需求,接下来我们一起来讨论什么是换肤,以及常用的几种换肤方案。

一.什么是前端换肤

前端换肤最直观的就是颜色的切换,不同主题下,页面的主色调进行改变。

常见换肤需求分为两种:

  1. 静态换肤:提供可选择的主题样式或者颜色进行切换,主题选项比较固定;
  2. 动态换肤:可以支持自定义色值,比如通过取色板颜色或者后端接口返回,选择范围不固定,比较广泛;

动态换肤缓存用户选择的主题,可以考虑以下几种方式:

  • 利用路由进行标记;
  • 利用缓存: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-colormap-get($color-springtime, $key);
    [data-theme='epipelagic'] & {
          background-colormap-get($color-epipelagic, $key);
    }
}
// 页面中进行使用
@import 'theme.scss';
.page-loading {
  background-size100% 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({
  rootElementdocument,
  shadowDOMfalse,
  onlyLegacytrue,
  // 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
  watchtrue,
  // 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-colorvar(--primary-color);
  background-size100% 100%;
}
.busiunessInfo-content p {
  colorvar(--second-color);
}

页面效果:

效果1
效果1
效果2
效果2

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;
  1. 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-transitionall .3s cubic-bezier(.645,.045,.355,1) !default;
$--fade-transitionopacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--fade-linear-transitionopacity 200ms linear !default;
$--md-fade-transitiontransform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--border-transition-baseborder-color .2s cubic-bezier(.645,.045,.355,1) !default;
$--color-transition-basecolor .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-1mix($--color-white, $--color-primary, 10%) !default/* 53a8ff */
$--color-primary-light-2mix($--color-white, $--color-primary, 20%) !default/* 66b1ff */
$--color-primary-light-3mix($--color-white, $--color-primary, 30%) !default/* 79bbff */
$--color-primary-light-4mix($--color-white, $--color-primary, 40%) !default/* 8cc5ff */
$--color-primary-light-5mix($--color-white, $--color-primary, 50%) !default/* a0cfff */
$--color-primary-light-6mix($--color-white, $--color-primary, 60%) !default/* b3d8ff */
$--color-primary-light-7mix($--color-white, $--color-primary, 70%) !default/* c6e2ff */
$--color-primary-light-8mix($--color-white, $--color-primary, 80%) !default/* d9ecff */
$--color-primary-light-9mix($--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. 静态主题的方式
// 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'// 覆盖上面定义的变量
  1. 动态主题的方式
 // 替换引入 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',
      },
  });

三.附加:其他换肤方案

  1. 我们在 app 中会看到页面整体会变为灰色的情况,可以通过这个属性可以实现: html { filter:grayscale(1) }
css效果
css效果
filter的其他特性和对应效果
filter的其他特性和对应效果
  1. 小程序如何进行换肤 因为小程序技术的特殊性,所以传统的前端换肤方案没法在小程序中使用,这里提供几种小程序的换肤方案供参考:
  • 如果没有线上存在多套皮肤的需求,可以抽取颜色变量通过线下编译修改主题色;
  • 如果有线上多套皮肤的需求,则采用传统前端的多套 CSS 皮肤方案加更改类名的方式;
  • 针对动态换肤,后端接口返回色值字段,前端通过内联方式对页面元素进行色值设置;

参考文档:https://zhuanlan.zhihu.com/p/407434343?utm_source=wechat_session&utm_medium=social&utm_oi=1350733647192981504

作者简介

屈会敏:出发永远是最有意义的事情,去做就对了

分类:

前端

标签:

CSS

作者介绍

sunilwang
V1