Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在线主题切换 #32

Open
liyongning opened this issue Jun 28, 2022 · 0 comments
Open

在线主题切换 #32

liyongning opened this issue Jun 28, 2022 · 0 comments
Labels
前端 前端知识

Comments

@liyongning
Copy link
Owner

在线主题切换

在线主题切换的本质就是通过 JS 替换主题 link 标签的 href 属性,加载对应主题的样式包。

样式包可以是多套 CSS 样式,也可以是由 CSS 变量组成的主题包。

多套 CSS 样式

优点 是简单、易于理解,缺点 也很明显,可维护性差、扩展性差、开发工作量大(需要研发同学为系统开发多套样式)。可阅读下面的示例代码感受一下

index.html

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>multi theme</title>
    <!-- 主题包,通过 JS 动态切换 link 的 href 值 -->
	<link rel="stylesheet" class="theme" href="./blue.css">
</head>

<body>
	<div class="theme div-ele">
		multi theme
	</div>
	<button onclick="toggleRedTheme()">red theme</button>
	<button onclick="toggleBlueTheme()">blue theme</button>

	<script>
        // 切换红色主题
		function toggleRedTheme() {
			document.querySelector('.theme').setAttribute('href', './red.css')
		}

        // 切换蓝色主题
		function toggleBlueTheme() {
			document.querySelector('.theme').setAttribute('href', './blue.css')
		}
	</script>
</body>
</html>

blue.css

/* 蓝色主题 */
.div-ele {
	width: 200px;
	height: 200px;
	line-height: 200px;
	text-align: center;
	color: #fff;
	margin-bottom: 20px;
	background-color: blue;
}

red.css

/* 红色主题 */
.theme {
	width: 200px;
	height: 200px;
	line-height: 200px;
	text-align: center;
	color: #fff;
	margin-bottom: 20px;
	background-color: red;
}

当有一天你接手了类似的一个老旧系统,产品过来跟你说,我们现在需要给系统新增一套样式,全新的 UI 设计稿已经出来了,你抽时间做一下吧。这里大家要搞清楚的是,这不是一个抽时间就能完成的简单需求,这意味着你需要为系统重写一套新的样式,比如叫 yellow.css

首先你需要复制已有样式,然后在浏览器中对照设计稿挨个去修改相关样式代码,并将修改同步到代码文件中,对于一个大型系统来说,这个工作量真的是......

针对上面的问题,有没有什么优化办法呢?一个呼之欲出的答案就是样式抽离。

当你新增或修改已有 UI 样式时,其实修改的只是部分样式,比如背景色、字体颜色、边框色等,这部分经常被修改的样式我们称为主题样式,你需要将这些样式找出来。这里难在寻找样式,就像大海捞针,那能否把相关样式抽出来呢?就像写代码一样,将公共逻辑抽离,然后在各个地方复用。

Sass 变量

这里我们需要借助 CSS 预编译语言去实现,比如 Sass。将多套 CSS 样式中的公共样式(主题样式)抽离,通过 Sass 变量维护公共样式,每次新增或修改主题时,只需要修改主题变量文件,然后重新编译生成新的 CSS 样式。也就是说这里的样式需要通过 Sass 或 Less 语言编写,然后编译成 CSS,因为浏览器只认识 CSS。

这样就进一步提升了系统的可维护性和扩展性,也降低了主题样式的开发工作量。

index.html

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>multi theme</title>
    <!-- 主题包,通过 JS 动态切换 link 的 href 值 -->
	<link rel="stylesheet" href="./blue.css">
</head>

<body>
	<div class="div-ele">
		multi theme
	</div>
	<button onclick="toggleRedTheme()">red theme</button>
	<button onclick="toggleBlueTheme()">blue theme</button>

	<script>
        // 切换红色主题
		function toggleRedTheme() {
			document.querySelector('.theme').setAttribute('href', './red.css')
		}

        // 切换蓝色主题
		function toggleBlueTheme() {
			document.querySelector('.theme').setAttribute('href', './blue.css')
		}
	</script>
</body>

</html>

var.scss

// blue theme
$backgroundColor: blue;

// red theme
// $backgroundColor: red;

index.scss

@import './var.scss';

.div-ele {
	width: 200px;
	height: 200px;
	line-height: 200px;
	text-align: center;
	color: #fff;
	margin-bottom: 20px;
	background-color: $backgroundColor
}

index.scss 编译后生成如下两套样式

blue.css

/* 蓝色主题 */
.div-ele {
  width: 200px;
  height: 200px;
  line-height: 200px;
  text-align: center;
  color: #fff;
  margin-bottom: 20px;
  background-color: blue;
}

red.css

/* 红色主题 */
.div-ele {
  width: 200px;
  height: 200px;
  line-height: 200px;
  text-align: center;
  color: #fff;
  margin-bottom: 20px;
  background-color: red;
}

这时候当产品说新增一套黄色样式的时候,我只需要在 var.scss 文件中修改对应的主题样式,然后编译生成 yellow.css 样式文件即可。

像 ElementUI、Ant Design 就是这样的思路,样式包中内置主题样式变量文件,比如 var.scss,文件中维护了大量的样式变量,如果你需要定制自己的主题样式,只需要修改这个变量文件,然后重新编译组件库,发版就可以了。

当然了,这里抛开技术之外,有一个跨团队合作的问题(研发、设计、产品),你需要协调三个团队的资源去完成这件事,让产品和设计同学合作,根据业务和产品特点为团队出一套 UI 规范,研发同学根据 UI 规范完成多主题样式的研发。

但这里存在一个问题,组件库都支持按需打包,只打包使用到的组件和组件的样式。但是当业务系统需要支持多主题时,组件库就没办法再提供样式的按需打包了。

首先,组件库多主题需要配置不同的样式变量(var.scss)文件,然后编译生成多套样式,将样式包独立发布。

业务系统在使用组件库时,手动引入样式包,不能再使用组件库的样式按需打包能力,因为业务系统切换主题样式是发生在运行时,而按需打包是发生在编译时。

运行时切换主题的方式和 多套 CSS 样式 一样,也是通过 JavaScript 操作 link 标签,完成样式的替换,所以该方案算是第一个方案的一个优化。

CSS 变量

Sass 变量的方案虽然提升了可维护性和可扩展性,但是却导致另外一个问题,组件库丢失了样式按需打包的能力。

而丢失的原因是因为主题切换发生在运行时,但是组件库的样式却需要在编译期将 Sass 编译为 CSS,两者具有不同的运行时段,所以结合起来使用就导致无法使用组件库样式按需打包的能力。

这时候就需要想有没有什么办法能让两者发生在同一时刻,比如都发生在运行时或编译时,可惜编译时暂时还没什么好的方案,但是运行时可以使用 CSS 变量的方式。

CSS 变量是 CSS 的新功能,可以简单理解为原生支持像 Sass、Less 语言的变量能力,从 2017 年 3 月份之后,所有主要浏览器已经都支持了 CSS 变量。

所以这里的方案就是 CSS 变量,将所有主题样式抽离到独立的主题样式文件中,然后在运行时通过 JavaScript 动态替换 link 标签。

CSS 变量基本使用

/* 最佳实践是将样式变量定义在根伪类 :root 下,这样就可以在 HTML 文档的任何地方访问到定义的样式变量了,相当于全局作用域 */
:root {
	--backgroundColor: red;
}

.div-ele {
	/* 通过 var 函数来获取指定变量的值 */
	background-color: var(--backgroundColor);
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>multi theme</title>
    <!-- 主题包,通过 JS 动态切换 link 的 href 值 -->
	<link rel="stylesheet" class="theme" href="./blue.css">
    <!-- 系统样式,其中的主题样式使用 CSS 变量 -->
	<link rel="stylesheet" href="./index.css">
</head>

<body>
	<div class="div-ele">
		multi theme
	</div>
	<button onclick="toggleRedTheme()">red theme</button>
	<button onclick="toggleBlueTheme()">blue theme</button>

	<script>
        // 切换红色主题
		function toggleRedTheme() {
			document.querySelector('.theme').setAttribute('href', './red.css')
		}

        // 切换蓝色主题
		function toggleBlueTheme() {
			document.querySelector('.theme').setAttribute('href', './blue.css')
		}
	</script>
</body>

</html>

index.css

/* 系统样式,其中的主题样式使用 CSS 变量 */
.div-ele {
	width: 200px;
	height: 200px;
	line-height: 200px;
	text-align: center;
	color: #fff;
	margin-bottom: 20px;
    /* 使用 CSS 变量声明的主题样式 */
	background-color: var(--backgroundColor);
}

red.css

/* 红色主题 */
:root {
	--backgroundColor: red;
}

blue.css

/* 蓝色主题 */
:root {
	--backgroundColor: blue;
}

所以,当产品需要为系统新增一套黄色主题时,只需要增加一个 yellow.css 文件即可

yellow.css

/* 黄色主题 */
:root {
	--backgroundColor: yellow;
}

总结

以上就是常见的在线主题切换方案:开发多套 CSS 样式、基于 Sass 变量的多套 CSS 样式、CSS 变量。其本质就是在切换主题时通过 JS 替换主题 link 标签的 href 属性,加载对应主题的样式包。

  • 多套 CSS 样式
    • **优点:**简单、易理解,就是写多套主题
    • **缺点:**开发工作量大、维护难度大、扩展性差
  • 基于 Sass 变量优化后的多套 CSS 样式
    • **优点:**通过主题样式的抽离,开发工作量小,维护难度中等,扩展性好
    • **缺点:**组件库样式丢失了按需打包的能力,因为在线切换的整个主题包,维护难度中等,后期每次新增和修改主题样式都需要重新编译生成对应的主题样式
  • CSS 变量
    • **优点:**开发工作量小、易维护、扩展性好,浏览器原生支持
    • **缺点:**虽然主流浏览器都支持了,单相对上面两个方案来说是劣势,性能稍微优点没那么优秀

所以如果你的业务复杂度没那么高(一个页面有上万个 DOM 节点),浏览器兼容性也还好,CSS 变量可以成为你的首选方案,结合 Sass 等预编译语言去实现在线主题切换。

拓展

虽然现在有了多主题方案,但是在团队内如何很好的落地呢?

看到这个问题,你可能会想这还不简单?主题肯定是内置到组件库啊。嗯,没问题,这是一种应用场景,但这只是冰山一角。

大家要知道 CSS 主题样式的应用场景不止是组件库(基础 UI 库、业务组件库、物料库),更多的其实是在你的业务代码中,可以仔细想想,你平时开发时是不是需要写很多 CSS 代码,这些 CSS 代码中也会包含很多主题相关的样式。

你如果将相关样式直接写死成设计稿上给定的数据,在主题切换时,这部分写死的样式就无法被切换,这是切换的只是组件库中相关 UI 的样式。你的业务代码怎么办?

  1. 原始方案,将主题变量全部通过文档记录,要求每个开发同学熟记这些主题变量,并在业务代码中使用。这个方法一听就很变态(变量那么多)
  2. vscode 插件,将主题变量封装成 vscode 插件,或者代码片段,拿代码片段举例来说,比如背景主题色,输入 background-color 直接生成 background-color: var(--backgroundColor);。这里只给大家提供一个思路,具体实现可以自己探索探索,有好的实现可以在评论区和大家的分享分享

链接

多主题切换的示例代码:liyongning/multi-theme


当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注点赞收藏评论

新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn

文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。

@liyongning liyongning added the 前端 前端知识 label Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
前端 前端知识
Projects
None yet
Development

No branches or pull requests

1 participant