CSS终于支持检测粘性定位是否粘住以及滚动相关状态

164 阅读5分钟

前言

我们知道,position: sticky可以很轻松帮助我们实现粘性定位,但目前存在一个很大的问题:我们无法检测粘性元素是否已经粘在某一侧。

这是一个很常见的需求,我们很可能需要在元素粘住的时候添加相应样式(例如导航栏粘在顶部后添加阴影)如果要使用 JS 去检测滚动偏移去计算,那将消耗比较多的资源,且需要考虑的情况也很多。虽然 CSS 也有比较 hack 的方法实现这样的阴影,但更多其他需求很难说都能做到。

与此类似的,其他与滚动相关的状态我们也很难检测,例如:容器是否可滚动、是否可以朝某个方向滚动、滚动是否已经吸附等等,这些东西在新的CSS容器查询中都将得到解决

容器查询

什么是容器查询?简单来说就是查询某个元素是否匹配某些特定的条件,并给其下面的元素设置指定样式。这类似于@media媒体查询,不同的是容器查询使用@container,且我们必须在目标元素通过CSS container-type 指定需要查询的条件,之前支持的条件有:

  • inline-size: 可查询容器的内联尺寸(默认为宽度)
  • size: 可查询容器的宽度和高度
  • normal: 可查询容器的某些样式是否满足特定的值
.post {
  container-type: inline-size;
}

@container (width > 650px) and (width < 1000px) {
/* 如果.post的宽度满足范围,其下面的.card将会生效该样式 */
  .card {
    width: 50%;
    background-color: gray;
    font-size: 1em;
  }
}

.card {
    color: white;
    /** 也可以将@container查询嵌套在一般的声明中,它与上面的声明效果相同。这需要浏览器支持嵌套@声明 */
    @container (width > 650px) {
        color: red;
    }
}

@container style(color: red) or style(border: 2px solid red) {
/** 与@media类似,条件可以通过 and or not 来组合。当样式满足条件,则该查询生效 */
}

早在 2023 年初各大浏览器就均已支持上述的容器查询,更多介绍和用法可参考MDN或其他文章,本次我们带来的是容器查询最新支持的滚动状态查询。在最新的 Edge 或 Chrome 133版本中,container-type新增了scroll-state支持,拥有该值的元素支持以下三种滚动状态查询。

查询粘性元素是否粘住

粘性元素在到达指定偏移之前会表现为静态定位,滚动指定偏移后才会粘在某一侧。在之前,无论是 JS 还是 CSS 都没有十分有效的方法查询粘性元素是否粘住。依靠 JS 监听滚动有一个非常大的问题,我们无法监听offsetTop这类值的变化,只能退而求其次监听其他元素的 Resize 和 Mutation,亦或是不断轮询,这是非常麻烦的。

而容器查询可通过scroll-state(stuck: top)直接查询粘性元素是否在某一方向粘住(stuck),方向的值可以为leftrighttopbottom以及其对应的四个逻辑方向,另外none也是有效值,用于查询未粘住的情况。具体使用如下:

div {
    container-type: scroll-state;
    position: sticky;
    top: 20px;
    nav {
        @container scroll-state(stuck: top) {
          background: var(--surface-1);
          box-shadow: var(--shadow-5);
          border-radius: 10px;
          margin-inline: 20px;
        }
    }
}

完整的高级示例可在线打开CodePen参考,注意需版本在133之上的 Edge 或 Chrome

sticky.gif

查询指定方向是否可滚动

一些情况我们可能需要查询容器是否可以往某个方向滚动,并使某些元素动态展示出来。最常见的当属“回到顶部”按钮,又或者是横向滚动发生后展示左右箭头按钮便于用户点击,亦或是展示阴影来表明某个方向可以滚动。这些通过 JS 倒是不难实现,但现在可通过 CSS 更加高效地实现。

通过scroll-state(scrollable: top)可查询其是否可以往某个方向滚动。可指定的值与上例相同,另外none代表无可滚动方向,即不可滚动

.cotainer {
    container-type: scroll-state;
    .to-top {
        opacity: 0;
        pointer-events: none;
        @container scroll-state(scrollable: top) {
          /** 当容器可以向上滚动时,将“回到顶部”按钮展示出来 */
          opacity: 1;
          pointer-events: all;
        }
    }
}

完整的高级示例可参考CodePen

查询目标是否即将滚动吸附或已吸附

我们知道scroll-snap-type可以为滚动容器设置滚动吸附,当滚动一定距离后自动滚动到指定位置,使目标吸附到滚动容器,即轮播图的效果。

如果要做一些比较生动的效果,例如某一目标即将吸附时,该目标进行高亮。在 JS 层面我们之前可以通过scrollsnapchangingscrollsnapchange事件获得比较准确的时机,做起来稍显麻烦,但若是通过监听滚动自行计算那就更麻烦了。

新的容器查询scroll-state(snapped: x)可查询目标是否即将或者已经吸附(snap)到滚动容器,其可以指定xy以及对应的两个逻辑方向blockinline,也可以指定both查询双边,另外none可以用于查询其是否为滚动吸附目标

比较高级的示例可在线打开CodePen1CodePen2参考

snapped.gif

总结

CSS 容器查询新增了scroll-state支持,而scroll-state可以查询stuck(粘性元素)、scrollable(可滚动)、snapped(滚动吸附)三种滚动相关状态。目前仅 Chromium 系浏览器 133+ 的版本支持,只适合了解或对兼容性没有要求的项目使用。

未来的标准将在 DOM 元素上添加matchContainer()方法,这类似于window.matchMedia()方法,可用于在 JS 动态监听某些条件是否生效,灵活性又大大提升,不过短期是看不到了。

在133以及未来的134版本中,谷歌还新增了不少比较重量级的特性,未来几天会抽空输出,感兴趣的可以关注一波哦,以后也会不断给大家带来 Web 新特性的报道