CSS

基础用法

CSS变量的定义

在前端的领域中,标准的实现总是比社区的约定要慢的多,前端框架最喜欢的 $ 被 Sass 变量用掉了。而最常用的 @ 也被 Less 用掉了。官方为了让 css 变量也能够在 Sass 及 Less 中使用,无奈只能妥协的使用 --
css的变量声明标识符为:--,即变量名前面加2个连接线

  1. body {
  2. --head_color: #000;
  3. --head_bar: #F7EFD2;
  4. }

上面代码中,body选择器里面声明了两个变量。它与正常的属性定义上没有什么不同,只是没有默认含义,所以其又叫做CSS自定义属性
这里需要注意的是,其变量名对大小写是敏感的。

CSS变量的引用

变量的引用也很简单,提供了一个方法专门进行引用,var()函数用于读取变量。

  1. .head {
  2. color: var(--head_color);
  3. }

这样就引用了,另外,如果担心变量没有定义,它还提供了默认值的设置方式。
可以使用第二个参数,表示变量的默认值。如果该变量不存在,就会使用这个默认值。

  1. color: var(--head_color, #7F583F);

需要注意的是,变量值只能用作属性值,不能用作属性名。
如果变量值是数值,不能与数值单位直接连用。

  1. .a {
  2. --gap: 20;
  3. /* 无效 */
  4. margin-top: var(--gap)px;
  5. }

数值与单位直接写在一起,必须使用calc()函数,将它们连接。

  1. .a{
  2. --gap: 20;
  3. margin-top: calc(var(--gap) * 1px);
  4. }

CSS变量的作用域

变量只能作用于自身以及后代元素,兄弟元素,祖先元素都不能享用。所以,如果变量是全局享用的,则建议放在:root上,例如:

  1. :root {
  2. --color: red;
  3. }

当然,也可以使用body或者html标签:

  1. body {
  2. --color: red;
  3. }

CSS变量就像js的变量,每个类名或者花括号就像一个function,里面的变量只有上下文以内可以获取,这就让CSS有了更多可能性。
这使得外部变量稍微改变,整个CSS都可以大大联动,且是实时的。

  1. <style>
  2. /* 在 body 选择器中声明了两个变量 */
  3. body {
  4. --primary-color: red;
  5. /* 变量名大小写敏感,--primary-color 和 --PRIMARY-COLOR 是两个不同变量 */
  6. --PRIMARY-COLOR: initial;
  7. }
  8. /** 同一个 CSS 变量,可以在多个选择器内声明。优先级高的会替换优先级低的 */
  9. main {
  10. --primary-color: blue;
  11. }
  12. /** 使用 CSS 变量 */
  13. .text-primary {
  14. /* var() 函数用于读取变量。 */
  15. color: var(--primary-color)
  16. }
  17. <style>
  18. <!-- 呈现红色字体,body 选择器的颜色 -->
  19. <div class="text-primary">red</div>
  20. <!-- 呈现蓝色字体,main 选择器定义的颜色 -->
  21. <main class="text-primary">blue</main>
  22. <!-- 呈现紫色字体,当前内联样式表的定义 -->
  23. <div style='--primary-color: purple" class="text-primary">purple</main>

可以看到针对同一个 css 变量,可以在多个选择器内声明。读取的时候,优先级最高的声明生效。这与 css 的”层叠”(cascade)规则是一致的。由于这个原因,全局的变量通常放在根元素:root里面,确保任何选择器都可以读取它们。

  1. :root {
  2. --primary-color: #06c;
  3. }

JavaScript操作CSS变量

同时, css 变量提供了 JavaScript 与 css 通信的方法。就是利用 js 操作 css 变量。可以使用:

  1. <style>
  2. /* ...和上面 CSS 一致 */
  3. </style>
  4. <!-- 呈现黄色字体 -->
  5. <div class="text-primary">red</div>
  6. <!-- 呈现蓝色字体,main 选择器定义的颜色 -->
  7. <main id='primary' class="text-primary">blue</main>
  8. <!-- 呈现紫色字体,当前内联样式表的定义 -->
  9. <div id="secondary" style='--primary-color: purple">purple</main>
  10. <script>
  11. // 设置变量
  12. document.body.style.setProperty('--primary-color', 'yellow');
  13. // 设置变量,js DOM 元素 ID 就是全局变量,所以直接设置 main 即可
  14. // 变为 红色
  15. primary.style.setProperty('--primary-color', 'red');
  16. // 变为 黄色,因为当前样式被移除了,使用 body 上面样式
  17. secondary.style.removeProperty('--primary-color');
  18. // 通过动态计算获取变量值
  19. getComputedStyle(document.body).getPropertyValue('--primary-color')
  20. </script>

可以在业务项目中定义以及替换 CSS 变量,可以参考 mvp.css。该库大量使用了 CSS 变量并且可以根据自己需求修改它。

  1. :root {
  2. --border-radius: 5px;
  3. --box-shadow: 2px 2px 10px;
  4. --color: #118bee;
  5. --color-accent: #118bee0b;
  6. --color-bg: #fff;
  7. --color-bg-secondary: #e9e9e9;
  8. --color-secondary: #920de9;
  9. --color-secondary-accent: #920de90b;
  10. --color-shadow: #f4f4f4;
  11. --color-text: #000;
  12. --color-text-secondary: #999;
  13. --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  14. --hover-brightness: 1.2;
  15. --justify-important: center;
  16. --justify-normal: left;
  17. --line-height: 150%;
  18. --width-card: 285px;
  19. --width-card-medium: 460px;
  20. --width-card-wide: 800px;
  21. --width-content: 1080px;
  22. }

可以看到基于 CSS 变量,可以更友好的和设计师的设计意图结合在一起。也易于修改,在业务项目中合理使用无疑可以事半功倍。

实现默认配置

结合 CSS 预处理器 + CSS 变量便可以实现组件样式的默认配置。先介绍两个关于该功能的前置知识点:事实上,CSS 变量的 var() 函数还可以使用第二个参数,表示变量的默认值。如果该变量此前没有定义或者是无效值,就会使用这个默认值。

  1. /* 没有设置过 --primary-color,颜色默认使用 #7F583F */
  2. color: var(--primary-color, #7F583F);

虽然目前 CSS 变量不是新的属性,但不是所有的浏览器都支持 CSS 变量的,这里还是要考虑一下优雅降级。

  1. /* 对于不支持 CSS 变量的浏览器,可以采用下面的写法。*/
  2. a {
  3. /* 颜色默认值 */
  4. color: #7F583F;
  5. /* 不支持则不识别该条规则 */
  6. color: var(--primary);
  7. }

结合 CSS 处理器 + CSS 变量便可以实现组件样式的默认配置。

  1. // 先导入所有 Less 变量
  2. @import (reference) './var.less';
  3. // 利用正则去替换 Less 变量 为 CSS 变量
  4. .theme(@property, @imp) {
  5. @{property}: e(replace(@imp, '@([^() ]+)', '@{$1}', 'ig'));
  6. @{property}: e(replace(@imp, '@([^() ]+)', 'var(--$1, @{$1})', 'ig'));
  7. }

函数效果如下所示:

  1. @import '../common/style/theme.less';
  2. .van-button {
  3. // ... 其他省略
  4. .theme(height, '@button-default-height');
  5. .theme(line-height, '@button-line-height');
  6. .theme(font-size, '@button-default-font-size');
  7. }
  8. // => less 编译之后生成
  9. .van-button{
  10. // ... 其他省略
  11. height:44px;
  12. height:var(--button-default-height,44px);
  13. line-height:20px;
  14. line-height:var(--button-line-height,20px);
  15. font-size:16px;
  16. font-size:var(--button-default-font-size,16px);
  17. }

可以看到每调用一次 Less 函数将会被编译成两个属性。第一个属性的设定对于不支持 CSS 变量的设备可以直接使用,如果当前设备支持 CSS 变量,则会使用 CSS 变量,但是由于当前 CSS 变量未定义,就会使用变量的默认值。虽然 @button-default-height 也是一个变量,但是该变量仅仅只是 less 变量,最终生成的代码中并没有 --button-default-height 这样的变量。此时就可以在使用样式的位置或者 :root 中添加变量 --button-default-height。这种方式更适合组件开发,因为该方案不声明任何 css 变量,只是预留的 css 变量名称和默认属性。这样的话,无论开发者的选择器优先度有多低,代码都可以很容易的覆盖默认属性。因为仅仅使用 css 的默认值。大家可能有时候会想,这样的话,不是有更多的代码了吗?其实未必,事实上可以直接直接在页面内部定义变量样式。其他组件直接通过 style 去使用页面内的变量。事实上书写的代码多少,重点在于想要控制默认样式的粒度大小。粒度越小,则需要在各个组件内部书写的变量越多,粒度大,也就不必考虑太多。

Space Toggle 逻辑切换

CSS 没有逻辑切换似乎是一种共识,但是可以利用选框(单选与多选)和 CSS 变量来实现判断逻辑。先来看看如何使用 CSS 变量。

  1. <style>
  2. .red-box {
  3. --toggler: ;
  4. --red-if-toggler: var(--toggler) red;
  5. background: var(--red-if-toggler, green); /* will be red! */
  6. }
  7. .green-box {
  8. --toggler: initial;
  9. --red-if-toggler: var(--toggler) red;
  10. background: var(--red-if-toggler, green); /* will be green! */
  11. }
  12. </style>
  13. <!-- 宽度高度为 100px 的 红色盒子 -->
  14. <div
  15. style="height: 100px; width: 100px"
  16. class="red-box"
  17. ></div>
  18. <!-- 宽度高度为 100px 的 绿色盒子 -->
  19. <div
  20. style="height: 100px; width: 100px"
  21. class="green-box"
  22. ></div>

这里因为一个变量 --toggler 使用空格 或者 initial 而产生了不同的结果,基于这样的结果不难想象可以触发变量的修改而产生不同的结果。他不是一个 bug,也不是一个hack。
他的原理完完全全的在 CSS Custom Properties 规范中。 :::success This value serializes as the empty string, but actually writing an empty value into a custom property, like —foo: ;, is a valid (empty) value, not the guaranteed-invalid value. If, for whatever reason, one wants to manually reset a variable to the guaranteed-invalid value, using the keyword initial will do this.
解释如下:
事实上 -foo: ; 这个变量并不是一个无效值,它是一个空值。initial 才是 CSS 变量的无效值。其实这也可以理解,css 没有所谓的空字符串,空白也不代表着无效,只能使用特定值来表示该变量无效。 ::: 这个时候,再回头来看原来的 CSS 代码。

  1. .red-box {
  2. /* 当前为空值 */
  3. --toggler: ;
  4. /* 因为 var(--toggler) 得到了空,所以得到结果 为 --red-if-toggler: red */
  5. --red-if-toggler: var(--toggler) red;
  6. /** 变量是 red, 不会使用 green */
  7. background: var(--red-if-toggler, green); /* will be red! */
  8. }
  9. .green-box {
  10. /** 当前为无效值 */
  11. --toggler: initial;
  12. /** 仍旧无效数据,因为 var 只会在参数不是 initial 时候进行替换 */
  13. --red-if-toggler: var(--toggler) red;
  14. /** 最终无效值没用,得到绿色 */
  15. background: var(--red-if-toggler, green); /* will be green! */
  16. /* 根据当前的功能,甚至可以做到 and 和 or 的逻辑
  17. * --tog1 --tog2 --tog3 同时为 空值时是 红色
  18. */
  19. --red-if-togglersalltrue: var(--tog1) var(--tog2) var(--tog3) red;
  20. /*
  21. * --tog1 --tog2 --tog3 任意为 空值时是 红色
  22. */
  23. --red-if-anytogglertrue: var(--tog1, var(--tog2, var(--tog3))) red;
  24. }

新式媒体查询

当需要开发响应式网站的时候,必须要使用媒体查询 @media。先看一下用传统的方式编写这个基本的响应式 CSS:

  1. .breakpoints-demo > * {
  2. width: 100%;
  3. background: red;
  4. }
  5. @media (min-width: 37.5em) and (max-width: 56.249em) {
  6. .breakpoints-demo > * {
  7. width: 49%;
  8. }
  9. }
  10. @media (min-width: 56.25em) and (max-width: 74.99em) {
  11. .breakpoints-demo > * {
  12. width: 32%;
  13. }
  14. }
  15. @media (min-width: 56.25em) {
  16. .breakpoints-demo > * {
  17. background: green;
  18. }
  19. }
  20. @media (min-width: 75em) {
  21. .breakpoints-demo > * {
  22. width: 24%;
  23. }
  24. }

同样,可以利用 css 变量来优化代码结构,可以写出这样的代码:

  1. /** 移动优先的样式规则 */
  2. .breakpoints-demo > * {
  3. /** 小于 37.5em, 宽度 100% */
  4. --xs-width: var(--media-xs) 100%;
  5. /** 小于 56.249em, 宽度 49% */
  6. --sm-width: var(--media-sm) 49%;
  7. --md-width: var(--media-md) 32%;
  8. --lg-width: var(--media-gte-lg) 24%;
  9. width: var(--xs-width, var(--sm-width, var(--md-width, var(--lg-width))));
  10. --sm-and-down-bg: var(--media-lte-sm) red;
  11. --md-and-up-bg: var(--media-gte-md) green;
  12. background: var(--sm-and-down-bg, var(--md-and-up-bg));
  13. }

可以看出,第二种 CSS 代码非常清晰,数据和逻辑保持在一个 CSS 规则中,而不是被 @media 切割到多个区块中。这样,不但更容易编写,也更加容易开发者读。详情可以参考 css-media-vars。该代码库仅仅只有 3kb 大小,但是却是把整个编写代码的风格修改的完全不同。原理如下所示:

  1. /**
  2. * css-media-vars
  3. * BSD 2-Clause License
  4. * Copyright (c) James0x57, PropJockey, 2020
  5. */
  6. html {
  7. --media-print: initial;
  8. --media-screen: initial;
  9. --media-speech: initial;
  10. --media-xs: initial;
  11. --media-sm: initial;
  12. --media-md: initial;
  13. --media-lg: initial;
  14. --media-xl: initial;
  15. /* ... */
  16. --media-pointer-fine: initial;
  17. --media-pointer-none: initial;
  18. }
  19. /* 把当前变量变为空值 */
  20. @media print {
  21. html { --media-print: ; }
  22. }
  23. @media screen {
  24. html { --media-screen: ; }
  25. }
  26. @media speech {
  27. html { --media-speech: ; }
  28. }
  29. /* 把当前变量变为空值 */
  30. @media (max-width: 37.499em) {
  31. html {
  32. --media-xs: ;
  33. --media-lte-sm: ;
  34. --media-lte-md: ;
  35. --media-lte-lg: ;
  36. }
  37. }

其他

继 CSS 键盘记录器暴露了 CSS 安全性问题之后,CSS Space Toggle 技术不但可以应用于上面的功能,甚至还可以编写 UI 库 augmented-ui 以及扫雷游戏。