CSS @property
@property - CSS: Cascading Style Sheets | MDN
At 规则 - CSS:层叠样式表 | MDN
Custom properties (–*): CSS variables - CSS: Cascading Style Sheets | MDN
CSS Houdini - Developer guides | MDN
📚 什么是@property?
@property
CSS at-rule 是 CSS Houdini API [🔗] 的一部分,它允许开发者显式地定义他们的CSS 自定义属性, 允许进行属性类型检查、设定默认值以及定义该自定义属性是否可以被继承**。
- @property 规则提供了一个直接在样式表中注册自定义属性的方式,而无需运行任何 JS 代码。
- 有效的 @property 规则会注册一个自定义属性,就像 【CSS.registerProperty🔗】 函数被使用同样的参数调用了一样。
在过去,我们使用CSS自定义变量(CSS Variables)来存储和复用值,但它们并不具备类型检查和默认值设定的功能。
而CSS @property则弥补了这一空白,使得自定义属性更加功能丰富和强大。
💎 语法
@property --property-name {syntax: "<color>";inherits: false;initial-value: #c0ffee;
}
--property-name
: 自定义属性名称syntax
: 定义了自定义属性接受的值的类型。- CSS 基本数据类型 - CSS:层叠样式表 | MDN
- 可能是(长度)、(数字)、(百分比)、(长度百分比)、(颜色)、(图像)、(URL地址)、(整数)、(角度)、
+
(空格分隔)和#
字号(逗号分隔)的乘法器表示期望的是一个值的列表,- 例如
<color>#
意味着期望的语法是一个以逗号分隔的<color>值列表
。
- 例如
- 竖线(|)可以为预期的语法创建"或"条件,
- 例如
<length> | auto
接受<length>或auto
,而<color># | <integer>#
期望的是以逗号分隔的<color>值列表
或以逗号分隔的<integer>值列表
。
- 例如
inherits
: 指定该自定义属性是否可以被子元素继承,默认为 false。initial-value
:设置自定义属性的默认值。
@property 规则中 syntax
和 inherits
描述符是必需的;
如果其中任何一项缺失,整条规则都将失效并且会被忽略。
initial-value
描述符仅在 syntax 描述符为通用 syntax 定义时是可选的,否则initial-value也是必需的——如果此时该描述符缺失,整条规则都将失效且被忽略。
未知的描述符自身都是无效的,且会被忽略。但是不会造成整条@property规则的失效。
例子 1:
<div class="container"><div class="item one">Item one</div><div class="item two">Item two</div><div class="item three">Item three</div>
</div>
- 定义两个自定义属性,
--item-size
和--item-color
,用它们来定义三个子元素item
的宽度和高度以及背景颜色。
/* --item-size and --item-color */
@property --item-size {syntax: "<percentage>";inherits: true;initial-value: 40%;
}@property --item-color {syntax: "<color>";inherits: false;initial-value: aqua;
}
- 自定义属性
--item-size
:- 该属性接受的值的类型只是百分比
<percentage>
; - 初始值设置为40%;
- 属性是可继承的,这意味着,当用作项目大小的值时,它的大小将始终相对于其父级的大小。
- 该属性接受的值的类型只是百分比
- 自定义属性
--item-color
:- 该属性接受的值的类型 只是
<color>
类型 - 初始值是 关键字
aqua
- 属性不继承
- 该属性接受的值的类型 只是
.container {display: flex;height: 200px;border: 1px dashed black;/* 使用自定义属性 *//* 在父元素 设置了自定义属性的值 */--item-size: 20%;--item-color: orange;
}/* 使用自定义属性 设置 item的 宽高 和背景颜色 */
.item {width: var(--item-size);height: var(--item-size);background-color: var(--item-color);
}/* 设置自定义属性在元素自己身上的值 */
.two {--item-size: initial;--item-color: inherit;
}.three {/* 无效值 */--item-size: 1000px;--item-color: xyz;
}
🍀 分析:
-
两个自定义属性
--item-size: 20%
和--item-color: orange;
设置在父级容器container
上,覆盖了定义时设置的默认值:--item-size:40%
和--item-color:aqua
。其中--item-size
为可继承;--item-color
不可继承。 -
对 class 为
item
的子元素,通过自定义属性设置了 宽高和背景颜色。- 这个时候,宽高的值是 相对于父容器的宽的
20%
。因为,父容器重新设置--item-size
的值。
- 这个时候,宽高的值是 相对于父容器的宽的
-
对于
one
,没有设置这些自定义属性。--item-size
是可继承的,因此使用其父容器上设置的值20% 。--item-color
是不可继承的,因此不考虑父级上的orange
。而是使用默认的初始值 aqua。
-
对于
two
,对两个自定义属性--item-size
、--item-color
设置了 CSS全局关键字,这两个属性对于所有值类型都是有效值,因此无论语法描述符的值如何都是有效的。--item-size:initial
: 使用该属性的初始值。在 @property 声明中设置的初始值initial-value
为40%
;--item-color:inherit
: 表示从其父元素(也就是container
)继承orange
。即使自定义属性被设置为不被继承,也要显式地从其父级继承orange
。
-
对于
three
,--item-size
和--item-color
都是无效值。--item-size
值为1000px
。虽然1000px
是一个<length>
值,但是@property 声明时要求该值是一个<percentage>
类型。因此该声明无效并被忽略,这意味着使用了父级上可继承的20%
。--item-color
值为xyz
也是无效的。- 首先,值
xyz
不是 CSS 数据类型[<color>🔗](https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value)
的关键字<color-name>
的有效值。所以会被忽略,所以直接显示的是item
定义的样式。 - 其次,
--item-color
不能被继承,因此使用aqua
的默认值,也不使用父级的值orange
。
- 首先,值
例子 2:使用 CSS @property 实现背景色渐变动画
@property --colorA {syntax: "<color>";inherits: false;initial-value: red;
}@property --colorB {syntax: "<color>";inherits: false;initial-value: yellow;
}@property --colorC {syntax: "<color>";inherits: false;initial-value: blue;
}.box {width: 300px;height: 300px;background: linear-gradient(45deg,var(--colorA),var(--colorB), var(--colorC));animation: animate 3s linear infinite alternate;
}@keyframes animate {20% {--colorA: blue;--colorB: #F57F17;--colorC: red;}40% {--colorA: #FF1744;--colorB: #5E35B1;--colorC: yellow;}60% {--colorA: #E53935;--colorB: #1E88E5;--colorC: #4CAF50;}80% {--colorA: #76FF03;--colorB: teal;--colorC: indigo;}
}
<div class="box"></div>
例子3: 使用自定义属性完成图片切换
<div class="section"><div class="box bg mask1"></div><div class="box bg mask2"></div></div>
$img1: 'https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/191/191-bigskin-6.jpg';
$img2: 'https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/191/191-bigskin-8.jpg';
$mask1: linear-gradient(45deg, #000 0, #000 var(--per), transparent calc(var(--per) + 10%), transparent);
$mask2: conic-gradient(#000 0, #000 var(--per), transparent calc(var(--per) + 10%), transparent);
@property --per {syntax: '<percentage>';inherits: false;initial-value: -10%;
}
.section {width: 100%;display: -webkit-box;display: -ms-flexbox;display: flex;
}
.section div {margin: 20px;
}
.section {.box {width: 600px;height: 300px;}.bg {background: url($img1);background-repeat: no-repeat;background-position: 50%;background-size: cover;position: relative;&::after {content: '';position: absolute;top: 0;bottom: 0;left: 0;right: 0;background: url($img2);background-size: cover;background-position: 50%;animation: animate 2s ease-in-out infinite alternate;}}.mask1 {&::after {mask: $mask1;}}.mask2 {&::after {mask: $mask2;}}
}@keyframes animate {0% {--per: -10%;}100% {--per: 100%;}
}