CSS 变量使用详解

简介: CSS 变量使用详解

前言


这篇文章你将学到以下内容:


  • CSS 变量
  • CSS 常用函数
  • iPhone X 系列机型适配
  • CSS At-rules


正文


一、简述


CSS 变量(CSS Variables),也称作 CSS 自定义属性(CSS Custom Properties),它是带有前缀 -- 属性名,且带有值的自定义属性。然后通过 var 函数在全文范围复用。


至于为什么采用 --,大概是因为 @Less 占用了,$Sass 占用了吧。


1.1 语法


定义 CSS 变量的语法非常简单,在变量名称之前添加两个短横线 --


--<custom-property-name>: <declaration-value>


其中 表示变量名称, 表示变量值,形如:--*。这类自定义 CSS 属性与 colorfont-sizebackground-image 等属性并没有什么不同,只是它没有默认含义罢了,它必须通过 var() 函数复用之后,才会产生意义。


其中「变量名称」命名约束是比较宽松的,可以是数字、字母、下划线 _、短横线 - 的组合,但不能包含 $[^(% 等字符。比如:


--some-keyword: left;
--some-color: #f00;
--some-complex-value: 3px 6px rgb(20, 32, 54);


甚至可以是以数字开头、也可以是中文、韩文等。


:root {
 --红色: #f00; /* 有效 */
 --1: 1px; /* 有效 */
}
body {
 background-color: var(--红色);
 height: var(--1);
}


当然,实际项目中,千万别以这种花里胡哨、奇奇怪怪的组合来命名变量名称,主要是避免被打。建议使用 kebab-case 方式进行命名,比如 --theme-primary 等。


请注意,CSS 变量名称是大小写敏感的,--foo--Foo 是两个不同的变量。这一点与 CSS 属性大小写不敏感是有区别的。


1.2 作用域


同一个 CSS 变量,可以在多个选择器内声明,读取顺序与 CSS 匹配规则一致,优先级最高的生效。请注意,CSS 变量并没有 !important 用法,变量的覆盖规则由 CSS 选择器权重决定。


一般情况下,全局性变量放在 :root 内声明,也可以在任意元素中声明 CSS 变量,视实际情况而定即可。如果是小程序,则在全局样式 app.wxsspage 内声明。


:root 这个 CSS 伪类匹配文档树的根元素。对于 HTML 来说,:root 表示 元素,除了优先级更高之外,与 html 选择器相同。


:root {
  --theme-primary: #f00; /* 全局可复用 */
}
header {
  --theme-primary: #0f0; /* 仅 header 范围内可复用 */
}
section {
  --theme-primary: #00f; /* 仅 section 范围内可复用 */
}


比如  内使用 color: var(--theme-primary),生效的将会是 color: #00f。再者,以下示例中,在

中引用 --color 变量,最终生效的是 ID 选择器的变量值。


:root {
  --color: #f00;
}
div {
  --color: #0f0;
}
#id {
  --color: #00f;
}


总的来讲,CSS 变量是有作用域概念的,它只能作用于自身或后代元素,而兄弟元素、祖先元素都是不用享用的。

可以试下这个示例:css-variable-scope-demo


1.3 兼容性


兼容性如下,还是挺不错的(如果忽略 IE 的话),更多请看 Can I use


000000000000.webp.jpg


很棒,IE 全系不支持,骂骂咧咧地说:还是用 SCSS 或 LESS 吧。可以参考下这个项目:css-vars-ponyfill

对于不支持 CSS 变量的浏览器,可以采用如下方式兼容处理:


:root {
  --color-primary: #00f;
}
a {
  color: #00f;
  color: var(--color-primary);
}


也可以使用 @supports 规则,然而它也不兼容 IE 浏览器。


@supports (--foo: 0) {
  /* supported */
}
@supports (not (--foo: 0)) {
  /* unsupported */
}


二、JavaScript 操作


利用 CSS.supports() 方法即可判断当前浏览器是否支持 CSS 变量,如下:


const isSupported = window.CSS.supports('--foo', 0)


由于 CSS 变量就是自定义的 CSS 属性嘛,因此按照平常设置 CSS 属性的方式去操作即可,如下:


const element = document.querySelector('selectors')
// 定义 CSS 变量
element.style.setProperty('--color', '#f00')
// 读取 CSS 变量
element.style.getPropertyValue('--color', '#f00')
// 删除 CSS 变量
element.style.removeProperty('--color')


另外有一个比较奇怪的用法(来自 EXAMPLE 7),如下:


:root {
  --foo: if(x > 5) this.width = 10;
}


尽管这个属性值是「无用」的,不会使得任意 CSS 属性产生实际效果,但是这个 CSS 变量定义是「有效」的。它可以被 JavaScript 读取,至于有什么用,我也不知道。


二、CSS 函数


2.1 var 函数


var() 函数用于读取 CSS 变量,它可以替代元素中「任何属性」中的「值的任何部分」,不能作为属性名、选择器或其他处理属性值之外的值。


语法如下:


var(<custom-property-name>[, <declaration-value>])
  • 表示自定义属性名
  • (可选)表示声明值(后备值),仅自定义属性没有定义时,它才会有效(类似 ES6 中的函数参数设定默认值)。


2.1.1 CSS 变量不合法的缺省特性


看看以下示例,变量 --color 的值为 20px,显然它作为 background-color 值的话是无效的,那么  会显示什么背景颜色呢?红色?绿色?还是...


body {
  --color: 20px;
  background-color: #f00;
  background-color: var(--color, #0f0); /* 正确语法,与 background-color: 20px 有着本质上的区别 */
}


0000000000.webp.jpg


它最终生效的属性值为 transparent,即 background-color 的默认值,因此相当于:


body {
  --color: 20px;
  background-color: #f00;
  background-color: transparent;
}


可看 EXAMPLE 13


但请注意,以下示例生效的是 background-color: #f00,就怕有人看到上面示例之后,对原来的认知产生怀疑,特意说明下。


body {
  background-color: #f00; /* 有效 */
  background-color: 20px; /* 语法错误,这条规则声明被丢弃,因此上一条规则生效 */
}


因此,当 CSS 变量值不合法时,生效的是 CSS 属性的“默认值”。

但注意,CSS 变量值不合法并不能使得 声明值生效,它仅限于 CSS 变量没有定义才会生效(类似函数参数的默认值仅实参为 undefined 才会生效,即便是 null 等 falsy 实参也不会使其生效一样)。


为什么这里默认值要打双引号呢,原因是标准 EXAMPLE 13 部分明确说明了:


If the property was one that’s inherited by default, such as color, it would compute to the inherited value rather than the initial value.


也就是说,如果一个 CSS 属性是可继承的,那个当它应用了一个不合法的 CSS 变量值,最终生效的是其继承值,而不是默认值。比如:


<style>
  :root {
    --not-a-color: 20px;
  }
  body {
    color: #f00;
  }
  p {
    color: var(--not-a-color);
  }
</style>
<body>
  <p>字体会是什么颜色呢?</p>
</body>

000000000.webp.jpg


你看最终

生效的 color 是其从  中继承过来的 #f00 红色,而不是 color 的默认颜色 canvastext


插个话题,我很好奇 canvastext 颜色是什么颜色,一般来说它会是黑色 rgb(0, 0, 0),然后我尝试将系统调至深色模式,然而它并不会默认变为白色,哈哈。然后我翻查了下标准,发现它跟  有关,它一般由浏览器来定义(如下),可看 6.2 System Color 章节。


0000000.webp.jpg


因此,比较严谨的说法是:当 CSS 变量值不合法时,生效的是 CSS 属性的继承值或初始值。


2.1.2 var 函数的尾随空格


<style>
  body {
    --size: 20;
    font-size: var(--size)px;
  }
</style>
<body>
  <p>字号是多大呢?</p>
</body>


猜一下 font-size 会是预期的 20px 吗?它不是,如下图:


00000.webp.jpg


请注意,浏览器最终解析出来的规则是:font-size: var(--size) px;,它在 var(--size)px 之间多了一个「空格」,因此这条规则是无效的(注意并不是引用 CSS 变量无效),所以字号是浏览器默认字体大小 16px

如果你使用诸如 VS Code 等编辑器,它一般会有 semi-colon expected 错误提醒的,如果保存自动格式化,它将会被保存为:font-size: var(--size) px;


0000.webp.jpg

这种情况可结合 calc() 函数处理,比如:


body {
  --size: 20;
  font-size: calc(var(--size) * 1px); /* 这样就能正常计算得出 20px 了 */
}


但个人更推荐这样用:对于一些长度、大小等 CSS 属性值,在定义 CSS 变量时,应带上单位:


body {
  --size: 20px;
  font-size: var(--size);
}


请注意,如果变量值包含单位,就不能写成字符串形式。


body {
  --size: '20px';
  font-size: var(--size); /* 注意,CSS 变量引用的语法是有效的,但经 CSS 解析器计算之后,其值并不符合 font-size 属性值的要求,因此被判定为语法错误,规则会被丢弃。 */
}

相当于 font-size: '20px'; 语法错误,规则会被丢弃,因此取其继承值或默认值。


2.1.3 CSS 变量的相互传递性


我们在某个选择器中定义了一个 CSS 变量,它除了在子元素中被复用,它本身作用域内也可以复用,而且与编写顺序无关。比如:


body {
  --size: 20px;
  font-size: var(--size);
}


body {
  font-size: var(--size);
  --size: 20px;
}

以上两个示例,均是有效的。后者并不会因为 --size: 20px; 定义在后,就不会生效。这样规则,对于我们通过 JavaScript 动态设置 CSS 变量有着非常重要的意义。


2.2 calc 函数


calc() 语法非常地简单,如下:


property: calc(expression)

该函数接收一个表达式作为它的参数,表达式的返回值作为 calc() 函数的值。表达式可以是 +-*/ 的组合,而且可以混用不同单位进行运算。

它同样支持 CSS 变量,例如:


.foo {
  --height: 30px;
  width: calc(100% - 30px);
  height: calc(100vh - var(--height))
}


注意点:

  • 对于 +- 运算,运算符两边必须要有「空格」,而 */ 运算则没有要求,因此建议都加上空白符。
  • 对于 * 运算,参与运算的至少有一个数值(),且不能为 0
  • 对于 / 运算,运算符 / 右侧必须是一个数值()。
  • calc() 函数支持嵌套写法,但其实被嵌套的 calc() 函数只会被当做普通的括号,因此函数内直接使用括号就好了。


那么嵌套语法有什么用呢,比如:


.foo {
  --widthA: 100px;
  --widthB: calc(var(--widthA) / 2);
  --widthC: calc(var(--widthB) / 2);
  width: var(--widthC);
}


那么,以上 --widthC 的值就会变成 calc(calc(100px / 2) / 2),即 25px

Web 前端总是绕不开兼容性,那么看下 calc() 函数的兼容性如何:


000.webp.jpg


绿悠悠的一片,甚好!可以看到 IE9 以上都支持,可 IE 浏览器不支持嵌套写法,由于 IE 浏览器都不支持 CSS 变量,因此这个无伤大雅。


2.3 env 和 constant 函数


2017 年 Apple 公司发布了 iPhone X 和 iOS 11,开启了「刘海屏」和底部小横条之路。

于是就有了「安全区 Safe Area」之说(详见):

A safe area defines the area within a view that isn’t covered by a navigation bar, tab bar, toolbar, or other views a view controller might provide.

简单来讲,以 iPhone X 为例,其安全区是指不受刘海(Sensor Housing)、底部小横条(Home Indicator)、设备圆角(Corners)影响的区域,如下图的浅蓝色区域所示,其中粉色部分是指浏览器默认的 Margin 值,通常为了抹平各浏览器不同的外边距,都会设置 * { margin: 0 }

--.webp.jpg

我们知道,viewport 是规则的矩形,如果显示设备的屏幕是不规则(比如圆形)的话,页面中的某些部分就会被裁剪。那么 viewport-fit 可以通过设置可视 viewport 大小来控制裁剪区域。

其中 viewport-fit 提供了 auto(默认)、containcover 三种属性值(详见):


<meta name="viewport" content="viewport-fit=auto|contain|cover" />


属性值 描述
auto 默认值,表现与 contain 一致,viewport 会显示在「安全区」之内,相当于 viewport-fit: contain
contain 将可视 viewport 设置为页面所有内容均可见的最大矩形。
cover 将可视 viewport 大小设置为显示设备屏幕的外接矩形。


以圆形屏幕为例:


=.webp.jpg


Apple 公司为了适配旗下的全面屏设备,(iOS 11 起)WebKit 内核的浏览器中定义了 safe-area-inset-* 四个环境变量。

  • safe-area-inset-top
  • safe-area-inset-right
  • safe-area-inset-bottom
  • safe-area-inset-left

需要注意的是,在竖屏和横屏状态下,safe-area-inset-* 值是不同的。比如,竖屏状态下环境变量 safe-area-inset-leftsafe-area-inset-right 的值为 0,横屏状态下环境变量 safe-area-inset-top 的值为 0


---.webp.jpg


上图源自 Deng's Blog


通过 env()constant() 函数就能引用以上几个环境变量,对于不支持 env()constant() 的浏览器,包含它的样式规则将被忽略。


自 Safari Technology Preview 41 和 iOS 11.2 beta 起,constant() 函数已被移除,并用 env() 函数替换(详见)。可为了兼容性,一般两个都会写。

另外,若要环境变量 safe-area-inset-* 生效,需将页面设置为 viewport-fit: cover


接下来,会介绍如何适配 iPhone X 系列刘海屏手机,有以下示例:


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      body {
        width: 100%;
        background-color: darkorange;
      }
      .container {
        height: 100%;
        min-height: 200vh;
        background-color: forestgreen;
      }
    </style>
  </head>
  <body>
    <div class="container"></div>
  </body>
</html>



当我们不做任何处理,以上示例在 iPhone X 系列手机横屏状态下,左右边框会空出一部分,究其原因就是 Safari 浏览器会将网页内容置于「安全区之内」,相当于 viewport-fit: contain


】】.webp.jpg

当我们将 标签内的 viewport-fit 改为 cover 之后,并在页面中添加一首诗。

、、.webp.jpg

这样,Viewport 就占满了显示设备最大的矩形,但因为设备的刘海、圆角等因素,会导致页面中的部分内容无法完全显示。


然后,我们试着在 container 内添加左右外边距,其值分别取 safe-area-inset-leftsafe-area-inset-right 环境变量。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      body {
        width: 100%;
        background-color: darkorange;
      }
      .container {
        height: 100%;
        min-height: 200vh;
        background-color: forestgreen;
        /* 新增 */
        margin-left: env(safe-area-inset-left);
        margin-right: env(safe-area-inset-left);
      }
    </style>
  </head>
  <body>
    <div class="container">
      <article>
        <h1>蒹葭</h1>
        <p>蒹葭苍苍,白露为霜。所谓伊人,在水一方。溯洄从之,道阻且长。溯游从之,宛在水中央。</p>
        <p>蒹葭萋萋,白露未晞。所谓伊人,在水之湄。溯洄从之,道阻且跻。溯游从之,宛在水中坻。</p>
        <p>蒹葭采采,白露未已。所谓伊人,在水之涘。溯洄从之,道阻且右。溯游从之,宛在水中沚。</p>
        <p></p>
      </article>
    </div>
  </body>
</html>


横屏、竖屏显示如下:


】】】.webp.jpg


考虑 env()constant() 函数兼容性的写法如下:


@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
  .container {
    /* 请注意,constant 和 env 先后顺序如下 */
    margin-left: constant(safe-area-inset-left); /* 兼容 iOS < 11.2 */
    margin-left: env(safe-area-inset-left); /* 兼容 iOS >= 11.2 */
    margin-right: constant(safe-area-inset-right);
    margin-right: env(safe-area-inset-right);
  }
}


2.4 max 和 min 函数


从文章排版来看,这是极不美观的,我们希望在左右两边再加点内边距。PS:上述图片为了更方便对比,采用了外边距(Margin),接下来会将其修改为内边距(Padding)。


.container {
  /* 这里将原先的 margin 修改为 padding */
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-left);
}

但有个小小的需求,我们只希望竖屏状态下,添加 10px 的左右内边距,而横屏状态下取 safe-area-inset-* 的值就好。

它们提供了两个 CSS 函数:min()max(),利用它们就能实现这需求。


.container {
  /* 这里使用到 max() 函数,表示取二者最大值。 */
  padding-left: max(10px, env(safe-area-inset-left));
  padding-right: max(10px, env(safe-area-inset-left));
}


效果如下:


’‘’.webp.jpg


如果考虑兼容性的话,可以使用 @supports 语法来处理:


@supports (padding: max(0px)) {
  .container {
    padding-left: max(10px, env(safe-area-inset-left));
    padding-right: max(10px, env(safe-area-inset-left));
  }
}


对于 max()min() 的语法和使用非常地简单,分别表示取最大值和最小值。

两者语法是一致的,以 max 为例:


property: max(expression [, expression])


它接受一个或多个值,若有多个值则采用逗号 , 分隔,选择最大的值作为 CSS 属性的值。每个值除了可以是直接数值,还可以是数学运算(如 calc())、其他表达式(如 attr())。


还支持嵌套 max()min() 函数,需要时可以使用小括号 () 来设定运算顺序。

兼容性如下,一如既往 IE 全系不支持:


,,,》.webp.jpg


至此,相信你对 iPhone X 等机型的适配有更深刻的了解,适配起来就完全没有压力了。


三、CSS At-rules


一个 at-rule 是一个 CSS 语句,它以 @ 符号开头,后接一个标识符,并包括直到下一个分号 ; 的所有内容或下一个 CSS 块,以先到者为准。

主要可分为不可嵌套、可嵌套两类:


不可嵌套 at-rule:

  • @charset:指定样式表中使用的字符编码。
  • @import:导入其他外部样式表。
  • @namespace:指示  CSS 引擎必须考虑XML命名空间。

可嵌套 at-rule:

  • @media:用于基于一个或多个媒体查询的结果来应用样式表中的一部分。
  • @font-face:指定一个用于显示文本的自定义字体。
  • @keyframs:通过在动画序列中定义关键帧的样式来控制 CSS 动画序列中的中间步骤。
  • @supports:指定依赖于浏览器中的一个或多个特定的 CSS 功能的支持声明。
  • @document:根据文档的 URL 限制其中包含的样式规则的作用范围(实验特性)。
  • @page:用于在打印文档时修改某些 CSS 属性。

每个 at-rule 规则都有不同的语法,有一部分 at-rule 可以归为一类:条件规则组

这些规则组所指的条件总等效于 truefalse,如果为 true 那么它里面的 CSS 语句生效。

本文仅介绍 @supports@media,其他规则请看 CSS At-rules


3.1 @supports


@supports 常用于 CSS 兼容性判断。

它由一组支持条件和一组样式声明组成。支持条件可以是一个或多个条件使用逻辑与 and、逻辑或 or、逻辑非 not 组合而成。

  • 单一条件:由一个 CSS 属性和属性值组成,中间用分号 ; 隔开。


@supports (transform-origin: 5% 5%) {
  /* 样式声明 */
}

transform-origin 的实现语法认为 5% 5% 是有效的值,表达式会返回 true,此时规则内声明的样式就会生效。

  • 多个条件:使用 notandor 操作符组合。

相当于 JavaScript 中的 !&&|| 操作符啦,需设定运算顺序,则使用括号包裹。


/* 当 transform-origin: 10em 10em 10em 无效时,表达式返回 true */
@supports not (transform-origin: 10em 10em 10em) {
  /* 样式声明 */
}


/* 当所有条件同时为真时,表达式才返回 true */
@supports (display: table-cell) and (display: list-item) {
  /* 样式声明 */
}


/* 当条件至少有一个为真时,表达式才返回 true */
@supports (transform-style: preserve) or (-moz-transform-style: preserve) {
  /* 样式声明 */
}


还有一个实验性的语法:selector(),有兴趣请看这里


??.webp.jpg


兼容性仍然是 IE 全系不支持,呵呵~


3.2 @media


未完待续...



目录
相关文章
|
6月前
|
前端开发 开发者
CSS变量,也被称为CSS自定义属性或级联变量
【4月更文挑战第7天】CSS变量,也被称为CSS自定义属性或级联变量
53 3
|
17天前
|
前端开发 JavaScript
如何在 JavaScript 中访问和修改 CSS 变量?
【10月更文挑战第28天】通过以上方法,可以在JavaScript中灵活地访问和修改CSS变量,从而实现根据用户交互、页面状态等动态地改变页面样式,为网页添加更多的交互性和动态效果。在实际应用中,可以根据具体的需求和场景选择合适的方法来操作CSS变量。
|
17天前
|
前端开发 JavaScript 数据处理
CSS 变量的作用域和 JavaScript 变量的作用域有什么不同?
【10月更文挑战第28天】CSS变量和JavaScript变量虽然都有各自的作用域概念,但由于它们所属的语言和应用场景不同,其作用域的定义、范围、覆盖规则以及与其他语言特性的交互方式等方面都存在明显的差异。理解这些差异有助于更好地在Web开发中分别运用它们来实现预期的页面效果和功能逻辑。
|
17天前
|
前端开发 JavaScript
如何在 CSS 变量中使用函数?
【10月更文挑战第28天】虽然CSS变量本身不能像传统编程语言中的函数那样直接进行复杂的运算和逻辑处理,但通过CSS预处理器、`calc()` 函数以及与JavaScript的结合,可以在很大程度上实现类似函数的功能,提高CSS样式的灵活性和可维护性,满足各种不同的页面设计和交互需求。
|
17天前
|
前端开发 开发者 容器
CSS 变量的作用域是什么?
【10月更文挑战第28天】理解CSS变量的作用域规则对于有效地使用CSS变量来组织和管理页面样式非常重要。通过合理地利用全局作用域和局部作用域,以及掌握变量的覆盖和继承规则,可以创建更具可维护性、灵活性和可扩展性的CSS样式表,实现各种复杂的页面设计和样式需求。
|
17天前
|
前端开发 JavaScript UED
|
17天前
|
前端开发 JavaScript UED
如何使用 JavaScript 动态修改 CSS 变量的值?
【10月更文挑战第28天】使用JavaScript动态修改CSS变量的值可以为页面带来更丰富的交互效果和动态样式变化,根据不同的应用场景和需求,可以选择合适的方法来实现CSS变量的动态修改,从而提高页面的灵活性和用户体验。
|
2月前
|
前端开发
Vue3基础(十ba)___在css中使用props或者计算属性的变量,来实现动态样式
本文介绍了如何在Vue3中通过CSS变量和props或计算属性来实现动态样式。
44 0
|
5月前
|
前端开发 JavaScript 开发者
CSS进阶-CSS变量
【6月更文挑战第13天】本文介绍了CSS变量(Custom Properties)的基本概念、应用场景和常见问题。通过声明与使用示例,展示了如何定义和引用变量。文章讨论了兼容性、作用域、错误引用及JavaScript交互等易错点,并提供了相应的解决方案。此外,还分享了实践技巧,如模块化色彩系统、响应式设计和动画制作。通过学习,开发者能更好地利用CSS变量提高样式灵活性和维护性。
66 3
|
4月前
|
前端开发 JavaScript 编译器
如何在CSS中写变量?一文带你了解前端样式利器
如何在CSS中写变量?一文带你了解前端样式利器
56 0

热门文章

最新文章

下一篇
无影云桌面