likes
comments
collection
share

现代 CSS 真是太酷了

作者站长头像
站长
· 阅读数 10

前几天看到大神 Chris Coyier 发的一条推文

现代 CSS 真是太酷了

Modern CSS is so cool

(现代 CSS 真是太酷了)

确实很酷,酷到几乎都不认识😂。不懂没有关系,我们可以学习嘛。下一番功夫,我们也可以说出:Modern CSS is so cool!

那就按照图片上出现的顺序,从上到下,逐个击破。

max-inline-size

我们给一个元素设置 max-inline-size:200px, 这个元素的最大宽度就变成了200px:

现代 CSS 真是太酷了

似乎和设置 max-width: 200px 效果一模一样?是的,一模一样。直到有个新需求:

新需求:把这段文字旋转90度显示

旋转渲染的方向?那我们给它加上 ‌writing-mode: vertical-rl

现代 CSS 真是太酷了

文字旋转了90度,原先设置的 max-inline-size:200px 变成了这个p标签的「最大高度」,但我们把脖子往右歪着看这段文字,从文字的阅读方向来看,这 200px 依然是文字的「最大宽度」。

我们比较一下,如果设置的是 max-width: 200px, 文字旋转后会是什么效果:

现代 CSS 真是太酷了

可以看出,无论元素怎么旋转, max-width 永远只在物理水平方向起作用,这可能不是我们想要的结果,想要实现和 max-inline-size 一样的效果,也可以在旋转之后,把 max-width 改成 max-height,但明显不如 max-inline-size 灵活优雅。

综上,max-inline-size 的秘密是:让元素的最大宽度随着元素的渲染方向(一般与writing-mode一起使用)动态改变。

深入学习

MDN-max-inline-size

ch 单位

ch 是「character」的缩写,粗略(不完整)的理解是:1ch 就是一个字符的宽度,如果某个字体的字符宽度是3px,那 10ch 就代表 30px(10*3)。

来看看正统解释(MDN):

The advance measure (width) of the glyph "0" of the element's font

ch 代表某个元素所用的字体中的 0(零/zero)这个字符的宽度。ennnnnn🤔️。不得不说,这个属性有点「鬼魅」的气息。

举个例子,两段不同字体的文字,同样设置 max-width: 80ch ,宽度可能存在差异:

现代 CSS 真是太酷了

深入学习

MDN直达链接-Values_and_units

a look at the ch css unit

margin-inline

直接看图:

现代 CSS 真是太酷了

这个例子中,margin-inline 设置的元素「左右」两边的 margin。相当于:margin-left:20px;margin-right:50px;。如果我们改变一下元素的方向,设置 writing-mode: vertical-rl;:

现代 CSS 真是太酷了

元素旋转了90度,从我们的肉眼看:旋转前元素的「左右」margin 实际上变成了「上下」margin。但相对于文字的「阅读方向」(把脖子往右歪看看),margin 依然在文字的「左右」两边。

这就是 margin-inline 用武之地:在元素渲染方向改变的时候,保留「左右」边距。让我们对比一下,同样的场景,如果是用margin-left:20px;margin-right:50px; 会长什么样:

现代 CSS 真是太酷了

可以看到,元素发生旋转之后,margin-leftmargin-right 依然维持在物理水平方向,并没有跟着元素渲染方向改变。

另外,margin-inlinemargin-inline-startmargin-inline-end 的简写形式。顾名思义,也可以单独使用这两个专用属性设置 margin。

深入学习

MDN-margin-inline

CSS Container Queries

现代 CSS 真是太酷了 (图片来源-MDN)

我们用媒体查询(Media Queries)写响应式样式的时候,默认的参照对象是浏览器 viewport(视区)的尺寸,比如上图中的@media (max-width: 30em) 的意思是当浏览器的视区小等于 30em 的时候,应用某某样式。如果我们需要更精细的控制,比如:当元素的父级容器的宽度大于 200px 的时候,字号增大。这时候就不是相对于浏览器视区了,而是相对于元素的容器(container),这个特性叫做 Container Queries

举个例子,如果我希望元素的字号相对于父级容器的宽度发生改变

现代 CSS 真是太酷了

首先,给目标容器(我们的例子中就是 div)加上 container-type 属性,这个属性作用是标记某个元素为 container,值可以是:

  1. inline-size: 容器响应左右方向的变化
  2. size:容器响应上下左右的变化
  3. normal:默认值,啥也没做

我们想要元素响应其容器宽度(左右)的变化,就选 inline-size。这样一来,div 就变成了容器元素。

接着, ‌@container (min-width: 500px) 表示这是一个容器查询,当容器(也就是div)宽度大于 500px 的时候,字号增大为32px。

使用 @container 语法时,元素会找到父级元素中,标记了 container-type 的,离自己最近的那一个作为参照的 container。如果我们想要指哪打哪,可以给container命名:

现代 CSS 真是太酷了

见上图,我们用 container-name 把 div 命名为「MagicBox」,这样在 container 查询的时候就可以直接指定相对的容器啦。

最后,container-typecontainer-name 可以简写成 container:

div {
  container: MagicBox / inline-size;
}

完全等同于👇:

div {
  container-name: MagicBox;
  container-type: inline-size;
}

深入学习

MDN-CSS_Container_Queries

cqi 单位

cqi 是 「container query inline size」的缩写,看名字就知道和 Container Queries 相关。请确保理解了上文中的 Container Queries 再继续。

长话短说,1cqi 表示容器宽度的1%,5cqi 表示容器宽度的 5%,10cqi 表示容器宽度的10%......就这么简单,使用 cqi 单位可以很容易地让容器内部元素的样式随着元素宽度动态地,丝滑地改变。比如,想要字号随着容器宽度改变:

<div>
  <p>
    Lorem ipsum dolor sit amet
  </p>  
<div>
div {
   container-type: inline-size;
}

p {
  font-size: 2cqi;
}

以上代码,会让 p 元素的 font-size 始终保持是 div 容器的 2%。如果 div 宽度是 1000px,font-size 就是 20px; div 宽度 500,font-size 就是 10px

值得注意的是,使用 cqi 的元素如果没有标记 container-type 的父级元素,那最终会找到 viewport 作为容器。

最后,cqi 只是全家桶中的一个,学有余力,也可以看看:

cqw: 1% of a query container's width

cqh: 1% of a query container's height

cqi: 1% of a query container's inline size

cqb: 1% of a query container's block size

cqmin: The smaller value of either cqi or cqb

cqmax: The larger value of either cqi or cqb

深入学习

MDN-CSS_Container_Queries

CSS max()

现代 CSS 很强大,其中一大原因是可以使用函数来表达复杂状态。比如我们常见的 calc() 方法。max()也是一大利器:

max 函数返回传入的表达式(或值)中最大的那一个。

如推文中出现的例子:font-size: max(64cqi, 24px)font-size 的值会是 64cqi 和 24px 中较大的那一个。我们知道 64cqi 的大小取决于容器的宽度,是动态变化的,而 24px 是静态的值。所以这样写,相当于把 font-size最小值设置为了 24px,最小不能小于24px,大的话可以随着容器宽度的变大而改变。

一个动态,一个静态的连用是常见的用法,可以用来限制最大/最小值。当然,max 函数可以接受多个值或表达式:

width: max(10vw, 4em, 80px);

font-size: max(min(0.5vw, 0.5em), 1rem);

相信你可以一眼看出上面代码的效果。

深入学习

MDN-max()

@layer

身为前端工程师,一定遇到过 CSS 样式冲突问题,原因无外乎是某个样式被优先级更高的其他样式覆盖了。解决办法可能是给自己的样式加上优先级更高的选择器(比如 id),或者堆叠选择器的深度增加样式优先级(比如 div.box > p > div > span.target),如果都不灵,就祭出杀手锏: !important

@layer 特性就是为了解决上述样式优先级导致的混乱问题。核心原理就是给 CSS 样式加上 layer:

@layer reset, base, theme, override;

👆上面代码创建了 4 个 layer,他们的优先级从低到高:base 的优先级高于 reset, theme 高于 base, override 最高。可以看出,申明的顺序决定优先级的高低。layer的命名和个数随意定,取决于具体的样式架构,比如我这么写的设计思路是:reset 来存放HTML的重置样式;base 放基础样式兜底;theme 放主题样式;override 优先级最高,可以覆盖前面所有layer 的样式。

创建 layer 也可以这样写:

@layer reset;
@layer base;     
@layer theme;  
@layer override;

/* 等价于 @layer reset, base, theme, override; */

来个例子,有以下 HTML 结构👇:

<div class="box" id="box-id">
    <p>Lorem ipsum dolor sit amet</p>
</div>

给 @layer 写样式👇:

@layer reset, base, theme, override;

@layer base {
  #box-id p {
    color: red;
  }
}

/* 虽然 class 选择器优先级不如 id,但由于 theme layer 的优先级更高,文字会被渲染成蓝色 */
@layer theme {
  .box p {
    color: blue;
  }
}

虽然 id 选择器的优先级高于 class 选择器,但由于class 选择器的样式写在更高优先级的 theme layer 中,所以最终 p 标签中得到文字会渲染成蓝色

请注意⚠️,没有写在 layer 中的样式,优先级高于所有 layer 中的样式:

@layer reset, base, theme, override;

@layer base {
  #box-id p {
    color: red;
  }
}

@layer theme {
  .box p {
    color: blue;
  }
}

/* ⚠️:没有写在 layer 中的样式,拥有最最最高的优先级,即使选择器优先级最低。最终文字会是黑色 */
p {
  color: black
}

这是因为,@layer 是新标准,需要无入侵地和旧有的样式兼容,最安全的策略,是让原先的无 layer 样式的优先级最高,各个 layer 样式就可以渐进地,局部地集成,不与原来的样式冲突。

Chris Coyier 建议,把所有的 CSS 都写到 layer 中,才能避免混乱。确实是这样的,如果只是局部集成,有可能因为引入了 layer 优先级,导致更混乱。无论如何,@layer 的全面应用还要考虑浏览器的兼容性,各大开源库的支持,任重道远。

深入学习

MDN-@layer

css tricks-css-cascade-layers

oklch()

就这样简单理解吧:这是一个 CSS 函数,调用后最终返回一个颜色。通过传入不同的参数,在一个「色彩空间」中,定位到一个颜色,返回。在UI设计上,OKLCH 被视为一个更好的色彩系统方案,引用这篇文章的说法:

✅ OKLCH 使用「感知亮度 L - 色度 C - 色相 H」三个易于理解的分量

✅ OKLCH 感知亮度均匀

✅ OKLCH 尽可能降低色度对亮度的影响(亥姆霍兹-科尔劳施效应)

✅ OKLCH 尽可能修复了色相偏移(阿布尼效应)

✅ OKLCH 支持为更广的色域提供编码支持(P3、Rec.2020及更高版本)

✅ oklch()已经加入目前 CSS4 的候选版本,Safari 已经支持

Anyway,我觉得这个东西不好在理论上理解。心里有个数,见到也大概知道是干嘛的,具体的在实战中学习吧。

深入学习

MDN-oklch()

OKLCH 色彩空间是搭建色彩系统的最佳选择

margin-trim

这还是实验中特性,即使是最新版(v112)的 Chrome 都还不支持,本着看个热闹的原则,了解一下也无妨。了解完发现还挺实用的。假设你写了个朴实无华的菜单:

<div>
    <span>Item1</span>
    <span>Item2</span>
    <span>Item3</span>
</div>

现代 CSS 真是太酷了

一个经典的菜单布局:容器是 div(一般设置成inline-block),里面每一项是 span。每一个 span 的左右 margin 都是 10px。这时候一个常见的样式需求是 -- 去掉第一个 span 的边距和最后一个 span 的边距,像这样👇:

现代 CSS 真是太酷了

在没有margin-trim的世界里,我们可以这样做:

/*去掉第一个span的左边距*/
span:first-child {
   margin-left: 0;
}

/*去掉最后一个span的右边距*/
span:last-child {
  margin-right: 0;
}

有点麻烦?有了margin-trim,只需要:

/*在元素的容器上加上 margin-trim */
div {
   margin-trim: inline;
}

最后,看看 margin-trim 有哪些:

  • none:默认值,啥也不干
  • block: 去掉容器内第一个块级子元素的左边距和最后一个快级子元素的右边距
  • block-start: 去掉容器内第一个块级子元素的左边距
  • block-end:去掉容器内最后一个块级子元素的右边距
  • inline: 去掉容器内第一个内联元素的左边距和最后一个内联元素的右边距
  • inline-start:去掉容器内第一个块内联子元素的左边距
  • inline-end:去掉容器内最后一个内联子元素的右边距

深入学习

MDN-margin trim

That’s it。这下我也可以真心地说一句:

Modern CSS is so cool