相关文章推荐
无邪的灌汤包  ·  spring boot - How ...·  1 年前    · 

其实 CSS Variables 并不是最近才出现的新生事物,早在2012年 W3C 就已经公布了 CSS Variables 的首个公开草案;2017年3月微软 Edge 浏览器也宣布支持 CSS 变量,此时所有主要浏览器都已经支持这个 CSS 新功能。

CSS Variables 本质是定义一系列样式属性,本质上和 color font-size 属性是一样的,只是没有默认含义,且可以被其他属性引用,并且提供了 JavaScript 基层API进行管理。因此 CSS Variables 天生就是为 动态主题 而生的,能够在运行时直接以简洁明了,灵活的方式调整页面样式。

Antd 在 antd@4.17.0-alpha.0 推出了实验性的动态主题方案,就是采用 CSS Variables 的方式来实现的,接下来本文将结合 Antd动态主题 来仔细探究 CSS Variables 和动态主题的内在联系。

本文分为 CSS Variables基础 Antd动态主题底层探究 两部分,依据循序渐进的方式,先介绍基础原理,再介绍动态主题的底层原理,已经熟悉基础原理的同学请跳过这一部分,直接进入干货部分。

CSS Variables 基础

1、变量声明

声明变量时,变量名之前必须加两个连词线( -- ),之所以使用 -- 这个符号来表示,是因为 $ 被 Sass 用掉了, @ 被 Less 用掉了。

html {
  --ant-primary-color: #1890ff;
  --ant-primary-color-hover: #40a9ff;
  --ant-primary-color-active: #096dd9;
  --ant-primary-color-outline: rgba(24, 144, 255, 0.2);

后续的单词一般采用HTML的属性的声明方式,使用 - 进行链接。

CSS Variables 又叫做CSS 自定义属性,它的值类型和 CSS 属性值的类型是一样的,可以放入字符串、色值、时间等。

变量名大小写敏感,--header-color--Header-Color是两个不同变量。

@keyframes waveEffect {
  100% {
    box-shadow: 0 0 0 var(--cmsAnt-primary-color);
    box-shadow: 0 0 0 6px var(--antd-wave-shadow-color);
@media screen and (min-width: 768px) {
  body {
    --primary:  #F7EFD2;
    --secondary: #7F583F;

并且支持在响应式布局@media和动画@keyframes中使用。

同样支持变量依赖声明。

html {
  --antd-wave-shadow-color: var(--ant-primary-color);
  --scroll-bar: 0;

2、var()函数

var()是用于读取变量,并且支持第二个参数作为默认值,这是个很棒的设计。

color: var(--foo, #7F583F);

以下是注意事项:

  • 1、 读取变量值只能作用于属性值,而不能作用于属性名。
  • .foo {
      --side: margin-top;
      /* 无效 */
      var(--side): 20px;
    
  • 2、可以字符串拼接,但是带单位的变量值不可以拼接
  • body:after {
      content: '--screen-category : 'var(--screen-category);
    

    假如假如需要带单位,则需要使用calc()函数。

    .foo {
      --gap: 20;
      /* 无效 */
      margin-top: var(--gap)px;
      /* 有效 */
      margin-top: calc(var(--gap) * 1px);
    

    如果变量值带有单位,就不能写成字符串。

    /* 无效 */
    .foo {
      --foo: '20px';
      font-size: var(--foo);
    /* 有效 */
    .foo {
      --foo: 20px;
      font-size: var(--foo);
    

    3、作用域

    同一个 CSS 变量,可以在多个选择器内声明。读取的时候,优先级最好的声明生效。

    <style>
      html {
        --color: pink;
      :root { --color: blue; }
      div { --color: green; }
      #alert { --color: red; }
      * { color: var(--color); }
    </style>
    <p>蓝色</p>
    <div>绿色</div>
    <div id="alert">红色</div>
    

    :root除了优先级更高之外,其他和html保持一致,因此p标签生效的是:root的作用域的变量定义,如果想对动态主题进行强制覆盖,目前动态主题方案一般是使用html作用域下,则可以使用:root作用域声明进行强制覆盖。

    选择器的优先级则是按照 html < div < ID 选择器

    4、兼容性处理

    CSS variables在一些旧版本主流浏览器或者IE浏览器是无法使用的,可以采用以下写法进行规避。

    color: #7F583F; color: var(--primary);

    或者使用@supports进行检测。

    @supports ( (--a: 0)) {
      /* supported */
    @supports ( not (--a: 0)) {
      /* not supported */
    

    5、JavaScript 操作

    首先 JavaScript 可以检测浏览器是否支持 CSS 变量。

    const isSupported =
      window.CSS &&
      window.CSS.supports &&
      window.CSS.supports('--a', 0);
    if (isSupported) {
      /* supported */
    } else {
      /* not supported */
    

    JavaScript API 写法。

    // 设置变量
    document.body.style.setProperty('--primary', '#7F583F');
    // 读取变量
    document.body.style.getPropertyValue('--primary').trim();
    // '#7F583F'
    // 删除变量
    document.body.style.removeProperty('--primary');
    

    这里补充一些其他文章没有提到的信息。

    首先<style>内定义的样式变量,无法通过 JavaScript API 来获取。

    // 在作用域的例子中,执行下面的逻辑
    document.querySelector(':root').style.getPropertyValue('--color')
    // 返回 ''
    document.querySelector(':root').style.setProperty('--color', 'gray')
    // 返回 undefined 并且字体颜色变成灰色
    document.querySelector(':root').style.getPropertyValue('--color')
    // 返回 'gray'
    

    到这里大家就明白了其内在限制了。

    Antd 动态主题底层探究

    首先看下 Antd 动态主题的文档,最主要的改动是修改引入的样式文件。

    -- import 'antd/dist/antd.min.css';
    ++ import 'antd/dist/antd.variable.min.css';
    

    本文抓取了最终引入的文件内容如下:

    html {
      --ant-primary-color: #1890ff;
      --ant-primary-color-hover: #40a9ff;
      --ant-primary-color-active: #096dd9;
      color: var(--ant-primary-color);
      text-decoration: none;
      background-color: transparent;
      outline: none;
      cursor: pointer;
      transition: color 0.3s;
      -webkit-text-decoration-skip: objects;
    

    这里就显而易见了,底层就是CSS Variables的设计。下面的语法实际也是对上面的 CSS Variables 的修改。

    ConfigProvider.config({ prefixCls: 'custom', theme: { primaryColor: '#25b864', }, });
    

    到这里你是不是会疑惑?CSS Variables 是不是唯一的动态主题实现方案?实际上并不是的,antd-theme-generator库就是就是基于 Less api 的动态主题方案,有兴趣的可以详细看下我的另外一篇文章《高度兼容低版本的 antd 的动态主题方案》。这篇文章具体介绍antd-theme-generator库的优缺点以及如何解决其中的问题落实到实际项目中去。

    CSS Variables 是不是唯一的动态主题实现方案?

    正如上面所说,Less 基础 API 也对样式变量进行了支持,而且相对于CSS Variables的语法丰富度更高。以 Antd Button 的样式文件为例。

    @import '../../style/themes/index';
    @import '../../style/mixins/index';
    @import './mixin';
    @btn-prefix-cls: ~'@{ant-prefix}-btn';
    // for compatible
    @btn-ghost-color: @text-color;
    @btn-ghost-bg: transparent;
    @btn-ghost-border: @border-color-base;
    // Button styles
    // -----------------------------
    .@{btn-prefix-cls} {
      &-primary {
        .btn-primary();
        .@{btn-prefix-cls}-group &:not(:first-child):not(:last-child) {
          border-right-color: @btn-group-border;
          border-left-color: @btn-group-border;
          &:disabled {
            border-color: @btn-default-border;
    

    如果你直接在HTML中引入类似上面 less 样式文件,

    <script> less = { env: "development" }; </script>
    <script src="less.js" data-env="development"></script>
    

    那么你就可以通过下面的语法进行样式动态调整。

    less.modifyVars({
        '@buttonFace': '#5B83AD',
        '@buttonText': '#D9EEF2',
    

    只是大家日常为了做到更好的浏览器兼容,没有直接使用 less 样式文件,都是使用经过编译后的 css 文件。

    这里还要提到一点就是 Less 命令行支持 Less variablesCSS Variables 的转换,这个就相当实用,例如在 Antd 动态主题中提到可以使用以下命令重新生成一份新前缀的 css 文件。

    lessc --js --modify-var="ant-prefix=custom" antd/dist/antd.variable.less modified.css
    

    更多的命令可以参考 Less 官网命令行使用

    到这里为止,本文分析了 CSS Variables 和动态主题互为表里的关系,有道是纸上得来终觉浅,绝知此事要躬行,大家可以在实际项目中可以多体验下动态主题的相关方案,毕竟动态主题的用户体验要远远高于传统的主题定制。

    就我自己的使用体验而言,相对于传统静态主题定制的方式,动态主题在实际使用过程中也存在以下局限性的。

  • 性能差,无法进行压缩、混淆,文件体积大
  • css in js 的思想有一定的冲突,无法使用 css module,实际代码开发体验不好
  • 不适用于微前端等应用场景,CSS 隔离会遇到很大的麻烦
  • 因此在决策是否使用动态主题时,还是要从自身项目的实际需要进行综合考量的,按需选取。

    引入文献:

  • 阮一峰老师-CSS 变量教程
  • W3C CSS Variables 官方文档
  • 掘金-# 高度兼容低版本的 antd 的动态主题方案
  • 道长王jj JavaScript
    私信