什么不支持 transition ? 那是你不知道 @property 好不好?

引言

我们都知道在 CSS 中我们能够通过 -- 来自定义 CSS 属性

css 复制代码
:root {
  --width: 20px;
}

定义了自定义属性后, 可以通过 var 进行引用(调用)

css 复制代码
:root {
  --width: 200px;
}

.box {
  width: var(--width);
}

上面 👆🏻 就是我们常用的 CSS 变量, 很强大, 通过它我们可以实现很多原先需要 JS 参与才能实现的功能!!!

那么本文要讲的 @property 功能其实和上文一样, 同样是用于自定 CSS 属性!!! 但是它又和直接使用 -- 来自定义 CSS 属性又有所区别, 而这也正是本文的重点....

一、@property

1.1 简介

@propertyCSS 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: 大部分文章里都提到如下两条规则, 但经过测试发现 syntaxinheritsinitial-value 三个都需要必填, 所以保守点在实际开发中还是能填则填吧

  • 规则中 syntaxinherits 是必需的;
  • initial-value 仅在 syntax 为通用("*") 时是可选的; 否则 initial-value 也是必需的, 如果缺失, 整条规则都将失效且被忽略

1.2 syntax

@property 声明中除了 syntax 比较复杂, 其他两个字段(inheritsinitial-value)则比较简单, 所以这里重点介绍下 syntax!!

  1. syntax 中支持的类型很多, 大部分 CSS 类型都是支持的, 下表是常用的一些类型介绍; 更多信息可查阅 @property/syntax

| 类型 | 介绍 | 有效值 | | --- | --- | | "<length>" | 长度, 用于表示距离尺寸的值, 由一组数字和长度单位组成 | 1px1em1vw | | "<number>" | 数字, 可为整数或小数 | 12.1 | | "<percentage>" | 百分比 | 10%50% | | "<length-percentage>" | <length><percentage>, 即长度或百分比 | 1px1em1vw10%50% | | "<color>" | 颜色, 支持所有 CSS 颜色格式 | red#000#ff0099rgb(255 0 153)hsl(150 30% 60%) | | "<angle>" | 角度值, 由数字加单位组成, 常见单位有: 度(deg)、百分度(grad)、弧度(rad)、圈数(turn) | 90deg38.8grad6.2832rad0.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.5s150.25ms | | "<resolution>" | 分辨率, 一般用于描述媒体查询中的分辨率值, 由数字加单位组成, 常见的单位有: dpi(每英寸的点数)、dpcm(每厘米的点数)、dppx(每一 px 的点数) | 1dpcm10dpcm | | "<custom-ident>" | 自定义字符串标识符 | nono79ground-level |

  1. 复杂组合

| 符号 | 介绍 | 例 | 有效值 | | --- | --- | --- | | + | 定义空格分隔的列表 | <length>+: 由空格分隔的一组长度(初始值 initial-value 单位需要统一) | 10px 30px 40px | | # | 定义逗号分隔的列表 | <length>#: 由逗号分隔的一组长度(初始值 initial-value 单位需要统一) | 10px, 30px, 40px | | | | 或, 定义多种情况 | <length>+ | <number>#: 由空格分隔的一组长度 或者 由逗号分隔的一组长度 | 10px 30em 40vw10px, 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 属性, 是不支持设置补间动画的!! 也就是该属性在 transitionanimation 中就没办法实现自然的过渡动画

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 属性, 就支持自动添加补间动画

这样的话在 transitionanimation 中就可以实现丝滑的过渡动画

下面我们使用 @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 中有很大一部分属性它是不支持补间动画的, 所以很多时候我们无法直接通过 transitionanimation 实现一些比较自然的动画效果, 这时往往需要结合 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',
})

四、参考

相关推荐
光影少年13 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_14 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891116 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾18 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking18 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu20 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym25 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫26 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫30 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat31 分钟前
前端性能优化2
前端