整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

大胆尝试这些新的CSS属性,释放CSS的力量吧(一)

大胆尝试这些新的CSS属性,释放CSS的力量吧(一)

载说明:原创不易,未经授权,谢绝任何形式的转载

本文章系《Unleashing the Power of CSS》(释放CSS的力量,暂且这么翻译吧)一书的学习笔记,希望通本书的学习,系统的梳理下CSS相关的高级新特性。本篇文章是其第一部分,由于全书英文版,理解和阅读会有偏差,欢迎各位大佬们指正,我们一起共同提高。

开篇

在过去的几年里,CSS引入了许多新的改进功能,并且跨浏览器的努力提高了兼容性,使这门语言比以往任何时候都更加稳定!让我们回顾一下布局、响应式设计、元素样式、属性和选择器方面的这些增强功能,并且也来看一看即将推出的新功能。

现在,浏览器之间新功能的协调实施意味着我们几乎可以在它们出现的同时开始使用这些功能,这对于保持我们的样式表尽可能简单非常有帮助。现在,只需几个单行属性就可以替代多行的hacky解决方案。在某些情况下,新提供的功能甚至可能意味着我们可以删除以前需要的JavaScript解决方案,以解决旧限制!

新的和增强的属性

Custom Properties(自定义属性或变量)

随着Internet Explorer 11的生命周期进入尾声,现在是时候开始使用自定义属性了!自定义属性,也被称为“CSS变量”,允许我们定义可在样式表中重复使用的值。自定义属性可以作为属性的整个值或部分值使用,我们还可以在JavaScript中修改自定义属性。

aspect-ratio

一种新的属性可以消除“填充hack”,它是 aspect-ratio 。它按照其名称的意思,允许我们为元素定义一个纵横比。我所提到的 hack 通常用于保持视频嵌入的16:9比例。现在,通过这个属性和声明 aspect-ratio: 16/9 ,可以实现这个比例。它还是实现完美正方形的快速方法,使用 aspect-ratio: 1 即可。

这是一个代码演示,展示了如何使用 aspect-ratio 与旧属性 object-fit 结合使用,以保持一致的头像大小,无论原始图像的比例如何,而且不会扭曲图像。

Html部分

<ul class="avatar-list">
  <li>
    <figure>
      <img src='https://images.unsplash.com/photo-1640952131659-49a06dd90ad2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NjkwNTE0MTQ&ixlib=rb-4.0.3&q=80&w=400' alt=''>
      <figcaption>Aaron Fizzle</figcaption>
    </figure>
  </li>
  <li>
    <figure>
      <img src='https://images.unsplash.com/photo-1544725176-7c40e5a71c5e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NjkwNTE0MTQ&ixlib=rb-4.0.3&q=80&w=400' alt=''>
      <figcaption>Lily Sebastian</figcaption>
    </figure>
  </li>
  <li>
    <figure>
      <img src='https://images.unsplash.com/photo-1628157588553-5eeea00af15c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NjkwNTE1MTU&ixlib=rb-4.0.3&q=80&w=400' alt=''>
      <figcaption>Devon Albian</figcaption>
    </figure>
  </li>
</ul>

Css部分

.avatar-list img {
  /* Make it a square */
  aspect-ratio: 1;
  /* Fit the image to it's container without distortion */
  object-fit: cover;
  /* Make the square round */
  border-radius: 50%;
  width: 100%; /* Make sure the image fills the container */
  height: 100%; /* Make sure the image fills the container */
}

* {
  box-sizing: border-box;
  margin: 0;
}

html {
  height: 100%;
}

body {
  min-height: 100%;
  font-family: system-ui, sans-serif;
  display: grid;
  place-content: center;
  background-color: mediumvioletred;
  padding-inline: 1rem;
}

/* Standard responsive image fix */
img {
  max-width: 100%;
}

.avatar-list {
  list-style: none;
  padding: 0;
  background: #fff;
  border-radius: 0.5rem;
  box-shadow: 0.25rem 0.25rem 0.5rem -0.15rem hsl(0 0% 0% / 30%);
  border: 1px solid hsl(0 0% 0% / 10%);
}

.avatar-list li {
  position: relative;
  padding: 3%;
  font-size: 1.35rem;
  font-size: clamp(0.8rem, 0.8rem + 2cqi, 1.5rem);
  color: hsl(0 0% 45%);
  letter-spacing: 0.03em;
}

.avatar-list li + li::before {
  content: "";
  position: absolute;
  top: 0;
  left: calc(15% + 1rem);
  right: 3%;
  border-top: 1px solid hsl(0 0% 0% / 15%);
}

.avatar-list figure {
  display: grid;
  grid-template-columns: 15% 1fr;
  align-items: center;
  gap: 1rem;
}

https://codepen.io/SitePoint/pen/oNaNaao

Individual Transform Properties(个体变换属性)

浏览器中还新增了各自的变换属性。Chrome 104进行的CSS变换具有独立的属性。这些属性是 scale , rotate 和 translate ,您可以使用它们来单独定义变换的各个部分。

并非所有的转换函数都有相应的个体属性,例如 skewX() 和 matrix() 。

.target {
  translate: 50% 0;
  rotate: 30deg;
  scale: 1.2;
}

Logical Properties(逻辑属性)

CSS的Logical Properties(逻辑属性)是一种用于处理文本和布局的属性,它们考虑了文本流的逻辑方向而不是物理方向。在CSS中,文档可以采用不同的书写模式,例如从左到右(LTR)的水平书写模式和从右到左(RTL)的水平书写模式,以及垂直书写模式。Logical Properties的目标是使样式更加灵活,适应不同的书写模式,而不需要为每种书写模式都编写不同的样式。

如果我们要为国际受众管理内容,则可以考虑使用逻辑属性。适用于大多数 CSS 2.1 属性,逻辑变体考虑了文本的编写模式和流。对于标准的英文文本,我们将“左/右”换成“内联”,用“top/bottom”换成“block”:

.element {
  margin-block: 2rem;
}

如上例所示,逻辑属性还提供了一次设置两边的简写,其中 margin-block 水平写入模式等效于 set margin-top 和 margin-bottom 。

新的选择器

近期对CSS最有影响力的三个变化是:is、:where和:has伪类选择器。以下是它们的概述:

:is() ,它用于选择满足括号内任何选择器的元素。这个伪类可以帮助你编写更简洁和可维护的 CSS 代码,尤其是当你需要同时匹配多个选择器时。例如, :is(#id, a, .class) 将具有一个 id 的特异性。

:where 是一个 CSS 伪类选择器,它与 :is 伪类选择器类似,可以用于选择满足括号内任何选择器的元素。它的语法也与 :is 相似,但有一个重要的区别::where 不会影响优先级。

与 :is 不同,:where 不会增加或改变样式规则的优先级。这意味着,无论你在样式表中的什么位置使用 :where,它都不会改变选择器的权重,不会增加特异性(specificity),也不会影响其他样式规则的优先级。

这使得 :where 在一些情况下非常有用,特别是当你需要选择一组元素,但不希望影响其他选择器的优先级时。例如,假设你有一个已经存在的 CSS 样式表,其中包含了一些具有不同权重和特异性的样式规则,但你希望添加一个新的规则,同时不改变其他规则的优先级,你可以使用 :where 来实现这一点。

/* 不使用 :where */
.btn {
  background-color: #3498db;
}

/* 使用 :where,不影响其他规则的优先级 */
:where(.btn-primary, .btn-secondary, .btn-danger) {
  background-color: #3498db;
  color: white;
}

:has() 是期待已久的“父选择器”,它允许检查父元素是否包含特定的子元素,并对父元素进行样式设置,或者扩展为复合选择器以对子元素进行样式设置。(本系列教程中有关于 :has() 的完整教程。)

这个演示利用 :where() 、 :is() 和 :has() 来创建一个作者简介组件,根据是否有头像来改变网格显示属性。

<aside class="bio">
  <img class="avatar" src='https://images.unsplash.com/photo-1554727242-741c14fa561c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NzExNDcxMjM&ixlib=rb-4.0.3&q=80&w=400' alt=''>
  <h2>Jane Stylesheet</h2>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsa quam aspernatur, nobis ex rem iure!</p>
</aside>

<aside class="bio">
  <h2>Bob Markup</h2>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam neque a blanditiis praesentium impedit.</p>
</aside>
* {
  box-sizing: border-box;
  margin: 0;
}

body {
  font-family: system-ui;
  padding: 3vw;
  background-color: mediumvioletred;
}

img {
  display: block;
  max-width: 100%;
}

:where(* + *) {
  margin-block-start: 1rem;
}

.bio :is(h2, p) {
  margin-block-start: 0;
}

.bio {
  background-color: white;
  display: grid;
  gap: 1rem;
  border-radius: 0.5rem;
  padding: 5%;
  box-shadow: 0 0 10px -2px hsl(0 0% 0% / 85%);
}

.bio:has(.avatar) {
  grid-template-areas: "avatar name" "avatar bio";
  grid-template-columns: min(25vw, 80px) 1fr;
}

.bio:has(.avatar) :not(.avatar) {
  grid-column: bio;
}

.bio .avatar {
  grid-area: avatar;
}

.avatar {
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 50%;
}

https://codepen.io/SitePoint/pen/WNaeJOy

:has() 在撰写本文时仅部分浏览器支持,因此上述演示目前仅适用于Safari 15.4+和Chrome/Edge 105+,以及启用 layout.css.has-selector.enabled 标志的Firefox 103。

增强的 :not()

最近,:not() 选择器已经增强,可以接受一个选择器列表,这使得 :not(nav a, footer a) 成为有效的语法。然而,与 :is() 和 :where() 不同,这个更新并没有使 :not() 对于无效的选择器更加宽容,因为需要保持向后兼容性支持。

焦点选择器

下面的两个新伪类都会影响焦点行为。当子元素处于焦点状态时,可以使用 :focus-within 选择器来为父元素设置样式,比如表单字段周围的容器。对于元素焦点样式,我们现在可以使用 :focus-visible ,它最近取代了 :focus 成为跨浏览器默认的元素焦点样式。

下面是 :focus-within 的一些关键点和与 :focus 的区别:

:focus-within 选择器:

  • 选择包含有焦点元素的祖先元素。
  • 当用户在页面上的某个元素上聚焦(例如,输入框或按钮),并且该元素是其祖先元素(例如,一个表单或一个包含该输入框的 div)内的子元素时,祖先元素将匹配 :focus-within。
  • 通常用于创建包含输入框的表单的外观,以在用户输入时改变整个表单的样式或行为。

:focus 选择器:

  • 选择当前具有焦点的元素。
  • 通常用于样式化或增强当前拥有焦点的元素,例如,更改输入框的边框颜色或文本区域的背景颜色。
  • 不会选择包含有焦点元素的父元素。

假设有以下 HTML 结构:

<div class="container">
  <input type="text" id="username" />
  <input type="password" id="password" />
</div>

现在,我们可以使用 :focus 和 :focus-within 来添加一些样式:

/* 当输入框具有焦点时,样式化输入框本身 */
input:focus {
  border: 2px solid blue;
}

/* 当包含有焦点输入框的容器具有焦点时,样式化整个容器 */
.container:focus-within {
  background-color: lightgray;
}

在上面的示例中,当用户点击输入框时,输入框自身会具有蓝色边框(使用 :focus 选择器),同时包含有焦点输入框的容器 .container 也会变为灰色背景(使用 :focus-within 选择器)。这使得用户在与表单交互时,不仅输入框本身被强调,整个表单容器也能够获得焦点的可视反馈。

总之,:focus-within 选择器用于选择包含有焦点元素的祖先元素,而 :focus 选择器用于样式化具有焦点的元素本身。这两个选择器可以一起使用,以创建更丰富的交互体验。

::marker

最后但并非最不重要的是,伪元素 ::marker 允许我们直接选择和样式化 <ul> 和 <ol> 元素上的列表项符号和编号,以及 <summary> 元素的“插入符号”。这意味着我们可以使用 ::marker 来仅改变列表的符号颜色!

元素样式的改进

accent-color

框架和设计系统最常见的改变之一是本地表单字段样式。在 accent-color 属性出现之前,甚至改变表单元素的颜色都是不可能的。现在,我们可以通过 accent-color 影响单选按钮和复选框的选中外观,以及范围输入和进度元素的填充状态。

下面是一个示例,演示了如何使用 accent-color 属性:

a {
  accent-color: blue;
}

在这个示例中,accent-color 属性应用于所有链接元素 (<a>),并将链接的强调颜色设置为蓝色。

color-scheme

如果我们想要根据用户的浅色或深色模式偏好来调整我们的界面,可以使用自定义切换和/或 prefers-color-scheme 查询,我们还应该添加color-scheme属性。这提供了一种选择,可以适应浏览器的UI元素,如滚动条、表单控件和CSS系统颜色。而 accent-color 让我们可以为一些元素选择自定义颜色, color-scheme 则要求浏览器进行更多的适应,例如要求文本输入和文本区域以浅色或深色主题显示。

建议将此应用于 :root 元素,并按照网站默认值的顺序列出这些值。换句话说,如果我们默认为浅色但支持深色,则列出 light dark 。如果我们默认为深色但支持浅色,则列出 dark light 。如果我们只支持 light 或 dark ,只需列出单个值即可:

:root {
  color-scheme: light dark;
}

例如,你可以这样定义一个明亮模式和一个暗模式的颜色方案:

/* 明亮模式 */
@media (prefers-color-scheme: light) {
  body {
    background-color: white;
    color: black;
  }
}

/* 暗模式 */
@media (prefers-color-scheme: dark) {
  body {
    background-color: black;
    color: white;
  }
}

Forced-color Modes (强制色彩模式)

为了完善关于颜色的主题,还有一个偏好查询和属性对需要讨论。在Windows上,一些用户需要“高对比度”主题,其中操作系统强制使用减少的调色板来代替我们定义的颜色。调色板填充系统颜色的值,替换背景、文本、按钮和链接颜色等内容,而像盒子阴影这样的样式则被删除。

如果我们有使用颜色的关键样式,比如产品颜色样本,我们可能需要在 forced-colors 属性旁边使用 force-color-adjust 查询。根据以下配对,我们原始的 .swatch 颜色将被保留:

@media (forced-colors: active) {
  .swatch {
    forced-color-adjust: none;
  }
}

强制使用颜色应该谨慎使用,只有在用户体验受到高对比度主题颜色交换的负面影响时才使用。如果您对高对比度主题不熟悉,请了解如何使用强制颜色进行样式设置。

Text Decoration

在文本装饰方面,我们现在有可用的 text-underline-offset 属性,它允许我们调整定义的 text-decoration 的位置,使其偏离原始位置。 text-decoration-thickness 伴随属性允许我们控制 text-decoration 的描边粗细。结合使用这些属性,可以消除使用边框甚至伪元素来样式化链接下划线的hack。

以下样式规则将文本下划线向下偏移 2 像素:

a {
  text-decoration: underline;
  text-underline-offset: 2px;
}

结束

由于文章内容篇幅有限,今天的内容就分享到这里,文章结尾,我想提醒您,文章的创作不易,如果您喜欢我的分享,请别忘了点赞和转发,让更多有需要的人看到。同时,如果您想获取更多前端技术的知识,欢迎关注我,您的支持将是我分享最大的动力。我会持续输出更多内容,敬请期待。

载说明:原创不易,未经授权,谢绝任何形式的转载

在我的好奇心驱使下,我想为什么不去查看一些热门网站,并了解一下它们是如何实现评论组件的布局。起初,我认为这将是一个简单的任务,但实际并非如此。

我花了很多时间试图理解这是如何工作的,以及如何通过现代 CSS(如 :has、size container queries 和 style queries)来改进它。在本文中,我将引导您了解我的思考过程,并分享我在其中所得到的发现。

简介

以下是我们将要构建的布局。乍一看,它可能看起来很简单,但其中有很多微小的细节。

我们有一个评论,可以嵌套两个更深层次。我在本文中将这些称为“深度”。

图中展示了深度是如何根据每个评论的嵌套级别而变化的。

思考布局

在深入细节之前,我更愿意先着手处理布局,并确保它能很好地运作。这样的做法旨在探索现代CSS解决该问题的潜力。

首先要记住的是HTML标记。评论的结构很适合使用无序列表<ul>。

<ul>
  <li>
    <Comment />
  </li>
  <li>
    <Comment />
  </li>
</ul>

< Comment /> 充当评论组件的占位符。这是两条评论的列表的HTML,没有任何回复。

如果对其中一条评论进行回复,那么将会添加一个新的 <ul>。

<ul>
  <li>
    <!-- Main comment -->
    <Comment />
    <ul>
      <!-- Comment reply -->
      <li>
        <Comment />
      </li>
    </ul>
  </li>
  <li>
    <Comment />
  </li>
</ul>

从语义角度来看,上面的描述是合理的。

评论包装器布局 - 填充解决方案

我将标题称为“评论包装器”,以免混淆评论组件本身的含义。在下一节中,我将解释我构建布局以处理评论回复的缩进或间距的想法。

请考虑以下标记:

<ul>
  <li>
    <!-- Main comment -->
    <Comment />
    <ul>
      <!-- Comment reply -->
      <li>
        <Comment />
      </li>
    </ul>
  </li>
  <li>
    <Comment />
  </li>
</ul>

我们有两条主评论和一条回复。从视觉角度来看,它将如下所示:

我更倾向于将所有的间距和缩进处理都保留在 <li> 元素上,因为它们充当了评论组件的容器。

使data据属性来处理间距

我首先考虑的是在 <ul> 和 <li> 元素上使用数据属性。

<ul>
  <li nested="true">
    <!-- Main comment -->
    <Comment />
    <ul>
      <!-- Comment reply -->
      <li>
        <Comment />
      </li>
    </ul>
  </li>
  <li>
    <Comment />
  </li>
</ul>

在CSS中,我们可以这样做:

li[data-nested="true"],
li[data-nested="true"]
  li {
  padding-left: 3rem;
}

虽然前面提到的方法是有效的,但是我们可以通过使用CSS变量和样式查询来进一步改进。

使用CSS样式变量查询

我们可以检查容器中是否添加了CSS变量--nested: true,并根据此对子元素进行样式设置。

考虑以下标记,我在 <ul> 元素中添加了内联CSS变量--nested: true。

<ul>
  <li style="--nested: true;">
    <!-- Main comment -->
    <Comment />
    <ul style="--nested: true;">
      <!-- Comment reply -->
      <li>
        <Comment />
      </li>
    </ul>
  </li>
  <li>
    <Comment />
  </li>
</ul>

使用样式查询,我可以检查CSS变量是否存在,并根据其来为 <li> 元素添加样式。目前,这个特性只在 Chrome 的实验性版本 Canary 中得到支持。

@container style(--nested: true) {
  /* Add spacing to the 2nd level <li> items. */
  li {
    padding-left: 3rem;
  }
}

你提到为什么我更喜欢使用样式查询而不是数据属性的原因:

  • 更易于理解:样式查询采用 @container 语法,简单的文字描述已经足够表达其含义。如果容器中有 --nested: true 的 CSS 变量,那么就应用接下来的样式。
  • 可以组合多个样式查询以更好地控制CSS:通过组合多个样式查询,我们可以更灵活地控制CSS样式。
  • 可以与尺寸容器查询结合使用:如果需要,我们还可以将样式查询与尺寸容器查询结合使用,进一步增强对CSS的控制能力。

评论包装器布局 - 使用CSS Subgrid

另一个解决方案是使用CSS子网格(subgrid)来构建嵌套评论布局。坦率地说,这将需要更多的CSS代码,但是探索新的CSS特性的潜力是非常有趣的。

让我简要地解释一下子网格(subgrid)来给您一个概念。考虑以下CSS网格:

<ul>
  <li class="main">
    <!-- Main comment -->
    <Comment />
    <ul>
      <!-- Comment reply -->
      <li>
        <Comment />
        <ul>
          <li>
            <Comment />
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>
li.main {
  display: grid;
  grid-template-columns: 3rem 3rem 1fr;
}

这将被添加到 <ul> 列表的第一个直接 <li> 元素中。这个网格看起来会像这样:

目前,在CSS网格中,不能将主网格传递给子项目。在我们的情况下,我希望将网格列传递给第一个 <ul>,然后再传递给该 <ul> 的 <li>。

幸好,CSS子网格(subgrid)使得这种操作成为可能。目前,它仅在Firefox和Safari浏览器中可用。Chrome浏览器也在朝这个方向发展!

请参考以下示意图:

首先,我们需要设置主网格如下所示。我们有3列。

@container style(--nested: true) {
  li.main {
    display: grid;
    grid-template-columns: 3rem 3rem 1fr;

    .comment {
      grid-column: 1 / -1;
    }
  }
}

.comment 组件将始终跨越整个宽度。这就是为什么我添加了 grid-column: 1 / -1。这意味着:“从第一列到最后一列,让评论组件横跨全部列”。这样做有助于避免在嵌套的每个深度中手动输入列号。类似于这样:

/* Not good */
@container style(--nested: true) {
  li.main .comment {
    grid-column: 1 / 4;
  }

  ul[depth="1"] .comment,
  ul[depth="2"] .comment {
    grid-column: 1 / 3;
  }

很好!接下来的步骤是将深度为1的评论放置在主网格内,然后添加子网格并定位内部的 <li> 元素。

@container style(--nested: true) {
  ul[depth="1"] {
    grid-column: 2 / 4;
    display: grid;
    grid-template-columns: subgrid;

    > li {
      grid-column: 1 / 3;
      display: grid;
      grid-template-columns: subgrid;
    }
  }
}

最后,我们需要定位深度为2的列表(depth=2)。

@container style(--nested: true) {
  ul[depth="2"] {
    grid-column: 2 / 3;
  }
}

这个解决方案确实有效,但我对其中所有的细节并不太喜欢。一个简单的内边距就可以解决问题。

思考连接线的问题

为了更清楚地显示评论和回复之间的关联,我们可以在主评论和回复之间添加连接线。Facebook团队使用了一个 <div> 元素来处理这些连接线。但是,我们能否尝试一些不同的方法呢?

请参考以下示意图:

你的第一反应可能会误导你:「嗨,这看起来就像一个带有左边框和底部边框以及左下角的边框半径的矩形。」

li:before {
  content: "";
  width: 30px;
  height: 70px;
  border-left: 2px solid #ef5da8;
  border-bottom: 2px solid #ef5da8;
  border-bottom-left-radius: 15px;
}

一开始我在上面的CSS代码中添加了height:..,但我意识到这样做不行。因为我无法准确知道连接线的高度。这是因为在CSS中无法直接根据内容动态调整高度。问题出在这里:我需要确保连接线的底部与第一个回复的头像对齐。

于是我想到可以使用伪元素来实现这个目的。如果那条弯曲的连接线可以分成两部分呢?

我们可以将连接线添加到主评论上,而弯曲的元素则用于表示回复。

接下来,如果我们有另一个回复针对第一个回复呢?以下是一个图示,展示了连接线是如何运作的:

在CSS中,我们需要使用伪元素来实现连接线的效果。在开始编写CSS代码之前,我想强调一下,这条线或弯曲部分将根据整行来定位。

处理添加到主评论的连接线

这是我们要解决的第一个挑战。如果主评论有回复,我们需要为其添加连接线。我们可以使用CSS的 :has 伪类来检查一个 <li> 元素是否包含一个 <ul>,如果是,则应用所需的CSS样式。

请参考以下HTML代码:

<ul style="--depth: 0;">
  <li style="--nested: true;">
    <Comment />
    <ul style="--depth: 1;">
      <li>
        <Comment />
      </li>
    </ul>
  </li>
  <li>
    <Comment />
  </li>
</ul>

我们需要为每个 <Comment /> 元素应用以下条件的样式:

  • 它是 <li> 元素的直接子元素
  • <li> 元素有一个 <ul> 作为子元素
  • 父元素的 depth 属性为 0 或 1

下面是如何将上述条件翻译为CSS代码。CSS变量 + 样式查询 + :has 伪类=一个强大的条件样式。

@container style(--depth: 0) or style(--depth: 1) {
  li:has(ul) > .comment {
    position: relative;

    &:before {
      content: "";
      position: absolute;
      left: calc(var(--size) / 2);
      top: 2rem;
      bottom: 0;
      width: 2px;
      background: #222;
    }
  }
}

上面的例子展示了为什么我更喜欢使用样式查询而不是HTML数据属性来处理评论和回复的深度。

接下来,我们需要为深度为1的回复添加连接线和弯曲元素。这次,我们将使用 <li> 元素的 :before 和 :after 伪元素。

@container style(--depth: 1) {
  li:not(:last-child) {
    position: relative;

    &:before {
      /* Line */
    }
  }

  li {
    position: relative;

    &:after {
      /* Curved element */
    }
  }
}

最后,我们需要为深度为2的每个 <li> 添加弯曲元素,同时在深度为2的所有 <li> 中除了最后一个之外,都需要添加连接线。我们需要按照以下逻辑进行操作:

  • 为深度为2的每个 <li> 添加弯曲元素。
  • 为深度为2的所有 <li> 中除了最后一个之外的每个 <li> 添加连接线。

弯曲元素是一个带有边框和左下角半径的矩形。让我来解释一下:

@container style(--depth: 2) {
  li {
    position: relative;

    &:after {
      content: "";
      position: absolute;
      inset-inline-start: 15px;
      top: -2px;
      height: 20px;
      width: 28px;
      border-inline-start: 2px solid #000;
      border-bottom: 2px solid #000;
      border-end-start-radius: 10px;
    }
  }

  li:not(:last-child) {
    &:before {
      /* Line */
    }
  }
}

请注意,我在边框(border)和边框圆角(border-radius)方面使用了逻辑属性。这样做有助于在文档语言为RTL(从右到左)时动态翻转用户界面。我将在文章后面详细介绍这个内容。

禁用连接线

如果出于某种原因我们需要隐藏连接线,那么通过样式查询(style queries)来实现这一点就像切换CSS变量的开关一样简单。

通过将所有与深度相关的样式查询嵌套在 --lines: true 的样式查询内部,我们可以确保只有在设置了该 CSS 变量时才会显示连接线。

@container style(--lines: true) {
  @container style(--depth: 0) {
  }

  @container style(--depth: 1) {
  }

  @container style(--depth: 1) {
  }

  @container style(--depth: 2) {
  }
}

评论组件

你可能会认为上面所有的内容都只是用于主要的布局和连接线。是的,没错!我甚至还没有考虑评论组件。

让我们仔细看一下评论组件:

乍一看,这似乎是使用 flexbox 的绝佳场景。我们可以通过 flexbox 将头像和评论框显示在同一行上。

<div class="comment">
  <div class="user"></div>
  <!-- Because an additional wrapper doesn't hurt. -->
  <div>
    <div class="comment__body"></div>
    <div class="comment__actions">
      <a href="#">Like</a>
      <a href="#">Reply</a>
    </div>
  </div>
</div>

请注意,上面的HTML代码非常基础,它并不代表生产级别的代码,只是用来帮助解释CSS的内容。

.comment {
  --size: 2rem;
  display: flex;
  gap: 0.5rem;
}

.avatar {
  flex: 0 0 var(--size);
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
}

这是基本的布局,但实际应用中可能会更加复杂。然而,在本文中,我将仅专注于需要解释的独特和重要的内容。

接下来,我们会讨论评论主体组件的一些考虑事项。

评论组件的这部分将需要处理以下内容:

  • 最小宽度
  • 长内容
  • 多语言内容(左到右 vs 右到左)
  • 上下文菜单
  • 评论交互
  • 编辑状态
  • 错误状态

我在这篇文章中无法详细展示上述所有内容,因为可能需要写一本书来完整讲述。

我将重点介绍一些我认为适合使用现代CSS的有趣技巧。

改变用户头像大小

在回复嵌套在评论中时,用户头像的大小将变小。这样做有助于在视觉上更容易区分主评论和回复。

使用样式查询是非常适合这种情况的。

.user {
  flex: 0 0 var(--size);
  width: var(--size);
  height: var(--size);
}

.comment {
  --size: 2rem;

  @container style(--depth: 1) or style(--depth: 2) {
    --size: 1.5rem;
  }
}

动态文本对齐与 dir=auto 属性

评论可能包含从左到右(LTR)或从右到左(RTL)的语言。根据内容的语言,文本对齐应该有所区别。感谢 dir=auto HTML 属性,我们可以让浏览器自动处理这一点。

<div class="comment">
  <div class="user"></div>
  <div>
    <div class="comment__body">
      <p dir="auto"></p>
    </div>
    <div class="comment__actions"></div>
  </div>
</div>

CSS 逻辑属性

通过使用 CSS 逻辑属性,我们可以构建评论组件,使其能根据文档的方向进行自适应调整。同样的原理也适用于连接线。

表情符号回复状态

当用户添加仅由表情符号组成的评论时,评论容器将会有一些变化:

  • 没有背景颜色
  • 没有内边距

这是使用CSS :has伪类的一个绝佳用例。

.comment:has(.emjois-wrapper) {
  background: var(--default);
  padding: var(--reset);
}

结论

现代CSS的潜力一直让人兴奋不已。尝试用新的方式思考已经构建的组件或布局,是学习新知识的绝佳途径。我在整个过程中学到了很多新东西,并享受了整个过程。

由于文章内容篇幅有限,今天的内容就分享到这里,文章结尾,我想提醒您,文章的创作不易,如果您喜欢我的分享,请别忘了点赞和转发,让更多有需要的人看到。同时,如果您想获取更多前端技术的知识,欢迎关注我,您的支持将是我分享最大的动力。我会持续输出更多内容,敬请期待。


者:Ahmad

译者:飘飘

https://ishadeed.com/article/image-techniques/