写CSS样式老要算来算去好麻烦,范围语法到底怎么帮咱省点脑细胞?

2,638字
11–17 分钟
in

每次写CSS,遇到要根据数值大小来切换样式,总得写一堆媒体查询或者JavaScript,搞得代码又长又乱。现在好了,Chrome 142开始支持的范围语法,搭配容器样式查询和if()函数,直接让样式自己学会“看数下菜碟”。这东西到底是啥神仙操作?咱这就从最基础的咋用开始,一步步把它整明白。

目录

范围语法是CSS里比较数值大小的新写法,能让样式根据某个值是否在特定区间内来动态变化。以前只能在媒体查询里比划屏幕尺寸,现在能直接拿自定义属性(像--lightness: 10%)或者HTML属性里的数字做比较。搭配@container style()if()函数,就可以让背景色、文字颜色这些样式根据数值变化自动切换,不用再写一堆判断逻辑了。

容器查样式咋写

先给元素安个“小本本”

要玩范围语法,得先让CSS有个能记住的数值。比如想做个根据亮度换文字颜色的卡片,先得给容器塞个自定义属性。

.card {
  /* 这个值可以在0%到100%之间随便调 */
  --亮度: 10%;
  /* 用它来设置背景色,这样背景色就跟着--亮度走了 */
  background: hsl(270, 100%, var(--亮度));
}

这步做完,背景色就有了。但文字颜色呢?得让里面的内容知道背景现在是亮还是暗。这时候就需要容器样式查询登场。

.card * {
  /* 注意:这里查的是.card上的--亮度,不是查自己的 */
  @container style(--亮度 < 50%) {
    color: white;
  }

  @container style(--亮度 >= 50%) {
    color: black;
  }
}

现在只要改--亮度的百分比,文字颜色就自动跟着变。比如设成10%,背景深紫,文字变白;设成80%,背景变浅,文字变黑。这里有个坑:@container规则如果写在.card里,它会去找.card的父容器,而不是.card自己。所以得把查询放在子元素的选择器里,才能正确读到.card上的--亮度

给容器取个名字更稳妥

如果页面里好几个容器都有--亮度属性,怕查串了,可以给容器命名。

.card {
  --亮度: 10%;
  background: hsl(270, 100%, var(--亮度));
  /* 给它起个名字 */
  container-name: 亮度盒子;
}

.card * {
  /* 查询的时候带上名字,只认这个盒子 */
  @container 亮度盒子 style(--亮度 < 50%) {
    color: white;
  }

  @container 亮度盒子 style(--亮度 >= 50%) {
    color: black;
  }
}

这样哪怕页面里还有别的容器也定义了--亮度,查询也只找叫“亮度盒子”的那个,不会搞混。要是想让查询往上找,就把container-name加在父级上就行。

if()函数咋用更顺手

跟容器查询有啥不一样

if()函数也能干类似的事,但它不用跑到子元素里写查询,直接在元素自身就能用。

.card {
  --亮度: 10%;
  background: hsl(270, 100%, var(--亮度));
  /* 直接在.card身上判断,不用单独写子元素规则 */
  color: if(
    style(--亮度 < 50%): white;
    style(--亮度 >= 50%): black
  );
}

这一下代码就短了不少。不过有个细节:if()能直接读当前元素的自定义属性,而容器查询得通过子元素去读父级。所以如果只想影响当前元素,用if()更省事。但要是想控制一堆子元素,容器查询能一次性搞定,不用给每个子元素都写一遍if()。

实战:做个消息小红点

做网页经常遇到消息数超过99就显示“99+”的需求。以前得用JS判断,现在CSS自己就能搞定。先给元素加个属性:

<div data-消息数="8"></div>

然后用::after伪元素来显示数字,用容器查询判断数字范围:

[data-消息数]::after {
  height: 1.25rem;
  border-radius: 1.25rem;

  /* 消息数是1-2位数的时候,直接显示数字,形状是圆的 */
  @container style(attr(data-消息数 type(<number>)) <= 99) {
    content: attr(data-消息数);
    aspect-ratio: 1 / 1;
  }

  /* 消息数超过99,就显示99+,并加点内边距 */
  @container style(attr(data-消息数 type(<number>)) > 99) {
    content: "99+";
    padding-inline: 0.1875rem;
  }
}

注意看,查询里用的是attr(data-消息数 type(<number>)),不是直接attr(data-消息数)。因为范围比较要的是数值,不指定类型的话,取出来是字符串,没法跟99比大小。这个坑踩过就知道疼了。同时,@container得放在::after的规则里,才能读到父元素[data-消息数]上的属性值。

跟不同单位数值比大小

长度也能跨单位比较

范围语法厉害的地方还在于,不同单位的长度也能比。比如想根据字号粗细自动调整字重,让字小的时候细一点,字大的时候粗一点,看着舒服。

<h1>
  <span>标题在这儿</span>
</h1>
h1 {
  /* 默认浏览器给h1的字号是32px,故意改成31px */
  font-size: 31px;

  span {
    /* 在span里,1em就等于31px */
    font-weight: if(
      style(1em < 32px): 100;
      style(1em > 32px): 900
    );
  }
}

这里1em是个相对单位,32px是绝对单位,但俩都是长度,所以能比。31px小于32px,字重就变成100,看着细一点;要是把font-size去掉,1em就变回默认的32px,俩条件都不符合,字重用回默认的700。这种写法让样式自己根据环境调整,不用JS一遍遍算。

单位混用时的注意点

比较的时候,单位可以不一样,但数据得是同一种类型。比如empx都是<length>,能比。%px就不能直接比,因为一个是百分比一个是长度。还有像之前的--亮度例子,如果不想带百分号,可以直接写成数字,但要确保所有比较的地方都不带单位。

.card {
  --亮度: 10;
  background: hsl(270, 100, var(--亮度));

  color: if(
    style(--亮度 < 50): white;
    style(--亮度 >= 50): black
  );
}

这样写也没问题,但要统一。别一个地方带百分号,另一个地方不带,那样比较会出问题。