年终盘点:2023年 CSS 领域新的一些变化

2023 年, CSS 领域有了新的变化,整体趋势是更加的易用,并贴合流行的框架做了扩展。Chrome 的开发者 blog 和 MDN 里更新了一些比较有意思的 CSS 2023 动向的内容,文章比较长,内容有点乱,这里总结分享一下给大家。

摘要

本文是 "2023 年度技术盘点" 系列文章,主要介绍前端 CSS 领域在 2023 年的重要进展。本文从 CSS 基础功能、排版、颜色、自适应设计、交互和组件等方面进行盘点。

可以查看文章目录以选择查看自己感兴趣的更新

基础功能

css 新增了三角函数相关函数:sin()cos()tan()asin()acos()atan()atan2()

三角函数的使用要和 calc 结合才能出效果。

三角函数

  • cos():返回角度的余弦值,该值介于 -11 之间。
  • sin():返回角度的正弦值,该值介于 -11 之间。
  • tan():返回角度的正切值,取值介于 −∞+∞ 之间。

以正弦为例,你可以这么写:width: calc(sin(var(--angle)) * 200px); 来计算宽度值。

我们实现一个计算角度垂线长度的问题来说明 sin 的用法。下面的例子里,绿色虚线的长度就是 斜边(200px)* sin(角度) 计算出来的:

实现 sin 计算三角形边长

由于 chrome 的 css 不支持 rem() 和 mod() 等取模函数,所以用了两个 css 变量表示 180deg 前后的角度
码上掘金怎么不支持直接预览了,我放个截图吧...

同理,cos 和 tan 可以如下使用:

css 复制代码
transform: scaleX(cos(var(--angle)))
transform: scaleX(tan(var(--angle)));

官方demo:codepen.io/web-dot-dev...

反三角函数

反三角函数是三角函数的逆运算,包含:asin()acos()atan()atan2()。前三个接受一个数值,返回对应的角度值。

第四个 atan2(),接受两个参数 AB。返回 X 轴正轴与 (B, A) 点之间的角度。

atan2对象限敏感,根据两个参数判断它是属于哪个象限并给出对应的角度值,值域范围[-pi, pi];atan对象限不敏感,值域范围为[-pi/2, pi/2]

下面还是举个例子来说明 atan()atan2() 的区别。

反三角函数示例

下一个新功能是 n-* 的选择器。大家之前也都用过诸如 :nth-of-type 这样的伪类来实现元素选择,现在官方增强了这个功能,使用 S 语法来更好地控制 :nth-child() 选择: :nth-child():nth-last-child()

n-* 选择器 与 S 语法

使用例子:

  • :nth-child(2):选择第二个子级。
  • :nth-child(2n):选择所有偶数子项(例如 2nd、4th、6th、8th 等)。**
  • :nth-child(2n+1):选择所有奇数子项(第 1、3、5、7,依此类推)。**
  • :nth-child(5n+1):选择第 1 个 (=(5×0)+1) 、第 6 个 (=(5×1)+1) 、第 11 个 (=(5×2)+1) ...子级。
  • :nth-child(5n+2):选择第 2 个 (=(5×0)+2) 、第 7 个 (=(5×1)+2) 、第 12 个 (=(5×2)+2) ... 子项。

里边的选择项可以是等差数列,等比数列,或者是自定义的任意数列,甚至可以组合使用:

  • :nth-child(n+3):选择从第 3 个开始的每个子级。
  • :nth-child(-n+5):选择不超过第 5 个子级(第 1 个、第 2 个、第 3 个、第 4 个、第 5 个。
  • :nth-child(n+3):nth-child(-n+5):选择第 3 个到第 5 个(第 3 个、第 4 个、第 5 个)的每个子级。

同理,:nth-last-child() ,也可以进行类似的选择,但不是从头开始计数,而是从头开始计数。

下面给一个创建斑马纹示例:

css 复制代码
tr:nth-child(even) {
  background-color: #E9E9E9;
}

// 另一种写法
tr:nth-of-type(even){ 
  background: #E9E9E9;
}

"nth-of-type"和"nth-child"的区别:p:nth-of-type(7)选择的p元素所在的父元素下的第7个P元素即:第7个p;p:nth-child(6)选择的p元素所在的父元素下的第6个子元素,且该元素是P元素。

nth-child 还可以结合 S 语法使用:

css 复制代码
:nth-child(An+B [of S]?)
:nth-last-child(An+B [of S]?)

指定 of S 后,An+B 逻辑仅应用于与给定选择器列表 S 匹配的元素。这实际上意味着,您可以在 An+B 执行操作之前预先过滤子项:

css 复制代码
:nth-child(2 of .highlight) {
  /* = Select the 2nd child element that has the class .highlight applied to it */
  outline: 0.3rem dashed hotpink;
  outline-offset: 0.7rem;
}

上面的例子,选择了父元素盒子中,类名叫 highlight 中的第二个子元素(注意不是物理顺序的第二个)来赋值样式。

再举个例子,上面的斑马纹还可以增强:

css 复制代码
// 筛选没有 hidden 属性的列,然后再计算斑马纹
tr:nth-child(even of :not([hidden])) {
  background-color: lightgrey;
}

@scope

Chrome 118+ 增加了对 @scope 的支持,这是一项 @ 规则,可将选择器的范围限定为文档的特定子树,从而控制 css 作用域,这对于将来需要做 css 隔离的业务场景有大用处:

css 复制代码
@scope (html) {
  .warning {
    display: none;
  }
}

/* Only match elements in the card component, but don't target those in nested components */
@scope (.card) to ([data-component]) {
  img {
    border: 0.25em solid #6300ff;
    border-radius: 0.5em;
  }
}

上面的第二个例子,只作用于 card 类,而不作用于其包含data-component属性的子元素,即 card类 和 [data-component] 之间的元素。

官网demo:codepen.io/web-dot-dev...

@layer 嵌套

其实这个特性22年就有了

官方 css 显然是吸取了各个前端框架和预处理器的经验,并进行了升级,现在 css 也要支持嵌套了。

之前这样的写法:

css 复制代码
ul li {}
ul li .child {
  color: red;
}

现在可以这样写了:

css 复制代码
@layer test {
    ul {
      li {
        & .child {
          color: red;
        }
      }
    }
    
    .card {
      &--header {
        /* is not equal to ".card--header" */
      }
    }
}

当然直接嵌套,不使用 @layer 也是可以的

使用具名 @layer 包裹的 css 片段可以支持嵌套优先级配置。

css 复制代码
@layer base, special;

@layer special {
  .item {
    color: red;
  }
}

@layer base {
  .item {
    color: green;
    border: 5px solid green;
    font-size: 1.5em;
    padding: 0.5em;
  }
}

预先定义各个具名layer的优先级,从左到右依次降低,上面的 item 类元素,最后会被加上红色的颜色,而不是绿色。

子网格布局

我们都知道有 grid 布局,现在迎来了增强,加入了 subgrid 布局。

我们这么定义一个网格布局:

css 复制代码
.container {
  display: grid;
  gap: 1rem;
  grid-template-columns: [column-1] 20ch [column-2] 1fr;
}

网格有两列,一列给20ch,一列占领剩余空间:

我们现在定义子网格:

css 复制代码
.container > div {
  grid-column: span 2;

  display: grid;
  grid-template-columns: subgrid; /* 20ch 1fr */
}

这样一来,子 div 虽然还是占了一行,但是他继承了父元素的网格布局,即父网格的列已经有效向下传递到子网格,在各个子 div 内部再实现布局时就可以直接使用。最新的浏览器中也标明了这种布局:

demo 如下:子网格布局 demo

看看你的浏览器支不支持,可以这样:

css 复制代码
@supports (grid-template-columns: subgrid) {
  /* safe to enhance to */
}

官方demo:codepen.io/web-dot-dev...

排版

2023 年,css 也开始针对排版更新了,可能是借鉴了 Tailwind 的经验...

首字母

Chrome 110 中推出了 initial-letter 属性,可以将首字母抬起或降低。

示例:<p>Lorem ipsum dolor ...</p>

css 复制代码
p {
  &::first-letter {
    initial-letter: 3 2;
    color: green;
  }
}

核心点是 first-letter 伪元素,他在 DOM 结构树中不会体现出来(所以有些迷,调试有困难,幽灵元素?),其中可选项 initial-letter 可以设置其偏移量,上面的例子表示首字母高度占用 3 行,下沉 2 行:

首字母属性同样支持中文

文字折行优化

新增两种写法 text-wrap: balancetext-wrap: pretty,官方解释请移步:www.w3.org/TR/css-text...

前者是浏览器为文本找出最佳的均衡换行解决方案:

emm,怎么感觉还不如原来。。。推荐使用 pretty,至少能左右对齐。

颜色

颜色方面是已有颜色库的扩充,增加了更多的颜色和可选项。官方的说法是,原来的 CSS 不支持高清画质,而大多数人放在口袋里、放在身上或安装在墙上的显示屏具有广色域和高清色彩。显示屏的颜色处理速度比 CSS 快,现在 CSS 的更新可以与之一较高低了。

高清 CSS

CSS Color Level 4 提出了高清 CSS 方案,从 Chrome 111 开始支持色彩空间和色域,他是在已有色域的基础上的颜色扩充,以适应现在更加高清显示的显示设备。

官方推出 7 中色域:

颜色空间是色域的排列,它建立了形状以及获取颜色的方法。颜色级别 4 引入了 12中颜色空间用于从色域中查找颜色:

之前只有四种颜色空间:

知道了概念,我们来看几种新的颜色定义。

HWB

HWB(色相、白度、黑度)是面向人类描述颜色的 sRGB 色域颜色空间:

css 复制代码
--modern: hwb(200deg 25% 25%);
color() 函数

color() 是一个标准化空间,用于访问使用 RGB 通道的颜色空间。而且可扩容至基于 RGB 的任何广色域色彩空间。使用举例:

css 复制代码
--display-p3: color(display-p3 1 1 1);
--srgb: color(srgb 1 1 1);

甚至之前的 rgb 也可以这么统一来写:

css 复制代码
--rgb: color(rgb 25 25 25)
其他

此外还有 lch / lab / oklch / OKLAB / dispaly-p3 等颜色空间,由于东西太多了,就不一样列出,上面有链接,可以自行点击查看。这类CIE 空间颜色都能够更好的表示整个人类可见的色谱。使用新的颜色空间还会影响颜色渐变和插值等功能。

color-mix()

他是用于颜色混淆的函数,灵感可能也是来自css预处理器。

官方demo:codepen.io/web-dot-dev...

为了便于理解,我们这里实现一个简单的切换颜色的例子:

主题颜色切换

上面的例子中,各个主题的颜色,通过混淆得来。我们拿一个来说明使用方式。

color-mix(in oklab, #0af 25%, black):在 oklab 色域空间内,将天蓝色的25%与黑色混合。此时文字的颜色如下:

看着是黑色的,其实有点蓝韵。如果将 25% 调整到 75% 就会明显了:

from 关键字

可以看做是 color-mix 的扩展增强:

color: rgb(from green r g b)

等价于 color: rgb(0 128 0),意思是从 green 开始,拿到 rgb 的值,并在之后的参数中使用,他计算的是转换后的相对颜色。于是会有如下写法:

css 复制代码
// r g b 是三个变量,可以换顺序使用
rgb(from green g g g)    /* rgb(128 128 128) */
rgb(from green b r g)    /* rgb(0 0 128) */
rgb(from green 0 0 g)    /* rgb(0 0 128) */

当然聪明的你也能猜到,透明度也能当做变量:

css 复制代码
rgb(from rgba(0, 128, 0, .5) r g b / alpha)      /* alpha=50% */

上面就相当于将颜色打成各个通道分量来实现混淆。我们来看一个:background: hsl(from blue calc(h + 180) s l).

这个是 hsl 的转变,其中第一个通道分量加了 180 后返回,其实是求蓝色的互补色:

响应式设计

新的一年,响应式布局又有了新的写法。

查询容器大小

相比于传统的媒体查询,容器查询更加细粒度,他可以应用在局部容器或者指定容器内:

css 复制代码
container: card / inline-size;

这样就创建了一个叫做 card 的适应内嵌大小的响应式容器,可以这样使用查询:

css 复制代码
/* 只有一个容器时,名字 card 可以省略 */
@container card (max-width: 400px) {
...
}

我们来看一个例子:响应式容器

容器样式

此外,在 Chrome 111 中,样式查询已部分实现:可以使用 @container style() 查询父元素上的自定义属性的值。我们来看个例子(兼容性不好):

css 复制代码
@container style(--theme: pink) {
  .demo-btn {
    background: linear-gradient(45deg, pink, violet);
    border: 4px double #f452dc;
  }
}

上面的例子,根据变量 theme 的值动态添加样式,完整 demo 如下:

codepen.io/web-dot-dev...

:has()

字面意思就是说选中拥有某个属性的元素:

css 复制代码
.card:has(.card__media) {
  --color: #6300ff;
}

上面的代码,是选中拥有一个类叫 card__media 的 card 元素。他可以让你在众多相同的类名叫 card 的元素中选中你想要的。

其不但可以在 DOM 树中向上选择父级元素,也可以进行横向选择。

比如有个列表:

html 复制代码
<ul class="dock">
  <li></li>
  ...
  <li></li>
</ul>

每一个 li 里是一个列表图片,我们想实现鼠标滑动变大,这么写:

css 复制代码
.dock li:hover {
  width: 3em;
}
.dock li:hover img {
  translate: 0% -15%;
}

如果想实现 mac 系统那种,左右图片也会变大的动画,可以这么写:

css 复制代码
.dock li:hover + li,
.dock li:has(+ li:hover) {
  width: 2.5em;
}

不但选择其下一个兄弟节点,还使用 has 选择其上一个兄弟节点:

媒体查询有了新功能

原来已有的媒体查询功能得到了史诗级加强。

适应设备刷新率

新增如下写法:

css 复制代码
@media (update: none) {
  ...
}

对于大多数新版设备,其更新一般是 fast,但是针对电子阅读器以及低能耗付款系统等设备,其刷新率可能较慢,可以使用 slow 适配,其余的像打印机一类的,可以设置为 none。demo

根据不同的设备刷新率采用不同的 CSS 表现形式,意味着您可以节省电池电量或减少错误的视图更新。由于体验者不可能凑齐刷新率不同的设备不太容易,这里使用js来模拟说明:

官方demo:小鸭子摇头

demo 是使用js模拟刷新率不同的设备,并不是对 update 的使用说明

媒体查询检查js脚本

脚本媒体查询可用于检查 JavaScript 是否可用。这对于渐进式增强非常有用。在此媒体查询推出之前,一个用于检测 JavaScript 是否可用的策略是在 HTML 中放置一个 nojs 类,然后使用 JavaScript 将其移除。现在可以使用 css 来检测了:

css 复制代码
@media (scripting: enabled) {
  .steam {
    opacity: 1;
  }
}
用于减低透明度的媒体查询

非不透明界面可能会引起头痛,或者让各类视力缺陷人群造成视觉障碍。因此,Windows、macOS 和 iOS 设有系统偏好设置,可以降低或移除界面的透明度。这个针对 prefers-reduced-transparency 的媒体查询非常适合使用其他偏好设置媒体查询:

css 复制代码
svg {
    --transparency: 50%;
    
    @media (prefers-reduced-transparency: reduce) {
      --transparency: 95%;
    }
}

交互

视图转换 API

官方说明是增强css过渡,更好的减少卡顿和图形交叠。

使用:const viewTransition = document.startViewTransition(updateCallback)

使用方式很像 requestAnimationFrame,这个是在当前事件循环结束前执行回调,而我们今天的这个 API,会捕获网页的当前状态。这包括截取屏幕截图。完成后,系统会调用传递给 .startViewTransition() 的回调 updateCallback。这正是 DOM 发生变化的地方。然后,API 会捕获网页的新状态。

官方说明如下:startViewTransition, 有兴趣的可以自行查看。

linear()

该函数用于模拟线性插值来近似计算加/减速类型的动画。使用方式如下:

css 复制代码
:root {
  --custom-easing: linear(
    0,
    0.06,
    0.25 18%,
    1 36%,
    0.81,
    0.75,
    0.81,
    1,
    0.94,
    1,
    1
  );
}

可以查看在线 demo 来创建一个使用案例。

Scroll End API

官方对滚动事件的增强,终于有了自己的滚动到底的检测了,这对于长列表渲染很有帮助:

js 复制代码
document.onscrollend = event => {
  Toast('scroll end')
}

滚动条驱动动画

这个是比较惊艳的更新了,大多数门户网站都有滚动懒加载或者淡入淡出的动画效果,这次改动补全了之前原生 css 没有滚动检测的空白。

官方演示 demo:Scroll-driven Animations (scroll-driven-animations.style)

其主要是通过 animation-timeline 属性设置驱动,可选的驱动方式有 scollview。我们看下面的例子:滚动动画

animation: text steps(var(--chars)) forwards; 来检测滚动的偏移量,并传给变量 --charsm,然后可以通过偏移量设置文字背景色的宽度:

另一种驱动方式 view 这么定义:animation-timeline: view();,可通过 animation-range 设置变化范围,用于监测当前元素是否在可视区域,官方 demo:CSS Wrapped 2023: Scroll-Driven Animations: View Timeline #CSSWrapped2023 (codepen.io)

给 display: none 添加动画

chrome 新增功能:transition-behavior,接受两个值:normalallow-discrete,其中 allow-discrete 模式可实现离散转换。使用方式:

css 复制代码
.card {
  transition: opacity 0.25s, display 0.25s;
  transition-behavior: allow-discrete;
}

demo: 删除卡片

@starting-style

@starting-style CSS 规则以新的 Web 功能为基础,用于在 display: none 之间添加动画效果,但也可以用于其他的 transition。可以看这个 demo

弹窗默认是隐藏在屏幕之下的:

css 复制代码
dialog {
  transition: translate 0.7s ease-out, overlay 0.7s ease-out, display 0.7s ease-out allow-discrete;
  translate: 0 100vh;
}

设置开关:

css 复制代码
dialog[open] {
  translate: 0 0;
}

但是这个是突然出现的,明明设置了 transition: translate,过渡却没出来,应该是 h5 的 dialog 的 open 看做 display 的修改了吧。我们设置过渡:

css 复制代码
@starting-style {
  dialog[open] {
    translate: 0 100vh;
  }
}

此时就有动画了。

overlay 、:popover-open 与 ::backdrop

css 原生也开始支持弹出层了,除了上面 h5 的 dialog,这里还有 popover 的 css 属性。

官方 demo:CSSWrapped2023 Overlay (codepen.io)

组件

popover

该组件有如下特点:

  • 晋升到顶层。弹出式窗口会显示在网页其余部分之上的单独图层上,因此无需调整 z-index。
  • 轻关闭功能。点击弹出窗口区域以外的位置将关闭弹出窗口并返回焦点。
  • 默认焦点管理。打开弹出式窗口会使下一个标签页停止在弹出式窗口内显示。
  • 无障碍键盘绑定。 点按 esc 键或双重切换按钮会关闭弹出式窗口并返回焦点。
  • 可访问的组件绑定。从语义上将弹出式窗口元素连接到弹出式窗口触发器。

只需要给一个普通的 div 加上 popover 属性,然后给按钮呢加上 popovertarget 对应这个 div 的 id 即可。详情可以查看 demo:

hr 标签焕发新春

之前对 hr 标签的理解就是很丑的横线,比较鸡肋,UI库都会去自己实现分割线,这与 H5 的初衷相违背,制定标准的人都希望自己的组件能被各个主流 UI 库使用来进行二次开发。

但现在路还很长,select 组件还是功能欠缺,各个UI库不得不自己实现

现在 hr 可以和 select 结合使用了:

html 复制代码
<select name="majors" id="major-select">
  <option value="">Select a favorite feature</option>
  <hr>
  <optgroup label="Foundations">
    <option value="trig">Trigonometric functions</option>
    <option value="nth">Complex nth-* selection</option>
    <option value="nesting">Nesting</option>
    <option value="subgrid">Subgrid</option>
    <option value="mq-ranges">Media query range syntax</option>
  </optgroup>
  ...

感觉还不错(但是原生 select 连搜索都不支持,可能企业级落地还有些困难):

:user-valid:user-invalid

这两个伪类是用于用户与输入内容校验的,在进行明显互动后校验表单是否无效。

下面是个例子(校验原生 h5 表单的规则),可用于说明与 :valid 的区别:表单校验

手风琴组件

这个组件用的还是 h5 的 details 标签,不是个新东西了,但是其增加了name 属性的支持。使用此属性时,具有相同 name 值的多个 <details> 元素会形成语义组。

我们来看 demo


总结

前端 CSS 的发展趋势是紧贴用户主流使用习惯,并借鉴各个 UI 库和框架的使用方式改造,与 H5 标签结合,力图完善自己的组件体系,跟上时代的变化潮流。整体方向如下:

  • 响应式和移动优先设计
  • CSS 变量与流程控制操作越来越多,打通与 js 的屏障
  • 动画与过渡优化
  • 颜色空间扩充
  • 易用性增强,函数扩充
  • 性能优化
相关推荐
毋若成8 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
榴莲千丞11 小时前
第8章利用CSS制作导航菜单
前端·css
guokanglun11 小时前
CSS样式实现3D效果
前端·css·3d
NiNg_1_23415 小时前
前端CSS3 渐变详解
前端·css·html
Au_ust17 小时前
css:权重计算
前端·css
前端Hardy18 小时前
HTML&CSS 打造的酷炫菜单选项卡
前端·javascript·css·html·css3
YUJIANYUE18 小时前
原生html+js输入框下拉多选带关闭模块完整案例
javascript·css·html
码手Lion18 小时前
CSS多列布局:打破传统布局的束缚
前端·css
Wx-bishekaifayuan18 小时前
springboot市社保局社保信息管理与分析系统-计算机设计毕业源码03479
java·css·spring boot·spring·spring cloud·servlet·guava
起风的秋天@18 小时前
CSS Modules在框架中的使用
前端·css