引言
我们都知道在 CSS 中我们能够通过 -- 来自定义 CSS 属性
css
:root {
--width: 20px;
}
定义了自定义属性后, 可以通过 var 进行引用(调用)
css
:root {
--width: 200px;
}
.box {
width: var(--width);
}
上面 👆🏻 就是我们常用的 CSS 变量, 很强大, 通过它我们可以实现很多原先需要 JS 参与才能实现的功能!!!
那么本文要讲的 @property 功能其实和上文一样, 同样是用于自定 CSS 属性!!! 但是它又和直接使用 -- 来自定义 CSS 属性又有所区别, 而这也正是本文的重点....
一、@property
1.1 简介
@property 是 CSS Houdini API 中的一个功能, 通过它我们可以显式地自定义 CSS 属性!! 同时我们还可以为定义的 CSS 属性设置类型检查、默认值以及该属性是否可以被继承!!
@property 语法比较简单, 如下代码所示:
@property自然不用解释, 它就是个关键词用于表示要定义一个CSS自定义属性--property-name则是自定义的CSS属性名称- 花括号
{}内则是声明字段(declaration-list), 其实就是对自定义属性的描述(规定) syntax则是定义属性值的类型, 在本例子中<color>表示定义的属性, 它的值是一个颜色inherits则是表示该属性值是否允许被继承initial-value则是用于设置默认值
css
@property --property-name {
syntax: "<color>";
inherits: false;
initial-value: #c0ffee;
}
用法和之前的保持一致, 都是配合 var() 来使用
html
<style>
@property --color {
syntax: "<color>";
inherits: false;
initial-value: #c0ffee;
}
.box {
--color: #000;
color: var(--color);
}
</style>
<div class="box">111</div>

补充 1: 如下代码
:root中--color: #000;是无效的, 会被@property --color覆盖
html
<style>
@property --color {
syntax: "<color>";
inherits: false;
initial-value: #c0ffee;
}
:root {
--color: #000;
}
.box {
color: var(--color);
}
</style>
<div class="box">111</div>

补充 2: 大部分文章里都提到如下两条规则, 但经过测试发现
syntax、inherits、initial-value三个都需要必填, 所以保守点在实际开发中还是能填则填吧
- 规则中
syntax和inherits是必需的;initial-value仅在syntax为通用("*") 时是可选的; 否则initial-value也是必需的, 如果缺失, 整条规则都将失效且被忽略
1.2 syntax
@property 声明中除了 syntax 比较复杂, 其他两个字段(inherits、initial-value)则比较简单, 所以这里重点介绍下 syntax!!
syntax中支持的类型很多, 大部分CSS类型都是支持的, 下表是常用的一些类型介绍; 更多信息可查阅 @property/syntax
| 类型 | 介绍 | 有效值 | | --- | --- | | "<length>" | 长度, 用于表示距离尺寸的值, 由一组数字和长度单位组成 | 1px、1em、1vw | | "<number>" | 数字, 可为整数或小数 | 1、2.1 | | "<percentage>" | 百分比 | 10%、50% | | "<length-percentage>" | <length> 或 <percentage>, 即长度或百分比 | 1px 、1em、1vw、10%、50% | | "<color>" | 颜色, 支持所有 CSS 颜色格式 | red、#000、#ff0099、rgb(255 0 153)、hsl(150 30% 60%) | | "<angle>" | 角度值, 由数字加单位组成, 常见单位有: 度(deg)、百分度(grad)、弧度(rad)、圈数(turn) | 90deg、38.8grad、6.2832rad、0.25turn | | "<transform-function>" | transform 变换函数 | rotate(45deg)、scale(1.5)、translate(10px, 10px) | | "<transform-list>" | 有效的 <transform-function> 列表 | rotate(45deg) translate(10px 10px) | | "<url>" | 任何有效的 url() 值 | url(./1.png) | | "<image>" | 图片类型, 包括 url()、渐变、element() | url(./1.png)、linear-gradient(blue, red)、element(#colonne3) | | "<integer>" | 整型, 包括正整形、负整形 | 12、+12、-12 | | "<time>" | 时间, 以秒(s)或毫秒(ms)为单位的时间值 | 1.5s、150.25ms | | "<resolution>" | 分辨率, 一般用于描述媒体查询中的分辨率值, 由数字加单位组成, 常见的单位有: dpi(每英寸的点数)、dpcm(每厘米的点数)、dppx(每一 px 的点数) | 1dpcm、10dpcm | | "<custom-ident>" | 自定义字符串标识符 | nono79、ground-level |
- 复杂组合
| 符号 | 介绍 | 例 | 有效值 | | --- | --- | --- | | + | 定义空格分隔的列表 | <length>+: 由空格分隔的一组长度(初始值 initial-value 单位需要统一) | 10px 30px 40px | | # | 定义逗号分隔的列表 | <length>#: 由逗号分隔的一组长度(初始值 initial-value 单位需要统一) | 10px, 30px, 40px | | | | 或, 定义多种情况 | <length>+ | <number>#: 由空格分隔的一组长度 或者 由逗号分隔的一组长度 | 10px 30em 40vw、 10px, 30em, 40vw |
二、特别之处
既然都是自定义 CSS 属性, 那么使用 @property 和直接进行自定义又有啥不同呢?
2.1 类型校验
是的和常规的
CSS自定义属性相比, 多了类型校验!! 更加规范!!!
下面使用常规方法自定义了一个 CSS 属性 --color; 初始值为 #000, 后面修改为 20px, 你会发现这个改动是会生效的
css
:root {
--color: #000;
}
.box {
--color: 20px; /* 生效 */
}

同样的情况放在 @property 就会失效, 因为这里在设置值时会进行类型校验
css
@property --color {
syntax: "<color>";
inherits: false;
initial-value: #000;
}
.box {
--color: 20px; /* 失效 */
}

2.2 继承
常规定义的 CSS 属性, 被作为 var() 变量使用时, 如果该值是无效的, 那么整个属性将失效
html
<style>
:root {
--color: #ff4d4f;
}
.parent {
--color: #ff9c6e;
}
.child {
--color: 20px;
color: var(--color); /* 整个属性失效 */
}
</style>
<div class="parent">
<div class="child">
child
</div>
</div>

同样的情况放在 @property 中, 如果 CSS 自定义属性 --color 允许被继承, 那么在为自定义属性赋值时会先进行类型校验, 如果是值是无效的, 就会 继承 自外部元素
html
<style>
@property --color {
syntax: "<color>";
inherits: true; /* 允许继承 */
initial-value: #ff4d4f;
}
.parent {
--color: #ff9c6e;
}
.child {
--color: 20px; /* 无效值, --color 将继承自外部元素 */
color: var(--color);
}
</style>
<div class="parent">
<div class="child">
child
</div>
</div>

上文是开启继承的情况, 如果没有启用继承, 那么在值校验时若发现是个无效值, 将直接取 初始值
html
<style>
@property --color {
syntax: "<color>";
inherits: false; /* 不允许继承 */
initial-value: #ff4d4f;
}
.parent {
--color: #ff9c6e;
}
.child {
--color: 20px;
color: var(--color); /* 整个属性失效 */
}
</style>
<div class="parent">
<div class="child">
child
</div>
</div>

2.3 自动添加补间动画 (支持「transition」)
直接定义出来的 CSS 属性, 是不支持设置补间动画的!! 也就是该属性在 transition 和 animation 中就没办法实现自然的过渡动画
html
<style>
.box {
padding: 20px;
background-color: #ffccc7;
--width: 200px; /* 自定义属性 */
width: var(--width);
transition: --width 0.4s; /* 为 --width 设置过渡动画(不生效) */
}
.box:hover {
--width: 400px;
}
</style>
<div class="box">box</div>
如图, 当鼠标 hover 到容器 .box 上, 容器宽度变大, 但是宽度的变化是没有过渡动画的, 显得很生硬

这里我们就可以使用 @property 来自定义 CSS 属性, 用它定义出来的 CSS 属性, 就支持自动添加补间动画
这样的话在 transition 和 animation 中就可以实现丝滑的过渡动画
下面我们使用 @property 来改造代码:
html
<style>
/* 自定义属性 */
@property --width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
.box {
padding: 20px;
background-color: #ffccc7;
--width: 200px;
width: var(--width);
transition: --width 0.4s; /* 为 --width 设置过渡动画(生效) */
}
.box:hover {
--width: 400px;
}
</style>
<div class="box">box</div>
如图, 当鼠标 hover 到容器 .box 上, 容器宽度变大, 同时宽度的变化是有过渡动画的, 显得很是丝滑

重点: 需要特别注意的是, 上文中我们并不是为 width 设置了过渡动画, 而是给自定义属性 --width 设置了过渡动画, 然后通过 CSS 变量 var() 将 --width 实时作用于 width
这一点特别关键, 在 CSS 中有很大一部分属性它是不支持补间动画的, 所以很多时候我们无法直接通过 transition 或 animation 实现一些比较自然的动画效果, 这时往往需要结合 JS 来实现
现在 @property 的出现将彻底改变这一现状, 对于不支持自动补间动画的属性, 我们完全可以将要 过渡的值 抽离出一个 CSS 自定义属性, 然后针对该属性设置动画, 然后使用 CSS 变量的方式, 将自定义属性实时应用到指定属性上!!
下面来看个例子, 如下代码:
html
<style>
.box {
width: 200px;
height: 200px;
padding: 10px;
background-image: linear-gradient(#ff4d4f, #597ef7);
transition: all 1s;
}
.box:hover {
background-image: linear-gradient(#ffa940, #f759ab);
}
</style>
<div class="box">box</div>
如下, 整个过渡是很生硬的, 即便我们设置了 transition

那么这里我们就可以使用 @property 进行一个改造:
- 这里我们需要针对背景色进行一个过渡, 所以我们可以抽离出两个自定义属性
--gradient-color-first、--gradient-color-second - 然后针对
--gradient-color-first、--gradient-color-second设置过渡属性 - 最后
--gradient-color-first、--gradient-color-second实现平滑的颜色过渡, 间接实现background-image: linear-gradient()的平滑过渡
html
<style>
@property --gradient-color-first {
syntax: "<color>";
inherits: false;
initial-value: #000;
}
@property --gradient-color-second {
syntax: "<color>";
inherits: false;
initial-value: #000;
}
.box {
--gradient-color-first:#ff4d4f;
--gradient-color-second:#597ef7;
width: 200px;
height: 200px;
padding: 10px;
background-image: linear-gradient(var(--gradient-color-first), var(--gradient-color-second));
transition: 1s --gradient-color-first, 1s --gradient-color-second;
}
.box:hover {
--gradient-color-first:#ffa940;
--gradient-color-second:#f759ab;
}
</style>
<div class="box">box</div>
最后效果如下:

三、registerProperty()
同样, 在 JS 中我们也可以通过原生提供的 CSS.registerProperty 方法来注册 CSS 自定义属性
如下代码演示, name 则是自定义属性名, 其他字段和 @property 中的描述字段是一样的!!!
js
window.CSS.registerProperty({
name: '--primary-color',
syntax: '<color>',
inherits: false,
initialValue: 'green',
})