一文讲透CSS变量和动态主题的内在联系

lxf2023-05-21 01:45:30

前言

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

CSS Variables本质是定义一系列样式属性,本质上和 colorfont-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浏览器是无法使用的,可以采用以下写法进行规避。

a {
  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;
  ...
}
...
a {
  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 的动态主题方案
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!