关于本文

来自:codercao

https://juejin.cn/post/7106702604024938503

demo在线体验地址:https://hongqingcao.github.io/v-theme-colors/ 源码地址:https://github.com/HongqingCao/v-theme-colors(ps 大部分功能暂未同步发布)

一、换肤

网站或者应用一键切换主题(简称:换肤)功能,对每个前端开发者来说已经非常常见了,通常是一深一浅,或自由组合衍生出众多主题,或任意主题,这时候,设计一个工程化主题切换功能,并梳理现代前端样式的解决方案显得尤为重要。

二、换肤调研

很久以前,通常的做法是每个颜色主题块编写样式表,切换的时候对应去切换。现代前端主题切换——目前主流的方案往往通过 CSS 变量(CSS 自定义属性)[1]来实现,将主题有关的颜色,通过业务和语义化的方式命名。接下来我们看看前端圈比较知名的UI他们是怎么做的:

(1)ElementUI[2]

我们可以看到 element-plus 官网,主题切换主题,是在html标签加里 class="dark" 涉及到主题的变量,基于html.dark,和:root配合改变。当然我们也可通过源码看到element-plus是用的是scss。

image.png

(2)ant.design[3]

我们可以看到 ant.design 官网,主题切换主题,是在html标签加里 color-scheme 和在body里添加自定义标签data-theme="dark",和:root配合改变。CSS 属性允许元素指示它可以轻松呈现的配色方案,操作系统配色方案的常见选择是“亮”和“暗”,或者是“白天模式”和“夜间模式”。当用户选择其中一种配色方案时,操作系统会对用户界面进行调整。这包括表单控件、滚动条和 CSS 系统颜色的使用值。通过源码,我们也可以看到ant用的是less。

image.png

三、换肤痛点和思考

(1) 正如上文ElementUI和ant,都分别借助不同的CSS预处理器(sass和less)来组织代码,再微前端盛行的时代,怎么设计一套通用的多团队可用,并且去css预处理的换肤CSS 自定义属性?

(2) 谁来维护不同主题色,研发和设计之间,如何保持不同主题色值的同步沟通?

(3) 如何最小化前端工程师的开发量,不需要做多份主题色?
(4) ...

基于以上考虑,举个例子,我们希望做到在开发时,编写:

.text {color: var(--c-color)
}
复制代码

这样就可以一劳永逸——直接支持两套或者多套主题模式。

但是业务往往是千变万化的,正如我司:

(1)换肤的需求是,有一个色调(深浅),然后,根据深浅,衍生出很多 主题色,比如深蓝、深黄、深红、浅蓝、浅黄、浅红...

(2)对于深浅来说,有定于出一些基础色,对于组件颜色来说,通常用一组基础色即可满足,但是业务页面中可能涉及到千变万化的颜色...

四、换肤架构

image.png

正如上图,我们可以把换肤提升到一个平台或中台来:

(1)对于UED同学,他们可以自己去配置换肤相关的系统色系,衍生的主题色,系统色系色系基础色

(2)对于各个前端团队来说,可以通过主题色,色系基础色,任意自定义和配置 自己业务需要颜色的变量

image.png

五、换肤技术选型和实现

笔者这里是用基于css-vars-ponyfill[4]的换肤方案,至于它的优势正如它们官方所描述,在传统和现代浏览器为css自定义属性 提供客户端支持的 pnyfill。

image.png

image.png

【1】该方案的亮点和规则

(1)纯JS实现,对外暴露initThemes初始化方法,不依赖CSS预处理器(sass和less),兼容ie9

(2)抽离深浅色系基础色(统一治理输出),以及主题色,混合颜色(黑白色)都可以动态接口获得

(3)统一规范业务色常量命名,JS定义自定义函数方法 1、 Mix函数实现媲美sass的颜色混合机制,2、十六进制和RGB(rgba)互相转换函数

(4)技术路线不抖,直接用var()函数使用,后期封装成JS库 ,皮肤配置中台,可以提供给各个团队使用

(5)关于业务自定义变量,设计有两个治理方案:(1)全局变量, 全局单独维护(2)局部业务变量,局部单独维护

【2】核心原理

(1)在应用端触发换肤操作的时候,配合 JavaScript 状态管理,同步主题切换的信号,对应对应触发initThemes 方法

// 测试新主题let varList = {...colorColor}let tPrimaryList = themePrimaryListinitThemes('', tPrimaryList, varList, '')
复制代码

(2)切换该主题色甚至该业务下的变量对应的值,通过css-vars-ponyfill,把自定义常量打到对应的DOM节点(通常是html或者body下),从而实现切换主题

theme.js

import cssVars from "css-vars-ponyfill";
import { themeTypeList, themePrimaryList } from "./themeList.js";
import { mix, hex2rgb } from "./com/util";
/*** initThemes 全局初始化 主题* @param theme 主题 [必填]* @param tPrimaryList 主题列表[必填] array ['theme1','theme2']* @param valList 自定义主题列表 {val1:['theme1-color','theme2-color']} ....* @param themeType 主题类型  -深浅 ....* @param changeType 区分改的是主题类型,还是主题色 [ 预留字段 ]....* @returns {boolean}*/export const initThemes = (theme, tPrimaryList, varList, themeType) => {let variables = getVariables(theme || "lightBlue",tPrimaryList,varList,themeType);cssVars({watch: true, // 当添加,删除或修改其或元素的禁用或href属性时,ponyfill将自行调用variables: variables, // variables 自定义属性名/值对的集合onlyLegacy: false, // false  默认将css变量编译为浏览器识别的css样式  true 当浏览器不支持css变量的时候将css变量编译为识别的css});
};
复制代码

themeList.js 这里存放一些假设我们在应用端设置的一些主题和色系(深浅)基础色

import { light } from './com/light'
import { dark } from './com/dark'// 主题 - 主题色
export const themePrimaryList = {dark: [{color: '#FFAA0E',name: '深黄',theme: 'darkYellow'},{color: '#FFAA0E',name: '深蓝',theme: 'darkBlue'},],light: [{color: '#FFAA0E',name: '深黄',theme: 'lightYellow'},{color: '#256DFF',name: '浅蓝',theme: 'lightBlue'}]
}export const themeTypeList = {dark: dark,light: light,
}
复制代码

【3】色组 & 色值平台设计

对于前端使用者来说,我们只需要关注 具体有哪些常量,和怎么去定于常量。例如我司,我们基于业务的定义了一些常用的语义化的一些业务常量,例如

// 功能色"--c-primary": color.C00, // 主题色"--c-primary-rgb": hex2rgb(color.C00), // 主题色RGB"--c-primary-hover": mix(white, color.C00, 12),"--c-primary-active": mix(black, color.C00, 12),"--c-fill-primary": mix(white, color.C00, 88), //主题色文字的背景填充色"--c-border-primary": mix(white, color.C00, 80), //主题色文字的边框色"--c-primary-mix-1": mix(white, color.C00, 10),"--c-primary-mix-2": mix(white, color.C00, 20),"--c-primary-mix-3": mix(white, color.C00, 30),"--c-primary-mix-4": mix(white, color.C00, 40),"--c-primary-mix-5": mix(white, color.C00, 50),"--c-primary-mix-6": mix(white, color.C00, 60),"--c-primary-mix-7": mix(white, color.C00, 70),"--c-primary-mix-8": mix(white, color.C00, 80),"--c-primary-mix-9": mix(white, color.C00, 90),"--c-success": color.C01, // 成功色"--c-warning": color.C01, // 警告色"--c-error": color.C01, // 错误色"--c-green": color.C06, // 语义绿 跌"--c-green-rgb": hex2rgb(color.C06),"--c-green-hover": mix(white, color.C06, 12),"--c-green-active": mix(black, color.C06, 12),"--c-red": color.C08, // 语义红 涨"--c-red-rgb": hex2rgb(color.C08),"--c-red-hover": mix(white, color.C08, 12),"--c-red-active": mix(black, color.C08, 12),"--c-yellow": color.C07, // 语义黄"--c-yellow-rgb": hex2rgb(color.C07),"--c-yellow-hover": mix(white, color.C07, 12),"--c-yellow-active": mix(black, color.C07, 12),// 文字色"--c-text": color.C02, // 一般文本"--c-text-title": color.C02, // 标题"--c-text-subtitle": hex2rgb(color.C02, 0.65), // 次要 - 副标题"--c-text-info": hex2rgb(color.C02, 0.45), // 提示"--c-text-placeholder": color.C02, // 占位文本色"--c-text-link": color.C01, // 链接文本色"--c-text-disable": hex2rgb(color.C02, 0.3), // 禁止或失效// 填充色"--c-fill": color.C04, // 组件默认背景颜色"--c-fill-body": color.C11, // 页面背景"--c-fill-shadow": hex2rgb(color.C11, 0.1), // 阴影"--c-fill-zebra": color.C05, // 斑马线色"--c-fill-mask": color.C11, // 遮罩背景"--c-fill-disable": hex2rgb(color.C02, 0.07), // 禁止"--c-fill-scroll": hex2rgb(color.C02, 0.5), // 滚动条色"--c-fill-scroll-hover": mix(white, color.C02, 12),"--c-fill-scroll-active": mix(black, color.C02, 12),// 边框/分割线颜色"--c-border": color.C01, // 基本边框色"--c-border-line": hex2rgb(color.C02, 0.07), // 分割线"--c-border-light": hex2rgb(color.C02, 0.12), // 浅边框色 (小边框)"--c-border-lighter": hex2rgb(color.C02, 0.07), // 更浅色"--c-border-disable": hex2rgb(color.C02, 0.04), // 禁用边框// 图标色"--c-icon": hex2rgb(color.C02, 0.65),"--c-icon-hover": color.C09,"--c-icon-active": color.C01,"--c-icon-down": color.C10,// 标签色// ...getTabColor(color),// 业务自定义- 写在业务方变量//由主题切换的时候,动态传入入的 自定义变量列表 varList...getBusinessVars(theme, type, varList, tPrimaryList),复制代码

使用的时候只需要熟悉这些语义化的常量即可,当然我们也设计了一个可视化页面,可以看到全量的自定义变量,对应的颜色,这样更为方便全局查看。

当然至于上面的混合代码,可能各位看着有些奇怪,这是我们这边UED同学为了减少颜色设计了一套颜色规范(例如 悬浮色,根据8.8成默认色和1.2成白色混合计算得出;按下色根据8.8成默认色和1.2成黑色混合计算得出 ),例如混合Mix函数(颜色混合 规则符合 scss - mix),剩余的就是RGB和十六进制颜色互相转换 这类的函数

【4】获取在当前主题自定义变量颜色

自定义变量颜色:对于业务来说,可能基础色并不能满足所有业务的颜色覆盖,或者每种主题下基础色并不能一一对应,此时自定义变量颜色,这个功能变得必不可少。主要原理也是根据每种主题可以自己填写对应的业务需要的颜色(极端情况),这边也分以下两种情景:

(1)完全自定义常量多态,即一种主题色自定义常量都有对应一种颜色

例如 系统主题有四种 [dark1,dark2,light1,light2] ,某个业务背景色,我们定义了一个常量 --color-codercao-fill01 它在:
dark1主题下的颜色是一个基础色 dark.C01
dark2主题下的颜色是一个基础色 dark.C02
light1主题下的颜色是一个基础色 light.C03
light2主题下的颜色是 #fff

此刻,这种情况,便是比较极端情况,每个主题下,我们自定义的常量对应四个主题有四种颜色并且颜色毫无规律可言,可能是基础色,可能是任意色。
--color-codercao-fill01 :['dark.C01','dark.C02','light.C03','#fff']

(2)只和深浅相关自定义常量多态,只和深浅基础色有关的颜色

例如 系统主题有四种 [dark1,dark2,light1,light2] ,某个业务背景色,我们定义了一个常量 --color-codercao-fill02 它在:
dark1dark2深色主题下的颜色是一个基础色 #555
light1light2浅色主题下的颜色是一个基础色 #fff

此刻,这种情况,我们自定义的常量对应四个主题有只有两种颜色,这两种可能是基础色,可能是任意色。
--color-codercao-fill02 :['#555','#fff']

// 主题变量color.vue// (1)完全自定义常量多态,一种主题色对应一种颜色"--color-codercao-test0": mergeColor(["#444", "#666"],["#444", "#666"],type,theme,tPrimaryList), // (2)只和深浅相关自定义常量多态,只和深浅基础色有关的颜色"--color-codercao-test1": mergeColor([dark.C01], [light.C02], type), 复制代码

我们就需要根据主题切换的时候,动态去计算,于是我们就要设计一个计算颜色的方法,它拿到各个主题下的颜色,主题,主题列表,甚至主题类型去计算在当前主题下这个变量的颜色具体用哪个颜色

/*** mergeColor 获取在当前主题下该变量(自定义)的颜色* @param darkList [必填]  自定义常量在不同主题下的 深色系颜色列表   array ['theme1','theme2']* @param lightList [必填]  自定义常量在不同主题下的 浅色系颜色列表   array ['theme1','theme2']* @param type 主题类型  深 - 浅* @param theme 主题色-名* @param tPrimaryList 主题列表* @returns {boolean}*/const mergeColor = (darkList, lightList, type, theme, tPrimaryList) => {let colorList = type == "dark" ? darkList : lightList;let color = colorList[0],index = 0;// 如果 type 有值说明 该自定义主题常量,只和深浅基础色 (两种)有关if (!theme) {color = type == "dark" ? darkList[0] : lightList[0];} else {// 否则认为是 一种主题 一种色值index = getThemeIndex(theme, tPrimaryList);color = colorList[index];}return color;
};
复制代码

另外还有一种极端情况,是可以把css变量自定义打在对应的DOM上。

接下来我们就可以愉快的换肤玩耍了~

color.gif

六、总结

本换肤方案基于css-vars-ponyfill插件用纯JS编写换肤核心功能,不依css赖预处理,主要是通过高度抽离基础色,然后转换为比较语义化的业务(功能)变量色,然后配合主题,对应去改变具体的颜色,并预留自定义变量的功能,让换肤更有灵魂。当然你有更好的想法可以评论区一起探索和分享。

demo在线体验地址: https://hongqingcao.github.io/v-theme-colors/ 源码地址: https://github.com/HongqingCao/v-theme-colors(ps 大部分功能暂未同步发布)

往期回顾

#

如何使用 TypeScript 开发 React 函数式组件?

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

试着换个角度理解低代码平台设计的本质

【Web技术】1431- 总结前端主题切换的思考和现代前端样式的解决方案落地相关推荐

  1. 【Web技术】1374- 纯 JS 实现灵活的前端主题切换功能

    demo在线体验地址:https://hongqingcao.github.io/v-theme-colors/ 源码地址:https://github.com/HongqingCao/v-theme ...

  2. 用WEB技术栈开发NATIVE应用(二):WEEX 前端SDK原理详解

    摘要: WEEX依旧采取传统的web开发技术栈进行开发,同时app在终端的运行体验不输native app.其同时解决了开发效率.发版速度以及用户体验三个核心问题.那么WEEX是如何实现的?目前WEE ...

  3. 【Web技术】1424- 4 种在页面关闭时上传监控数据的解决方案

    来自:掘金,作者:我是leon 链接:https://juejin.cn/post/7106365076197605413 概览 本文以 "前端监控上报数据" 的业务场景,重点解析 ...

  4. 【Web技术】623- 简单好用的前端深色模式/主题化开发方案

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design  Ng组件库:ng-devui( ...

  5. 前端世界起争端,你是现代 Web 技术体系的坚定捍卫者吗?

    [CSDN 编者按]前段时间,Mapbox 工程师 Tom MacWright 的一篇对现代前端技术的"二次思考"文章吐槽了现代前端框架给当代前端带来的一些问题,在前端世界掀起了一 ...

  6. Blazor Web Assembly (WASM) 主题切换

    目录 介绍 灵感 设计 实现 1. 切换样式表 2. CSS变量 3. 主题切换 4. 将按钮链接到开关 编码 主题库 1. ThemeToggle组件 2. ThemeService(核心) 3a ...

  7. 有关前端性能优化的方案—Vue 代码层面性能优化+Webpack 层面的优化+基础的 Web 技术优化+非框架代码优化

    文章目录: 一.代码层面的优化 1.1.v-if 和 v-show 区分使用场景 1.2.computed 和 watch 区分使用场景 1.3.v-for 遍历必须为 item 添加 key,且避免 ...

  8. 前端web 技术盘点

    尽管前端技术在无线领域受到了挫折,但这无法减缓其发展势头.在基础技术方面,规范和标准的发展.浏览器的快速演进为将来的Web应用打好了根基:随着网站规模的进一步变大,交互变得更复杂,大家更关注用新的开发 ...

  9. web技术分析| 一篇前端图像处理秘籍

    在短视频和直播带货霸占了流量铁王座的当下,产品之间的竞争十分激烈,在抖音短视频问世以来,随其后效仿的产品也不计其数.产品要想脱引而出,仅靠画质和流畅度是很难取胜的,不仅要节省流量开支同时还要保证画质的 ...

最新文章

  1. React中跨域问题的完美解决方案
  2. python画折线图代码-python画折线示意图实例代码
  3. Win7实用技巧之五库功能妙用
  4. Hibernate工作原理
  5. host文件知识详解
  6. C++ setw和setfill
  7. 在mac上用文本编辑器写python_Mac系统Python解释器、PyCharm编辑器安装及使用方法详解...
  8. 爬虫文档学习 xpath bs4 selenium scrapy...
  9. 冠状病毒过后世界九大未来预测
  10. 网络爬虫程序 *版本1.0.1* 修正几个问题
  11. ipv6单播地址包括哪两种类型_IPV6中为啥没有ARP了呢?一文带你搞懂NDP邻居发现协议...
  12. Java【第六篇】面向对象基础
  13. 【VSCode PlatformIO】 STC单片机开发头文件制作与添加方法
  14. java ape切割_无损分解和保持依赖的判断
  15. 关于大创项目的初期思考2020.11.14
  16. windows防火墙开端口失败
  17. 自己做量化交易软件(1)通通量化分析环境安装使用
  18. 连接手表_小米手表首批已到手 这些事儿你必须知道
  19. iShot--长截图的首选工具
  20. ubuntu 配置 vino-server

热门文章

  1. 数据结构课程设计——通讯录管理系统
  2. 使用jpedal解析PDF到XML
  3. python 爬取链家成交房数据案例
  4. linux电子数码相册实验报告,Linux实验报告.doc
  5. HE网站系统架设过程思路
  6. 2022 年牛客多校第四场补题记录
  7. vmware、操作系统、数据库软件、oracle 补丁集地址下载
  8. **京东撸货是什么,京东撸货具体怎么玩,能不能赚钱,我来告诉你**
  9. 拉勾网主页面HTML+CSS布局代码,commen.css+reset.css代码部分
  10. Vue中使用友盟CNZZ事件统计