💙💚小白也会定制任意主题,45行核心代码实现主题切换定制(css variable 保姆式详解)💛💜

3,839 阅读5分钟

随着前端技术的发展,对前端ui的个性化体验要求也越来越高。国外googleTwitterFacebook等大厂早早已经开始支持白天/黑夜模式主题定制。国内现在越来越多的系统也开始注重用户个性化视觉效果,越来越多的系统需要支持主题定制

传统的主题定制需要依赖less等预处理器实现。会有流程繁琐逻辑复杂性能损耗大(需要全局替换css样式)等缺点,很多前端研发都会望而却步,以技术无法达到为由拒绝此需求。

其实随着前端的不断进步,目前已经可以有非常成熟兼容度非常好的方案,可以非常容易的实现主题切换或定制。它就是css variable。(中文含义为css变量

先让我们来看下它的神奇效果(关闭左侧源码区域效果更佳):

接下来我们看下他的兼容度:

20220614173545.jpg

可以看到,除了可怜的IE已经被抛弃,其余的主流浏览器都支持!

接下来,我们开始正式从0开始介绍怎么样简单快速的使用css variable搭建原生切换主题功能!

定义css变量

定义css变量和定义普通的css样式键值一样简单。唯一的区别就是在css键值前面加上--(比如: --main-color: #000000;)。一般情况下我们会在html根元素下定义好全局的css变量,以后我们就可以在文档内部的任意位置使用该变量,再也不用每次改的时候一个个替换啦!

:root {
    --main-color: #000000;
}

注意: 自定义属性名是大小写敏感的,--my-color 和 --My-color 会被认为是两个不同的自定义属性。

使用css变量

使用css变量和设置一个普通的css值一样简单。唯一的区别就是在css值前面加上var(比如: color: color: var(--main-color);)。其实和js的理念一样,通过一个函数加变量入参形式,返回一个真实的结果。

div {
  background-color: var(--main-bg-color);
}

更灵活的使用css变量

因为css已经用变量形式呈现,结合强大的clac功能,我们可以设置一些基本的ui样式间距,通过计算动态得到标准的ui间距规范,再也不用辛辛苦苦测量ui高保真间距啦!

:root {
  /*************************** 在根元素内定义初始的css变量 ****************************/
  /*********** css变量和普通的css样式的key唯一的差别就是加一个前缀-- ************/
  /* 基础的内间距单元 */
  --spacing-unit: 6px;

  /* 通过放大按倍数的基础值来获得更为标准的ui间距规范 */
  --cell-padding: (4 * var(--spacing-unit));
  /* 同样可以通过计算来动态获取页面可以容纳的容器个数 */
  --cell-margin: (var(--grid-gutter) / 2);
}

还可以css变量的默认值

可以通过参数形式,向var传入默认值,可以防止变量为空的情况

.header {
  /* 如果变量未定义,我们还可以使用默认值来规避样式无法展示问题 */
  background-color: var(--primary-color, #5E35B1);
  padding-left: calc(4 * var(--spacing-unit));
  transition: background-color 1s;
}

主题切换实战详解(html+css+js)

在理解了定义、使用css变量之后,结合接下来的html和css和js每一行跟css variable有关的核心代码注释,就会变的很好理解如何实现主题切换:

js部分

// 辅助函数 用来获取css的key和value。注意:css变量页和普通的css键值(如padding、color等)一样可以获取和重新设置。css变量和普通的css样式的key唯一的差别就是加一个前缀-- 
var getVariable = function(styles, propertyName) {
  return String(styles.getPropertyValue(propertyName)).trim();
};

// 辅助函数 用来设置root根元素的变量值,也就是用来动态生成主题的方法
var setDocumentVariable = function(propertyName, value) {
  document.documentElement.style.setProperty(propertyName, value);
};

//设置默认主题色
var chooseDefaultColor = function(event) {
  //此处的target就是 点击的某一个按钮,从event.target获取当前dom节点。
  //然后用window.getComputedStyle来获取绑定在dom节点中的style。
  var styles = window.getComputedStyle(event.target);

  // 获取style中的个性化的背景色和文字颜色
  var primary = getVariable(styles, '--primary-color');
  var text = getVariable(styles, '--primary-color-text');
  // 将各个不同的个性化色值,设置成root根元素的色值,从而达到变更全局css变量的目的,进而实现主题切换
  setDocumentVariable('--primary-color', primary);
  setDocumentVariable('--primary-color-text', text);
};

  // 获取全局html根元素下的全局css变量对象
  var styles = window.getComputedStyle(document.documentElement);

  var quantum = document.getElementById('quantum');
  var gutter = document.getElementById('gutter');
  var columns = document.getElementById('columns');

  // 点击不同的按钮通过chooseDefaultColor设置不同的主题色
  var buttons = document.querySelectorAll('.picker-button');
  for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', chooseDefaultColor);
  }

  // 按照预设好的css变量。初始化input滑块的初始值
  quantum.value = getVariable(styles, '--spacing-unit').replace('px', '');
  gutter.value = getVariable(styles, '--margins');
  columns.value = getVariable(styles, '--grid-columns');

  // 滑动不同的色值,覆盖html根元素的css变量
  quantum.addEventListener('input', function() {
    setDocumentVariable('--spacing-unit', quantum.value + 'px');
  });

  gutter.addEventListener('input', function() {
    setDocumentVariable('--margins', gutter.value);
  });

  columns.addEventListener('input', function() {
    setDocumentVariable('--grid-columns', columns.value);
  });

css部分

:root {
  /*************************** 在根元素内定义初始的css变量 ****************************/
  /*************************** css变量和普通的css样式的key唯一的差别就是加一个前缀-- ****************************/

  /* 基础的内间距单元 */
  --spacing-unit: 6px;

  /* 基础外间距 */
  --margins: 2;

  /* 基础主题色,可以通过在style内部设置高优先级的--primary-color来覆盖此处的--primary-color */
  --primary-color: #5E35B1;
  --primary-color-text: #FFF;

  /* 每行可以显示几列.通过动态计算列宽实现 */
  --grid-columns: 3;

  /***************************** css变量还可以通过计算得到更为灵活的值****************************/

 /* 通过放大按倍数的基础值来获得更为标准的ui间距规范 */
  --margin-size: (var(--margins) * 2);
  /* 通过放大按倍数的基础值来获得更为标准的ui间距规范 */
  --cell-padding: (4 * var(--spacing-unit));
  /* 甚至可以通过变量与变量之间的计算来获取更为灵活的间距标准 */
  --grid-gutter: (var(--margins) * var(--spacing-unit));
  /* 甚至可以通过变量与变量之间的计算来获取更为灵活的间距标准 */
  --grid-margin: (var(--margin-size) * var(--spacing-unit));
  /* 同样可以通过计算来动态获取页面可以容纳的容器个数 */
  --cell-margin: (var(--grid-gutter) / 2);
}

.header {
  /* 省略部分非核心代码 */
  /* 如果变量未定义,我们还可以使用默认值来规避样式无法展示问题 */
  background-color: var(--primary-color, #5E35B1);
}

.title {
  /* 省略部分非核心代码 */
  /* 字体大小动态设置 */
  font-size: calc(4 * var(--spacing-unit));
}

.shade {
  /* 省略部分非核心代码 */
  /* 高度大小动态设置 */
  height: calc(8 * var(--spacing-unit));
}

.cell {
  /* 动态获取可以显示几列卡片 */
  width: calc(100% / var(--grid-columns) - var(--grid-gutter));
}

html部分

<div class="header">
  <div class="title">主题切换</div>
  <div class="shade"></div>

  <div class="controls">
    <p class="control">
      <span class="control-key">内间距大小:</span>
      <input class="control-value" type="range" id="quantum" min="4" max="8" step="1">
    </p>
    <p class="control">
      <span class="control-key">单列宽度:</span>
      <input class="control-value" type="range" id="columns" min="1" max="4" step="1">
    </p>
    <p class="control">
      <span class="control-key">外间距大小:</span>
      <input class="control-value" type="range" id="gutter" min="1" max="5" step="1">
    </p>
  </div>
</div>
<div class="grid">
  <!-- 可以看到这里通过style="--primary-color: #F44336; --primary-color-text: #FFF;"重新设置了css的变量值,因为css变量也遵从权重准则 -->
  <div class="cell" style="--primary-color: #F44336; --primary-color-text: #FFF;">
    <header class="cell-header">
      <div class="cell-title">
        红色
      </div>
    </header>
    <main class="cell-content">
      单击卡片上的按钮可以设置整个示例中的默认配色方案。
    </main>
    <div class="cell-actions">
      <button class="picker-button">
        使用此颜色作为主题色
        <div class="ripple"></div>
      </button>
    </div>
  </div>
  <div class="cell" style="--primary-color: #E91E63; --primary-color-text: #FFF;">
    <header class="cell-header">
      <div class="cell-title">
        Pink
      </div>
    </header>
    <main class="cell-content">
      卡片上的颜色不会收到全局css变量的影响,因为它们是在卡片级别单独定义的,也和普通股的 CSS 规则一样遵循优先级排序。
    </main>
    <div class="cell-actions">
      <button class="picker-button">
        使用此颜色作为主题色
        <div class="ripple"></div>
      </button>
    </div>
  </div>
  <div class="cell" style="--primary-color: #9C27B0; --primary-color-text: #FFF;">
    <header class="cell-header">
      <div class="cell-title">
        紫色
      </div>
    </header>
    <main class="cell-content">
      使用上面的控件来调整影响整个页面的一些属性。
    </main>
    <div class="cell-actions">
      <button class="picker-button">
        使用此颜色作为主题色
        <div class="ripple"></div>
      </button>
    </div>
  </div>
  <div class="cell" style="--primary-color: #00BCD4; --primary-color-text: #424242;">
    <header class="cell-header">
      <div class="cell-title">
        Cyan
      </div>
    </header>
    <main class="cell-content">
      通过拖拽控件可以任意调整主题间距、列宽等
    </main>
    <div class="cell-actions">
      <button class="picker-button">
        使用此颜色作为主题色
        <div class="ripple"></div>
      </button>
    </div>
  </div>
  <div class="cell" style="--primary-color: #009688; --primary-color-text: #FFF;">
    <header class="cell-header">
      <div class="cell-title">
        Teal
      </div>
    </header>
    <main class="cell-content">
      通过重新计算网格上单元格的相对大小来更改可以显示的列数。
    </main>
    <div class="cell-actions">
      <button class="picker-button">
        使用此颜色作为主题色
        <div class="ripple"></div>
      </button>
    </div>
  </div>
  <div class="cell" style="--primary-color: #4CAF50; --primary-color-text: #424242;">
    <header class="cell-header">
      <div class="cell-title">
        Green
      </div>
    </header>
    <main class="cell-content">
      如果觉得视图太拥挤或者太空旷,可以通过调整外间距可以自由控制每个元素的空间占比。
    </main>
    <div class="cell-actions">
      <button class="picker-button">
        使用此颜色作为主题色
        <div class="ripple"></div>
      </button>
    </div>
  </div>
</div>

可以看到,除了基本的html和css样式设置外,我们通过45行js代码就实现了主题切换,如果需要定制更多的主题颜色,按需增加主题色即可,甚至可以通过颜色面板,自由切换所有用户想要的主题色!