web自定义主题实现方案

1,964 阅读3分钟

需求背景

A用户:我喜欢亮色!B用户:我喜欢暗色系!...在实际的开发场景中,主题需求一般都是必不可少的,那我们如何简单地实现我们想要的效果呢!

解决方案

话不多说,我们先看一下demo效果

实现思路:在页面的载体上自定义一个属性标签,用于记录切换不同的主题,然后我们的css文件会根据属性标签值的改变而加载不同样式,来实现我们切换主题的效果。干巴巴的描述,理解起来有点晦涩难懂,这里我以自己写的demo为例,和大家一起交流探讨。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style type="text/css">
</head>

<body customize-theme-style="dark">
  <app-root></app-root>
>
</body>

</html>

目前很多单页面应用都会有一个index.html载体文件,路由<app-root></app-root>挂载在body节点上,所以我们的页面组件都是挂载在body上,那么我们在body节点上定义一个customize-theme-style="dark"属性标签用于记录主题状态的切换。接下来就是css会根据主题状态的切换来改变主题样式。 我这里CSS 预处理语言用的是less(如果你用的是sass,或者是其他的css预处理语言,思路都是相同的,语法上有些许不同而已),在资源文件夹里建一个styles用于存放我们的less样式文件,demo的编译环境是angular8.0,所以我们要在angular.json脚手架文件声明我们新建的资源文件(这里我就不详细说明配置过程了,不同的前端框架,配置过程不同,但框架从来不是技术实现的障碍,思路是一样的),

如上图所示index.less用来定义主题样式以及覆盖第三方组件样式,theme.less用于定义全局的样式变量 在index.less中:

// 暗黑主题
[customize-theme-style='dark'] {
  //背景
  .alain-default {
    background: @background-color;
  }

  //表格
  .ant-table-thead>tr>th {
    background-color: @table-head-color;
    color: @font-color;
  }

  ......
}
// 亮色主题
[customize-theme-style='light'] {
  ......  
}
......

主题状态的改变会加载不同的css样式,接下来就是如何改变主题样式状态了

实现思路:我们将用户设置的主题状态用localStorage存储在本地,如果用户没有设置,就加载默认主题。

    //获取用户上次设置的主题
    this.themeSkin = this.storageService.getStorageValue('customize-theme');
    if (this.themeSkin) {
      //设置主题
      const body = document.getElementsByTagName('body')[0];
      body.setAttribute('customize-theme-style', this.themeSkin);
    }
    //切换主题
    changeSkin(skin) {
    const body = document.getElementsByTagName('body')[0];
    body.setAttribute('customize-theme-style', skin);
    //存储主题
    this.storageService.setStorageValue('customize-theme', skin);
  }

完成以上步骤就基本上实现了自定义主题的切换需求。

然而事情并没有这么简单!!!

现在的框架都是提倡页面组件化,那么我们自己写的组件怎么适配主题呢? 所谓的适配也就是让组件读取当前载体文件body节点(根节点)上的主题属性值,这个也不难做到。 举个栗子:

//自定义组件中的样式
:host-context([customize-theme-style='dark']) h4 {
   .mixin-font-color('dark');
 }
 
:host-context([customize-theme-style='light']) h4 {
   .mixin-font-color('light');
}

//index.less

.mixin-font-color(@a) when(@a='dark') {
  color: #ffffff;
}

.mixin-font-color(@a) when(@a='light') {
  color: #212121;
}

编译后的css样式: [customize-theme-style='dark'][_nghost-fkw-c5] h4[_ngcontent-fkw-c5], [customize-theme-style='dark'] [_nghost-fkw-c5] h4[_ngcontent-fkw-c5] { color: #ffffff; }

:host-context()伪类选择器可以读取最外层的挂载点上的属性,通过这一特性就能实现组件主题化了 (API:angular.io/guide/compo…) 这里就给我们的组件中标题生成了一个唯一的样式。 至此,我们的主题需求问题就迎刃而解了。 如果有什么疑问或者你有更好的解决方案,欢迎留言交流,谢谢~