2021 年你可能不知道的 CSS 特性 (上)

简介: 前几天在 淘系前端午夜识堂和大家聊了一下 CSS 方面的话题,即 CSS 新特性。在这个话题中主要整理了有关于 CSS 方面的特性,并且尽可能的整理了一些大家现在能用或过不了多久就能用的属性。另外,虽然标题是“新特性”,但其中有蛮多特性并不是“新”,可能已经出现在你的项目中,或者你已经看过,只是不了解而以。接下来,就和大家一起来简单地回顾一下这些性,希望大家能喜欢,也希望对大家平时工作有所帮助。

作者:大貘

前几天在 淘系前端午夜识堂和大家聊了一下 CSS 方面的话题,即 CSS 新特性。在这个话题中主要整理了有关于 CSS 方面的特性,并且尽可能的整理了一些大家现在能用或过不了多久就能用的属性。另外,虽然标题是“新特性”,但其中有蛮多特性并不是“新”,可能已经出现在你的项目中,或者你已经看过,只是不了解而以。接下来,就和大家一起来简单地回顾一下这些性,希望大家能喜欢,也希望对大家平时工作有所帮助。



简介



如果您有关注过这两年的 CSS 发展状态报告2019年2020年,https://stateofcss.com/)的话,不难发现,在报告中有专门关于 CSS 新特性 (https://2020.stateofcss.com/en-US/features/)一项的介绍:

image.png

上图截取 2020 年 CSS 发展状态报告!(https://2020.stateofcss.com/en-US/features/)

是不是有种似有相识的感觉,或者有一些又是那么的陌生。对于我而言,这些都不陌生。不过我想说的是上图(或者报告)中提到的只是冰山一角,有很多是没有出现在调查问卷和报告中。比如接下来内容中提到的伪类选择器,内容可见性,容器查询等等。


我们先从 CSS 选择器开始!(^_^)




CSS 伪类选择器



CSS 选择发展到今日,可以说是一个庞大的体系了:


image.png

特别声明,上图来自于 @linxz 的博客如果上图看不清楚可以点击这里获取原图。(https://raw.githubusercontent.com/linxz/blog/gh-pages/img/2021-05/css-selector.png


现在描述 CSS 选择器的规范主要有 CSS (指的是 CSS 2.1),后面分为选择器 Level 3Level 4。伪类选择器在这三部分规范中都有出现过,他们是一个递进的过程,特别是在 Level 3Level 4,在原有的基础上新增了不少优秀的选择器。今天要提到的几个现代伪类选择器就是出于 Levele 4:

image.png


:is():where()


先来看 :is():where()@Elad Shechter  曾在推特上发了一个关于 :is():where()选择器的测试题

image.png


如果你是第一次看到这样的测试题,请先自测一下,如果是你,你会选择哪个答案(greenpurplered 还是 blue)?如果你选择的是 purple ,那么要恭喜你,你答对了。

image.png

Demo 地址:https://codepen.io/airen/full/ExWxbKe


示例中出现了 :is():where() 两个可能你从未接触过的伪类选择器:



:is(.header, .main) .p {  color: purple}
:where(.header, .main) .p {  color: red;}


其实这两个选择器等同于:



.header .p,.main .p {  color: purple;}
.header .p,.main .p {  color: red;}


他们唯一不同之处,就是选择器权重不同。:where() 的优先级总是为 0 ,但是 :is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。那么上面的示例中,我们可以用下图来清晰的描述他们之间的关系:


image.png

上图地址来自:https://twitter.com/eladsc/status/1372445466709868545


即,下面三个选择器选中的相同的元素:



.header .p,.main .p {  // ...}
:is(.header, .main) .p {  // ...}
:where(.header, .main) .p {  // ...}


不同的是他们的权重不同,其中:



.header .p,.main .p {  // ....}
:is(.header, .main) .p {  // ...}

示例中的两种选择器具有相同的权重(即020);其中:



:where(.header, .main) .p {  // ...}
.p {  // ...}

上面示例代码中的选择器具有相同权重(即 010):


image.png


:is():where() 伪类选择器的出现,将会让我们的选择器变得更简洁,比如下成这个 :is() 的示例:



// Level 0h1 {  font-size: 30px;}//  Level 1 section h1, article h1, aside h1, nav h1 {  font-size: 25px;}// Level 2 section section h1, section article h1, section aside h1, section nav h1,article section h1, article article h1, article aside h1, article nav h1,aside section h1, aside article h1, aside aside h1, aside nav h1,nav section h1, nav article h1, nav aside h1, nav nav h1, {  font-size: 20px;}

使用 :is() 可以像下面这样来描述:




<!-- HTML --><main class="main">  <p class="p">What is the text color?</p></main>
// CSS:where(.header, .main) .p {  color: red;}
.p {  color: blue;}
:is(.header, .main) .p {  color: purple;}
.header .p,.main .p {  color: green;}

可以点击这里查看答案:https://codepen.io/airen/full/dyvyJmW


:not():has()


或许大家平时在开发前端页面的时候,碰到类似下图这样的需求:

希望最后一张卡片没有margin-bottom (或第一张卡片没有 margin-top)。针对于这样的场景,:not() 选择器就非常有优势。为什么这么说呢?


在没有 :not() 选择器的时候,你可能会想到下面这样的方式:



.card + .card {  margin-top: 20px;}  
// 或
.card {  margin-bottom: 20px;}
.card.card--last { // 也可能会使用 .card:last-child  margin-bottom: 0;}

如果换成 :not() 选择器,可以这要来实现:


.card:not(:last-child) {  margin-bottom: 20px}

效果如下:


image.png

Demo URL: https://codepen.io/airen/full/MPNLEo


虽然 CSS 选择器已经非常强大了,但一直以来,在 CSS 中没有从子元素选到父元素的样的选择器(父选择器):


image.png


有人提出希望有一个 :parents() 这样的选择器!


虽然直到目前为止还没有 :parents() 选择器,但庆幸的是,:has() 选择器即将到来,它可以用来选择父级元素。目前 Igalia 公司正在为 Chrome 实现该选择器,其团队成员 Brian Kardell 新发表的《Can I :has()》文章中对 :has() 选择器进行了详细的阐述。



<section><!-- section 边框颜色是 blue,margin-bottom是 30px -->   <h1>H1 Level Title</h1></section>  
<section><!-- section 边框颜色是 #09f,margin-bottom是 30px -->   <h1>H2 Level Title</h1></section>  
<section><!-- section 边框颜色是 red -->   <p>Text Paragraphs</p></section>  /* CSS */
// 将匹配含有h1子元素的 section元素section:has(h1) {  border-color: blue;}// 将匹配含有h2子元素的 section元素section:has(h2) {  border-color: #09f;}// 将匹配含有p子元素的 section元素section:has(p) {  border-color: red;}// 将匹配除了含有p子元素的 section元素section:not(:has(p)) {  margin-bottom: 30px;}


在支持:has()的浏览器中,你将看到下图这样的效果:


image.png

示例中还演示了 :not():has() 组合在一起使用的,但两者组合在一起所达到的意思却完全不一样。其中 :not(:has(selector)) 匹配不含有 selector 元素的父元素,而 :has(:not(selector)) 匹配含有的不是 selector 子元素的元素。两者主要区别在于,:has(:not(selector)) 写法必须要含有一个子元素,而 :not(:has()) 可以不含有元素也会被匹配。


有意思的是,Github 中有一个 Issue 在讨论 :has-child() 选择器,或许哪一天,我们在组件中就可以这样使用:

image.png

:empty 和 :blank


在现代 Web 的开发的过程中,总是无法避免数据吐出为空的情况,此时往往会给我们的 UI 带来额外的麻烦。比如说,在同一类的 UI 元件中编写了一定的样式规则,但数据为空,此时在页面上可能会出现这样的场景:

image.gif

CSS 的 :empty:blank 两个伪类选择器可以帮助我们避免这种现象。这两个选择器都很有用:


  • 给空元素添加样式
  • 创建空的状态


既然都是可以为空元素添加样式,那何为空元素,他们之间的差异又是什么?先来回答第一个问题,何为空元素?空元素是指元素没有任何子元素或子节点,比如:



<!-- 空元素 --><div class="error"></div><div class="error"><!-- 注释 --></div>
<!-- 非空元素 --><div class="error"> </div><!-- 中间有一个空格符 --><div class="error"></div><!-- 断行 --><div class="error">  <!-- 注释 --></div><!-- 注释断行排列 --><div class="error"><span></span></div>


:empty:blank 相比,:empty 只能选中没有子元素的元素。子元素只可以是元素节点或文本(包括空格)。注释或处理指令都不会产生影响。


image.png

Demo: https://codepen.io/airen/full/yLMLKyo


注意,在空元素上即使使用伪元素 ::before::after 创建内容,也能被:empty 识别。


:blank 要比 :empty 灵活地多,只要该元素中无任何子元素都能被识别。不过 W3C 规范对该伪类选择器的定义更趋向于作用到表单控件中,比如用户没有在 inputtextarea 中输入内容时,提交表单能被识别到。有点类似于表单验证的功能。


早在 2018 年 Zell Liew 在推特上就针对 :empty:blank 做过相关的讨论,并发表一了篇有关于这方面的博文《:empty and :blank》:


image.png


到目前为止,:empty 已得到主流浏览器支持,可以用于实际生产中,但 :blank 伪类选择器还是存在一定的争议的,有关于这方面的讨论可以点击这里查阅

https://github.com/w3c/csswg-drafts/issues/1967


:focus-visible 和 :focus-within


CSSer 对于 :focus 应该不会感到陌生,早期对于可聚焦元素可以使用 :focus 来给元素设置焦点状态下的 UI 风格,即焦点环样式:


image.gif


Chrome 86 开始,另外引入了 :focus-visible:focus-within 两种伪类选择器来帮助我们更好的控制 UI 焦点环的样式:

image.gif

虽然 :focus:focus-within:focus-visible 都可以用来设置焦点环的样式,但他们之间还是有一定的差异:


  • :focus :当用户使用鼠标点击焦点元素或使用键盘的 Tab 键(或快捷键)触发焦点元素焦点环的样式
  • :focus-visible :只有使用键盘的 Tab 键(或快捷键)触发焦点元素焦点环的样式。如果仅使用 :focus-visible 设置焦点环样式的话,那么用户使用鼠标点击焦点元素时不会触发焦点环样式
  • :focus-within:表示一个元素获得焦点,或该元素的后代元素获得焦点。这也意味着,它或它的后代获得焦点,都可以触发 :focus-within


来简单的看一个示例:



button:focus {   outline: 2px dotted #09f;   outline-offset: 2px; } 
button:focus-visible {   outline: 2px solid #f36;   outline-offset: 2px; }

你会发现,分别使用鼠标点击按钮和按Tab 让按钮获得焦点时焦点环样式效果不同:

image.png

不过需要注意的是,:focus:focus-visible 也会涉及到选择器权重的问题,就上面的示例来说,如果我们把 :focus 选择器对应的样式放置到 :focus-visible 之后:



这个时候,你会发现不管用户使用键盘 Tab 键还是鼠标让 <button> 获得焦点时,焦点样式都会采用 :focus 对应的样式:


image.gif


如果我们要让 :focus:focus-visible 可以有独自的样式,可以借助CSS选择器中的 :not() 来处理:



这个时候按 Tab 键盘和鼠标点击时焦点环样式就有相应的差异:


image.png

Demo: https://codepen.io/airen/full/YzGdoLq

或许你想到这样的场景了,如果你在做 A11Y 方面的优化,希望在移动端和PC端上对焦点元素设置不同的焦点环样式,那用上面这种方案来说就会非常的灵活。


对于 :focus-within 而言,它其实有点类似于 :has() 伪类选择器,可以在子元素得到焦点时,触发父元素:


image.png

上图描述了 :focus-within  和 :focus 之间的差异。更简单的说,它有点类似于 JavaScript 的事件冒泡,从可获得焦点元素开始一直冒泡到HTML的根元素 <html> ,都可以接收触发 :focus-within 事件。比如:



image.gif

Demo: https://codepen.io/airen/full/JjRxoLE


借助于 :focus-within 伪类选择器的特性,我们就可以让一些交互效果变得更为简单,比如下面这个示例,使用 :focus-within 就可以实现,不再需要任何 JavaScript 代码:


Demo: https://codepen.io/airen/full/KrPLmV

有了 :focus:focus-visible:focus-within 会让我们更好的管理焦点元素在焦点状态下焦点环的 UI 效果。


在 CSS 的世界中,除了这里提到的伪类选择器之外,还有很多其他的伪类选择器(或伪元素),比如 ::marker:in-range:out-of-range




如果你感兴趣的话,可以尝试着使用 :in-range:out-of-range 给一些 input (比如 typenumberrangeinput 元素,他们都有 minmax 属性的设置) 在用户输入范围值和非范围值时,提供不同的 UI 效果



CSS 颜色



CSS 颜色模块自从 Level 4 开始,除了新增了一些新的函数值,比如 hwb()lch()lab()color-mix()color-contrast()color() 之外,对原本的 rgb()hsl() 以及 #rrggbb 等颜色值定义语法规则也做出调整。


比如,原本我们熟悉的描述颜色值的方式:



#09f #90eafergb(123, 123, 123)rgba(123, 123, 123, .5)hsl(220, 50%, 50%)hsla(220, 50%, 50%, .5)

都有了新的语法规则,特别是对于 rgb()rgba()hsl()hsla() ,以往用逗号(,)作为分割符,现在可以直接使用空格做分割符,并且rgb()hsl() 函数在第三个值和第四个值之间加上 / 可以取替 rgba()hsla()


image.gif


另外,十六进制描述颜色,也可以在原本的语法规则中最后两位添加新的位值来描述透度明。比如 #rrggbbaa#rgba 。在Chrome 浏览器代码审查器中,已经可以看到这种语法规则的身影了:



也就是说,我们现在可以这样来描述颜色值:


#hex-with-alpha {  color: #0f08;  color: #00ff0088;}
#functional-notation {  color: hsl(0deg 0% 0%);  color: hsl(2rad 50% 50% / 80%);  color: rgb(0 0 0);  color: rgb(255 255 255 / .25);}
#lab-and-lch {  --ux-gray: lch(50% 0 0);  --rad-pink: lch(50% 200 230);  --rad-pink: lab(150% 160 0);  --pale-purple: lab(75% 50 -50);}

另外,我们现在描述颜色都是在sRBG 色值空间,而颜色色值空间是一个复杂的体系,除了 sRGB 之外还有其他的色值空间,比如说 LCH


image.gif


正如上图所示,LCH 颜色空间的颜色数量要比 sRGB 颜色空间的多,而且描述的颜色更为细腻。


image.gif


如上图所示,可以在color() 函数中指定颜色空间:



#color-function {  --rad-pink: color(display-p3 1 0 1);  --rad-pink: color(lab 50% 150 -50);  --rad-pink: color(srgb 100% 0% 50%);}

注意,使用了color() 函数指定颜色空间除了要考虑该函数支持度(浏览器的兼容性)还需要考虑硬件设备对颜色空间的支持度。


在 CSS 中可以借助媒体查询 @media 来做相应的判断,比如下面的示例,如果终端设备支持的话,就会采用指定颜色空间的色值:



@media (dynamic-range: high) {  .neon-pink {    --neon-glow: color(display-p3 1 0 1);  }    .neon-green {    --neon-grow: color(display-p3 0 1 0);  }}


更为强大的是 CSS 颜色模块 Level 5 版本,对颜色函数能力做了进一步的扩展。比如,可以在 rgb()hsl()hwb()lab()lch() 函数基础上添加 from 关键词,实现基于一个颜色的基础上,只对某个参数做调整:



rgb() = rgb([from <color>]? <percentage>{3} [ / <alpha-value> ]? ) | rgb([from <color>]? <number>{3} [ / <alpha-value> ]? ) hsl() = hsl([from <color>]? <hue> <percentage> <percentage> [ / <alpha-value> ]? ) hwb() = hwb([from <color>]? <hue> <percentage> <percentage> [ / <alpha-value> ]? ) lab() = lab([from <color>]? <percentage> <number> <number> [ / <alpha-value> ]? ) lch() = lch([from <color>]? <percentage> <number> <hue> [ / <alpha-value> ]? )

来看一个 hsl() 的示例:

image.gif


上图展示的是,基于 --theme-primary (原色,即 hsl(274, 61%, 50%))颜色只对l 参数做调整,从 50% 调整到 30% ,从而获得一个新的颜色,即 hsl(274, 61%, 30%) 。使用这样的方式,我们就可以很轻易的获取基于某个颜色参数改变得来的颜色面板:


image.gif


除此之外, 颜色模块 Level 5 版本还新增了一些新的函数用来描述颜色,比如 color-mix()color-contrast()color-adjust() 等:



.color-mix {  --pink: color-mix(red, white);
  --brand: #0af;  --text1: color-mix(var(--brand) 25%, black);  --text2: color-mix(var(--brand) 40%, black);}
.color-contrast {  color: color-contrast(    var(--bg)    vs    black, white  );}
--text-on-bg: color-contrast(  var(--bg-blue-1)  vs  var(--text-subdued),  var(--text-light),  var(--text-lightest));
.color-adjust {  --brand: #0af;  --darker: color-adjust(var(--brand) lightness -50%);  --lighter: color-adjust(var(--brand) lightness +50%);}



简单介绍一下这几个颜色函数。其中 color-mix() 函数接受两个 <color> 值,并在给定的颜色空间中以指定的数量返回它们混合的结果。如果没有特殊说明,否则在 lch() 颜色空间中进行混合。比如下图所展示的就是 在lch() 颜色空间(默认)中将 redyellow 混合,每个 lch 通道的红色值占65%,黄色值占 35%,即 color-mix(red yellow 65%)


image.gif


color-contrast() 函数很有意思,它可以帮助我们提高Web可访问性方面的能力。其主要作用是获取一个颜色值,并将其与其他颜色值的列表进行比较,从列表中选择对比度最高的一个。


image.png

比如 color-contrast(white vs red, white, green) ,分别会拿 redwhitegreenwhite 对比,其中 greenwhite 对比度最高,最终会取 green 颜色:


image.png


color-adjust() 函数可用来在给定颜色空间中通过指定的变换函数对该颜色进行调整,除非另有规定,否则调整是在 lch() 色彩空间中进行的。比如 color-adjust(#0af lightness +50%) 是颜色 #0aflch 颜色空间中高度增加 50%


也就是说,在不久的未来,这些颜色函数可以很好的帮助我们控制Web中的颜色值。因为这些颜色函数都已成为主流浏览器的实验性属性,特别是 Safari 浏览器,对这方面的支持度要高于 Chrome 和 Firefox 浏览器。



CSS 背景



CSS 的 background 属性是一个简写属性,这个对于大家来说再熟悉不过了。不过在这里着重想把 background-positionbackground-repeat 单独拿出来和大家聊聊。你可能会问,这两个属性又不是什么新属性了,有什么值得聊的呢?其实未必,这两个属性虽然老,但有几个小细节对于很多开发者来说还是会感到陌生的。咱们先从 backgroundd-position 开始。



background-position


background-position 主要是用来指定背景图片在容器中的位置。大家较为熟悉的是,背景图片左上角(顶点)相对于容器左上角计算,如下图所示:


image.png

不过,有的时候,希望背景图片能相对于容器右侧边缘或底部边缘计算,比如下图:


image.png


要让背景图片距离容器右侧边缘和底部边缘都是 50px 。针对这样的场景,你或许首先会想到使用容器大小和背景图片大小进行计算,得出距离顶部和左侧边缘的距离,然后将计算出来的值运用于 background-position 中。当然,熟悉 CSS 的同学或许会想到使用 calc() 函数:



:root {  --xPosition: 50px;  --yPosition: 50px;}
.container {  background-position: calc(100% - var(--xPosition)  calc(100% - var(--yPosition)))}

使用 calc() 动态计算要比通过容器和背景图片尺寸大小计算方便得多。事实上呢?background-position 提供了另一种特性,我们可以通过关键词 toprightbottomleft 来指定方向。比如我们熟悉的用法 :



background-position: 50px 50px;
// 相当于background-position: top 50px  left 50px;


那么,我们要实现上图的效果,就可以使用 rightbottom 关键词,让事情变得非常简单:



:root {  --xPosition: 50px;  --yPosition: 50px;}
.container {  background-position: right var(--xPosition)  bottom var(--yPosition);}


最终效果如下:


Demo: https://codepen.io/airen/full/mdWyryr


注,示例中左侧是使用 calc() 计算的效果,右侧使用的是关键词实现的效果。


background-position 使用还有一个小细节需要注意,即 background-position 采用百分比值。因为使用百分比值的 background-position 计算会相对于其他单位值复杂:


image.png


正如上图所示,背景图片原始尺寸是 100px x 100px ,容器的尺寸(使用该背景图片的元素)是 410px x 210px ,如果使用 background-position: 75% 50% 时,它的计算如下:


  • 就该示例而言,background-position: 75% 50% 就相当于 bacckground-position: 232.5px 55px


注意,如果我们把 background-sizebackground-position 取百分比值场景结合起来使用的时候,会让事情变得更为复杂,特别是background-size取值为covercontain 更会让你感到蛋疼。为什么会这样呢?这已经超出来这篇文章所要探讨的话题,如果你感兴趣的话,可以以此为课题,深入探讨一下。


background-repeat


background-repeat 属性上除了可以使用我们熟悉的 no-repeatrepeat (或者 repeat-xrepeat-y) 之外还可以使用 roundspace

image.png

我们都知道,使用 repeat 的时候,有可能会造成背景图片在平铺的时候被裁剪。如果希望背景图片在平铺时不被剪切,那么可以使用 round 来替代,它的最大特色就是背景图片在平铺的时候会根据容器的宽高对背景图片做相应的尺寸调整。而 space 会留出相应的空间,即在保证背景图片不被裁剪的情况之下,对多出来的空间以空白的方式在背景图片之间留出。


image.png


针对于这些值,我录制了一个简单的视频,从视频的展示效果中可以更好的看出它们之间的差异:


Demo: https://codepen.io/airen/full/mdWyOOj



CSS 蒙层和剪切



如果你对设计或设计软件较为熟悉的话,对于蒙层和剪切不会感到陌生。设计师在做一些设计稿的时候,时常也会用到蒙层和剪切的能力。随着 CSS 的发展,在 CSS 的世界中也有了这两个特性,它们在 W3C 的 《CSS Masking Module Level 1》规范中定义,主要的作用如下图所示:




可以灵活的控制内容的显式区域。


蒙层和剪切对应的 CSS 属性就是 maskclip-path ,其中 mask 是一个简写属性,它的使用规则和 background 非常的相似。我将通过简单的示例来向大家展示它们能帮我们做些什么。


先看 mask



Demo: https://codepen.io/airen/full/MdQrvR


视频中的 emoji 和文本残缺不全(看上去被啃了一样)。在没有mask 的能力之前,如果我们要实现这样的效果几乎是不太可能,现在有了之后,实现起来就非常的简单。我们只需要像下面这样的一张图片(用于mask-image 上的图片),即蒙层图:


image.png




h1 {  mask-image: url(mask.png);}

而且我们还可以借助 mask 的合成能力,让多个蒙层做合成运算:


image.png

运用蒙层相关的能力,可以快速帮助我们实现一些业务场景所需的效果:


image.png


除此之外还可以实现换肤的效果:

image.gif

Demo: https://codepen.io/airen/full/yWvzYy


再来看剪切。在 CSS 中使用 clip-path 的能力,除了可以帮助我们控制好所要展示的区域之外,还可以结合不同的路径和函数值实现不规则的图形展示。比如 Clippy 所展示的效果:


image.gif


比如在实际需求中,需要实现一些不规则,又是多状态下的 UI 效果,那么 clip-path 就会很方便:


image.png


而且使用clip-path 结合 transitionanimation 可以在交互上做一些更好的动效:




左侧是原状态下效果,右侧是鼠标悬浮状态下效果:


Demo: https://codepen.io/airen/full/zYqbgRK


特别是,当clip-path 支持 path() 函数时,可以做得事情更多了,比如我们想要绘制一个心形的 UI 效果:

image.gif

另外就是把 clip-pathfloat 和 CSS 的 Shapes 结合可以实现一些不规则的图文混排的布局效果:


image.png




CSS 混合模式



CSS 混合模式是个很有意思的特性,目前主要有 mix-blend-modebackground-blend-mode 两个属性,前者是用于多个元素的合成,后者是用于多个背景的合成。使用它们可以实现一些特殊的效果,比如类似 Photoshop 中的滤镜效果:


image.png


采用混合模式特性,我们可以轻易的实现产品图换色的效果:


Demo: https://codepen.io/kylewetton/full/OJLmJoV


这里简单地介绍一下,这个效果是怎么实现的。


首先我们有一张类似下图的产品图:


image.gifimage.png

通过 SVG 的能力,我们描绘图一个纯黑色的 SVG 形状,形状和上图的沙发是相稳合的:

image.png

描绘出来的 SVG 代码并不复杂:



使用 CSS 自定义属性,配合少许的 JavaScript代码即可完成产品图换色的效果:



:root {  --fill: #f36;}
svg {  fill: var(--fill);  mix-blend-mode: multiply;}


我们使用 mix-blend-modemultiply 效果。另外,JavaScript 动态改变 --fill 的值代码也简单:



const inputEle = document.querySelector('input') 
inputEle.addEventListener('change', (e)=>{   document.documentElement.style.setProperty('--fill', e.target.value); })


很简单吧。而且你可以发挥你的想象空间,可以实现很多具有创意的效果。



CSS 自定义属性



CSS 自定义属性可以说是 CSS 的标配了,正如 W3C 规范所描述的,CSS 自定义属性又被称为 CSS 变量


image.gif


不过在这里要说的不是 CSS 自定义如何使用?要和大家说的是 CSS 自定义属性中的无效变量。CSS 自定义属性中的无效变量是很有用的一个特性,它可以实现 1 (真)和 0 (假)的开关切换效果。换句话说,可以很好的实现不同状态的 UI 效果。


同样地,在 W3C 规范中对无效变量有详细描述,但不仔细的话,还是会忽略该特性。先来看规范是怎么描述无效变量的:


image.gif


它的意思是:


当一个自定义属性的值是 initial 时,var() 函数不能使用它进行替换。除非 指定了一个有效的回退值,否则会使声明在计算值时无效。


在 CSS 中注册自定义属性时是使用 -- 来注册的,可以给已注册的自定义属性赋值,包括空字符串,但其中有一个细节非常有意思:



:root {  --invalid:; // 注意冒号和分号之间无空格符,也无任何字符  --valid: ;  // 注意冒号和分号之间只有一个空格符}

其中,--invalid 自定义属性被称为无效变量,而 --valid 自定义属性是一个有效变量。使用上面这种方式来区分有效和无效变量对于开发者而言,可读性极差,而且有些文本编辑器可能会对代码按自己配置的规格格式化,有可能会造成 --invalid:; 变成 --invalid: ; (有空格)。为此,一般使用关键词 initial来显式声明一个自定义属性为无效变量:



:root {  --invalid: initial; // 无效变量  --valid: ;          // 有效变量}


如果不使用 var() 函数调用已注册的自定义属性的话,那么对于已注册的自定义属性而言,不会起任何作用。而 var() 函数有两个参数,第一个参数就是自定义属性,第二个参数是备用值。当第一个参数是个无效值时,会采用第二个参数。正因如此,对于已注册的无效自定义属性(即无效变量),比如 上面代码中的 --invalid 。那么 var() 没有提供备用值(第二个参数),则会使 CSS 样式规则(声明)在计算值时无效:



:root {  --invalid: initial; // 无效变量  --valid: ;          // 有效变量}
foo {  background-color: var(--invalid); // 未提供备用值,则background-color 计算值无效  color: var(--invalid, red)        // 提供了备用值,--invalid是无效变量,则会采用备用值 red}


先来看一个简单的示例:



<div class="element">  <i>Element</i></div>
<i>Element</i>
<style>  .element {    --color: red;  // 只作用于 .element 元素及其后代元素  }
  i {    --foo: initial;                         // 无效变量    --color: var(--foo);                    // 无效变量    background-color: var(--color, orange); // --color是无效变量,采用备用值 orange  }</style>


运行上面的代码,你将看到的效果如下图所示:


image.png

Demo: https://codepen.io/airen/full/GRWJGvx


再来看另一个示例:



<section>Element</section>
<style>  :root {    --initial: initial; // 无效变量    --valid: ;          // 有效变量  }  section {    background-color: var(--initial, red); // 无效变量,会采用备用值 red    color: var(--valid, red);               //  有效变量,备用值被忽略  }</style>

因此,你看到的效果如下:


image.png

Demo: https://codepen.io/airen/full/poeJKpM


这个示例中 color 属性相当于设置了 color: ; 值,客户端在计算该值时将被忽略,因此会继承其祖先元素的 color 值,该例继承了 body 元素的 colorblack


你可能对 CSS 自定义属性中的无效变量有了一定的认知。我们继续往下。


为了在使用 CSS 自定义属性中的无效变量让开发者更易于理解,@Lea Verou在《The --var: ; hack to toggle multiple values with one custom property》引入了类似于Switch的概念,即:



:root {  --ON: initial; // 无效变量,相当于开启 var()的备用值  --OFF:;        // 有效变量,相当于关闭 var()的备用值}

这样一来,我们在做 UI 不同状态切换时,就只需要对 --ON--OFF 的切换。比如 @Lea Verou 在文章中提供的示例:



:root {  --ON: initial;  --OFF: ;}
button {  --is-raised: var(--OFF);    border: 1px solid var(--is-raised, rgb(0 0 0 / 0.1));  background: var(      --is-raised,      linear-gradient(hsl(0 0% 100% / 0.3), transparent)    )    hsl(200 100% 50%);  box-shadow: var(    --is-raised,    0 1px hsl(0 0% 100% / 0.8) inset,    0 0.1em 0.1em -0.1em rgb(0 0 0 / 0.2)  );  text-shadow: var(--is-raised, 0 -1px 1px rgb(0 0 0 / 0.3));}
button:hover {  --is-raised: var(--ON);}
button:active {  box-shadow: var(--is-raised, 0 1px 0.2em black inset);}

最终效果如下:


Demo: https://codepen.io/airen/full/XWNYRga


上面这个示例算是简单的了。不过我们可以利用该特性实现更复杂的 UI 效果。比如下面这个多种换肤效果:







label {  --box-shadow: var(--ON);  --box-shadow-active: var(--OFF);  box-shadow: 0 0 0 3px var(--box-shadow, rgba(0, 0, 0, 0.05))    var(--box-shadow-active, #2196f3);  cursor: pointer;}
label.dark {  background-color: var(--dark-bgcolor);}
label.light {  background-color: var(--light-bgcolor);}
label.blue {  background-color: var(--blue-bgcolor);}
#dark:checked ~ div .dark,#light:checked ~ div .light,#blue:checked ~ div .blue {  --box-shadow: var(--OFF);  --box-shadow-active: var(--ON);}
.nav {  color: var(--light, var(--light-color)) var(--dark, var(--dark-color))    var(--blue, var(--blue-color));  background-color: var(--light, var(--light-bgcolor))    var(--dark, var(--dark-bgcolor)) var(--blue, var(--blue-bgcolor));}
a.active,a:hover {  background-color: var(--light, var(--light-active-bgcolor))    var(--dark, var(--dark-active-bgcolor))    var(--blue, var(--blue-active-bgcolor));}
/* 设置切换开关 */:root {  --ON: initial;  --OFF: ;
  /* Dark */  --dark-color: rgba(156, 163, 175, 1);  --dark-bgcolor: rgba(17, 24, 39, 1);  --dark-active-bgcolor: rgba(55, 65, 81, 1);
  /* Light */  --light-color: rgba(55, 65, 81, 1);  --light-bgcolor: rgba(243, 244, 246, 1);  --light-active-bgcolor: rgba(209, 213, 219, 1);
  /* Blue */  --blue-color: rgba(165, 180, 252, 1);  --blue-bgcolor: rgba(49, 46, 129, 1);  --blue-active-bgcolor: rgba(67, 56, 202, 1);}
#dark:checked ~ .nav {  --light: var(--OFF);  --dark: var(--ON);  --blue: var(--OFF);}
/* 默认为Light */#light:checked ~ .nav {  --light: var(--ON);  --dark: var(--OFF);  --blue: var(--OFF);}
#blue:checked ~ .nav {  --light: var(--OFF);  --dark: var(--OFF);  --blue: var(--ON);}

效果如下:


Demo: https://codepen.io/airen/full/OJbEzzw


示例中没有使用任何 JavaScript 代码。是不是很有意思。要是感兴趣的话,你也可以尝试着撸撸这样的效果。


超长文预警,你可能不知道的CSS “新”特性未完待续...下次见。


image.png

相关文章
|
4月前
|
前端开发 JavaScript 开发者
探索Web设计新纪元:CSS3的革新特性如何重塑我们的网页视觉体验?
【8月更文挑战第26天】随着Web技术的发展,CSS3为前端开发带来了众多激动人心的新特性,极大提升了网页设计的视觉效果与创意空间。本文通过对比CSS3与CSS2,详细介绍了CSS3在选择器增强、圆角阴影处理、渐变背景应用、转换动画实现、文字效果优化、媒体查询支持及多列布局方面的显著改进,展示了CSS3如何助力开发者打造更具吸引力和互动性的网页体验。
56 1
|
3月前
|
前端开发
【前端web入门第四天】02 CSS三大特性+背景图
本文详细介绍了CSS的三大特性:继承性、层叠性和优先级,并深入讲解了背景图的相关属性,包括背景属性、背景图的平铺方式、位置设定、缩放、固定以及复合属性。其中,继承性指子元素自动继承父元素的文字控制属性;层叠性指相同属性后定义覆盖前定义,不同属性可叠加;优先级涉及选择器权重,包括行内样式、ID选择器等。背景图部分则通过具体示例展示了如何设置背景图像的位置、大小及固定方式等。
259 91
|
17天前
|
前端开发
css特性
css特性 1.继承性:子级继承父级文字控制属性 ps:子级拥有自己的样式则不会继承父级。 2.层叠性:相同的属性后面覆盖前面,不同的属性叠加 3.优先级:选择器优先级高的样式生效 公式:通配符<标签<类<id<行内样式<!important(选中范围越大,优先级越低) 叠加计算规则:存在复合选择器时,从左向右依次比较个数,同一级个数多的优先级高,如果个数相同,则向后比较;!important权重最高;继承权重最低。
|
7月前
|
机器学习/深度学习 移动开发 前端开发
CSS3 新特性
CSS3 新特性
73 0
|
4月前
|
前端开发 JavaScript API
Vue 3 新特性:在 Composition API 中使用 CSS Modules
Vue 3 新特性:在 Composition API 中使用 CSS Modules
|
7月前
|
前端开发 JavaScript 开发者
CSS3作为CSS的最新版本,引入了许多强大的新特性和改进
【5月更文挑战第26天】CSS3作为CSS的最新版本,引入了许多强大的新特性和改进
66 2
|
6月前
|
前端开发
番外篇-CSS3新增特性
番外篇-CSS3新增特性
35 0
|
7月前
|
前端开发 UED
【专栏:CSS 进阶篇】CSS3 新特性:过渡、动画与变形
【4月更文挑战第30天】CSS3的过渡、动画和变形三大特性为网页设计注入活力,创造生动丰富的用户体验。过渡提供平滑效果,常用于按钮点击等;动画实现复杂动态效果,适用于滚动字幕等;变形允许元素几何变换,如旋转和缩放。实际应用包括动态导航菜单、图片轮播和加载动画。然而,需注意浏览器兼容性、性能优化和设计平衡。掌握这些特性,将为网页设计带来更多创新可能。
179 1
|
7月前
|
前端开发 JavaScript 开发者
【Web 前端】css3的新特性有哪些?
【4月更文挑战第22天】【Web 前端】css3的新特性有哪些?
|
7月前
|
前端开发 容器
CSS面试考点:隐藏元素、BFC、垂直居中、CSS3新特性
【4月更文挑战第2天】 CSS面试考点:隐藏元素、BFC、垂直居中、CSS3新特性
53 10