CSS @property – 实验性

image.png

🤷‍♂️ 这是一个实验性技术,正如你所见,它兼容性目前糟糕!但是 Chrome 的发展是在不断支持它的,接下来了解一下它。

介绍

@ property CSS at-rule 是 CSS Houdini api 的一部分,它允许开发人员显式定义 CSS 自定义属性,允许属性类型检查,设置默认值,并定义属性是否可以继承值。

该规则直接在样式表中表示自定义属性的注册,无需运行任何 JS。有效的规则会生成一个已注册的自定义属性,就好像使用等效的 parameters.@property@property 调用了 CSS.registerProperty 一样

Houdini 是一组底层 api,它公开了 CSS 引擎的部分内容,使开发人员能够通过钩入浏览器渲染引擎的样式和布局过程来扩展 CSS。Houdini 是一组 api,可以让开发者直接访问 CSS 对象模型(CSSOM) ,使开发者能够编写浏览器可以解析为 CSS 的代码,从而创建新的 CSS 特性,而无需等待这些特性在浏览器中自行实现。

语法

@property --propery-name {
  syntax: '<color>';
  inherits: false;
  initial-value: #c0ffee;
}

一个有效的规则表示自定义属性注册,属性名称是规则的 prelude.@property 中的序列化。

@property 规则需要语法(syntax)和继承描述符(inheritsdescriptor); 如果缺少其中一个,则整个规则无效,必须忽略。只有在语法是通用语法定义的情况下,初始值描述符才是可选的,否则就需要描述符; 如果缺少初始值描述符,则整个规则无效,必须忽略它。

未知的描述符无效并被忽略,但是不要使 rule.@property 无效。

注意:应该在选择器块外面声明

如果您仍然不是很明白,接下来我们创建一个自定义属性 –my-color:

@property --my-color {
  syntax: '<color>';
  inherits: false;
  initial-value: #c0ffee;
}
  • syntax:语法
  • @property –my-color:声明一个自定义属性 –mycolor
  • inherits:是否允许继承
  • initial-value:初始值

如果使用 Javascript 实现,等价于:

window.CSS.registerProperty({
  name: '--my-color',
  syntax: '<color>',
  inherits: false,
  initialValue: '#c0ffee',
});

例子

如果您经常使用 css 的 transition 属性,试着回想一下,是不是遇到过这种情况:在给某个属性添加 transition 时,却没有生效。看下面这段 css 代码:

.el {
  background: linear-gradient(white, black);
  /* this transition won't work */
  transition: 1s;
}
.el:hover {
  background: linear-gradient(red, black);
}

你可能认为这个渐变中的白色会随着过渡渐变为红色,但事实并非如此,这种过渡是不可能的。如果我们过去需要使用一些小技巧实现它,比如用新的渐变颜色在伪元素中褪色,或者用比元素渐变更宽的背景位置来伪造它。

但是现在我们可以这样做了:

@property --gradient-start {
  syntax: "<color>";
  initial-value: white;
  inherits: false;
}
.el {
  --gradient-start: white;
  background: linear-gradient(var(--gradient-start), black);
  transition: --gradient-start 1s;
}
.el:hover {
  --gradient-start: red;
}

因为我们告诉 CSS 这个自定义属性是一个 <color>  ,所以它可以被处理或者动画化。之前的颜色值是无法被动画化的。

这里有个有趣的示例:
Video_2020-11-22_211946.gif

实现思路很简单,使用 CSS3 border-image 属性设置边框颜色为渐变色,然后使用 animation 改变渐变方向。代码大概这样子:

<div class="wrapper">
  <div class="box">
    <p>圆锥形渐变</p>
  </div>
  <div class="box">
    <p>径向渐变</p>
  </div>
</div>
:root {
  --angle: 90deg;
  --gradX: 100%;
  --gradY: 50%;
  --d: 2500ms;
  --c1: rgba(168, 239, 255, 1);
  --c2: rgba(66, 106, 116, 0.1);
}
.box {
  border-image: conic-gradient(from var(--angle), var(--c2), var(--c1) 0.1turn, var(--c1) 0.15turn, var(--c2) 0.25turn) 30;
  animation: borderRotate var(--d) linear infinite forwards;
}
.box:nth-child(2) {
  border-image: radial-gradient(ellipse at var(--gradX) var(--gradY), var(--c1), var(--c1) 10%, var(--c2) 40%) 30;
  animation: borderRadial var(--d) linear infinite forwards;
}
@keyframes borderRotate {
  100% {
    --angle: 420deg;
  }
}
@keyframes borderRadial {
  20% {
    --gradX: 100%;
    --gradY: 50%;
  }
  40% {
    --gradX: 100%;
    --gradY: 100%;
  }
  60% {
    --gradX: 50%;
    --gradY: 100%;
  }
  80% {
    --gradX: 0%;
    --gradY: 50%;
  }
  100% {
    --gradX: 50%;
    --gradY: 0%;
  }
}

此时,你会发现 border 上的渐变并不会因为设置了 animation 就动起来,这时候 @property 就排上用场了。
我们在 css 中先使用 @property 声明这几个需要变化的属性。

@property --angle {
  syntax: '<angle>';
  initial-value: 90deg;
  inherits: true;
}
@property --gradX {
  syntax: '<percentage>';
  initial-value: 50%;
  inherits: true;
}
@property --gradY {
  syntax: '<percentage>';
  initial-value: 0%;
  inherits: true;
}

此时 border 才真正的“动起来”了。

代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            box-sizing: border-box;
        }
        @property --angle {
            syntax: '<angle>';
            initial-value: 90deg;
            inherits: true;
        }
        @property --gradX {
            syntax: '<percentage>';
            initial-value: 50%;
            inherits: true;
        }
        @property --gradY {
            syntax: '<percentage>';
            initial-value: 0%;
            inherits: true;
        }
        body {
            font-family: Raleway, sans-serif;
            text-align: center;
            margin: 0;
            padding: 1rem;
            background-color: rgba(10, 12, 18, 1);
            color: white;
            min-height: 100vh;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: center;
        }
        p {
            margin: 0;
        }
        :root {
            --angle: 90deg;
            --gradX: 100%;
            --gradY: 50%;
            --d: 2500ms;
            --c1: rgba(168, 239, 255, 1);
            --c2: rgba(66, 106, 116, 0.1);
        }
        .wrapper {
            min-width: min(40rem, 100%);
        }
        .box {
            font-size: 3vw;
            margin: max(1rem, 3vw);
            border: 0.35rem solid;
            padding: 3vw;
            border-image: conic-gradient(from var(--angle), var(--c2), var(--c1) 0.1turn, var(--c1) 0.15turn, var(--c2) 0.25turn) 30;
            animation: borderRotate var(--d) linear infinite forwards;
        }
        .box:nth-child(2) {
            border-image: radial-gradient(ellipse at var(--gradX) var(--gradY), var(--c1), var(--c1) 10%, var(--c2) 40%) 30;
            animation: borderRadial var(--d) linear infinite forwards;
        }
        @keyframes borderRotate {
            100% {
                --angle: 420deg;
            }
        }
        @keyframes borderRadial {
            20% {
                --gradX: 100%;
                --gradY: 50%;
            }
            40% {
                --gradX: 100%;
                --gradY: 100%;
            }
            60% {
                --gradX: 50%;
                --gradY: 100%;
            }
            80% {
                --gradX: 0%;
                --gradY: 50%;
            }
            100% {
                --gradX: 50%;
                --gradY: 0%;
            }
        }
    </style>
</head>
<body>
    <div class="wrapper">
        <div class="box">
            <p>圆锥形渐变</p>
        </div>

        <div class="box">
            <p>径向渐变</p>
        </div>
    </div>
</body>
</html>

参考资料