前端开发者学堂 - fedev.cn

图解CSS:CSS滚动捕捉(Part2)

发布于 大漠

图解CSS:CSS滚动捕捉这一章节分成两个部分,第一部分主要介绍了 CSS 滚动捕捉理论方面的知识,详细介绍了 CSS 滚动捕捉中的 scroll-snap-typescroll-snap-alignscroll-marginscroll-padding 以及 scroll-snap-stop 等属性。并且通过一些示例向大家展示了它们怎么使用。在第二部分,也就是接下来的内容中,从简单的示例着手(有些示例在第一部分也出现过),慢慢过渡到复杂的示例,比如模拟 iOS列表向左滑动的交互效果。如果你感兴趣的话,请继续往下阅读。

滚动捕捉的实例

前面我们花了较长的篇幅向大家展示了滚动捕捉相关的特性。接下来我们由易到难,来看看 CSS 滚动捕捉能帮我们做哪些事情。接下来会从简单地示例着手,慢慢过渡到更为复杂的用例。

列表滚动

在我们的日常开发中,时常都会碰到列表滚动的效果。比如下图这样的效果:

往往面对这样的需求,开发者都会借助 JavaScript (比如 Swiper )。时至今日,我们就可以使用 CSS 滚动捕捉来实现。为了节约构建 UI 样式时间,我们来实现一个图片列表滚动的效果。先从简单的开始:

<!-- HTML -->
<div class="container">
    <img src="https://picsum.photos/500/300?random=1" alt="" />
    <img src="https://picsum.photos/500/300?random=2" alt="" />
    <img src="https://picsum.photos/500/300?random=3" alt="" />
    <img src="https://picsum.photos/500/300?random=4" alt="" />
    <img src="https://picsum.photos/500/300?random=5" alt="" />
    <img src="https://picsum.photos/500/300?random=6" alt="" />
    <img src="https://picsum.photos/500/300?random=7" alt="" />
</div>

/* CSS */
.container {
    overflow-x: auto;
    scroll-behavior:smooth;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    scroll-padding: 20px;
}

img {
    scroll-snap-align: start;
}

你将看到下面这样的效果:

注意,示例中我们还使用了 scroll-behavior:smooth; ,它也是用来改善滚动体验的,如果你对该特性感兴趣的话,可以阅读《改变用户体验的滚动新特性》一文。

如果你体验了上面的示例,你会发现,你每次滚动的时候,一不小心可能会同时滚动多张图片。或许这样的效果并不是你所要的,你可能是需要每次滚动两张图片:

实现该效果,可以在第135... 按这样的类推,在滚动项目(即 img )上显式设置 scroll-snap-stop 属性,并取值为 always

img:nth-of-type(2n + 1) {
    scroll-snap-stop: always;
}

体验下面的效果,会有较强的体感:

注意,这个示例中采用了伪类选择器 :nth-of-type(),其中 2n + 1,就是用来获取奇数的 img

还有另外一种场景,也是时常能看到的效果。那就是每次滚动的时候,滚动项目都在滚动容器的中间:

要实现上图这样的效果很简单,我们只需要把滚动项目中的 scroll-snap-align 的设置为 center 即可:

img {
    scroll-snap-align: center;
    scroll-snap-stop: always;
}

效果如下:

这里显式设置 scroll-snap-stop: always; 是为了确保每次滚动的时候,只滚动一张图片。

全屏滚动

像幻灯片播放的效果,也是常见的一种效果,如下所示:

其实这个效果和前面所介绍的图像列表滚动效果是很相似的。同样使用 CSS 滚动捕捉即实现。只是细节上有所调整,比如:

  • 图片宽度和容器宽度刚好相等
  • 滚动容器距离滚动项目没有额外的空间,即滚动容器不使用 paddingscroll-padding ,滚动项目不使用 marginscroll-margin

关键代码:

.container {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
}

img {
    scroll-snap-align: center;
    scroll-snap-stop: always;
}

效果如下:

你可能已经发现了,在这个示例中我们看不到 scroll-paddingscroll-margin 的身影。在这个基础上,我们要实现全屏滚动效果就很简单了。把滚动容器和图片设置到和屏幕一样大小即可:

如果想把上面水平全屏滚动更换成垂直全屏滚动效果,只需要将滚动容器中的 scroll-snap-type: x mandatory;overflow-y 设置为 auto。关键代码如下:

.container {
    display: flex;
    flex-direction: column;

    overflow-x: hidden;
    overflow-y: auto;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: y mandatory;
}

img {
    scroll-snap-align: center;
    scroll-snap-stop: always;
}

效果如下:

2D 图片网格

前面示例向大家演示的都只是在一个方向滚动,要么是水平滚动,要么是垂直滚动。其实我们可以使水平和滚动两个方向同时滚动。比如:

.container {
    scroll-snap-type: both mandatory;
}

为此我们可以用其来创建 2D 网格图片的滚动效果。关键代码如下:

.container {
    scroll-behavior: smooth;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: both mandatory;
    scroll-padding: 20px;
}

img {
    scroll-snap-align: start;
    scroll-snap-stop: always;
}

效果如下:

iOS日期时间选择器

有了 CSS 滚动捕捉器的特性,我们可以在多个滚动容器中使用该特生来实现 iOS 日期时间选择器效果:

实现日期时间选择器的效果其实很简单,需要三个滚动容器,并使用相应的滚动特性即可。先来看 HTML:

<div class="picker">
    <span class="dates" title="DAY">
        <date>Fri 24 Jul</date>
        <!-- ... -->
        <date>Thu 14 Aug</date>
    </span>
    <span class="hours" title="HOUR">
        <time>0</time>
        <!-- ... -->
        <time>23</time>
    </span>
    <span class="minutes" title="MIN">
        <time>0</time>
        <!-- ... -->
        <time>59</time>
    </span>
</div>

分别在 .dates.hours.minutes 中创建三个滚动容器:

CSS 代码很简单,只需要在在这三个滚动容器上设置 scroll-snap-type 设置值为 y mandatory,然后在滚动项目中使用 scroll-snap-align: center 让每个滚动项目在滚动容器中居中显示。最终关键的代码如下:

.picker > * {
    overflow-y: auto;
    overscroll-behavior-y: contain;
    scroll-snap-type: y mandatory;
}

.picker > * > * {
    scroll-snap-align: center;
}

最终效果如下:

该示例来自于 Codepen 上 @Adam Argyle 写的Demo: Scroll Snap Date Time Picker

注意,这个示例中还使用了 CSS 的逻辑属性。另外,CSS 滚动特性中指定滚动轴除了使用 xy 之外还可以使用 inlineblock。比如示例中的 y mandatory 可以换成 inline mandatory

.picker > * {
    scroll-snap-type: inline mandatory;
}

iOS 列表左滑显示按钮的交互效果

上图的效果是截取于 iOS 中短信列表向左滑动显示按钮的交互效果,而且这样的交互效果到目前来说也是一种主流的交互效果。在这要告诉大家的是,我们可以使用纯 CSS 就能实现该效果。 其中 @张鑫旭 老师就使用 CSS 滚动捕捉实现微信列表左滑显示按钮的交互效果。这里我们就使用该特性来模拟实现上图的效果。

<!-- HTML -->
<div class="container">
    <ul class="snap__container--y">
        <li class="snap__container">
            <div class="media">
                <div class="media__object">
                    <img src="https://picsum.photos/180/180?random=1" alt="" />
                </div>
                <div class="media__body">
                    <div class="media__heading">
                        <h4 class="media__title">106575360560</h4>
                        <time>14:32</time>
                    </div>
                    <div class="media__content">
                        <p>在赣州过大年,大年三...</p>
                    </div>
                </div>
            </div>
            <button class="button button__bell">
                <svg t="1618147439338" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2522" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
                    <path d="M832 720..."></path>
                </svg>
            </button>
            <button class="button button__delete">
                <svg t="1618147388483" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1112" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
                    <path d="M133.521504 339.666..."></path>
                </svg>
            </button>
            <s class="space"></s>
        </li>
    </ul>
</div>

/* CSS */
.snap__container--y {
    height: 100%;
    overflow-x: hidden;
    overflow-x: auto;
    scroll-snap-type: y mandatory;
    overscroll-behavior-y: contain;
}

.snap__container--y li {
    scroll-snap-align: start;
}

.snap__container {
    overflow-y: hidden;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
}

.snap__container .space {
    scroll-snap-align: end;
}

.snap__container .media {
    scroll-snap-align: start;
}

效果如下:

在这个示例中,我们有两个滚动容器,一个是列表项的容器(.snap__container--y),它有一个垂直滚动的效果,另一个是列表项(每个列表项向左滑动),即 snap__container

在对应的滚动容器上设置相应的scroll-snap-type的值,并且在滚动项上设置scroll-snap-align的值,这样就可以得到上面示例的效果。

模拟 Instagram Stories 交互效果

特别声明,这个示例的效果来自于 @Adam Argyle 的 《Building a Stories component》一文。

具体的效果如下:

上面视频演示的效果被称为故事组件(Stories),在Snapchat Stories 和 Instagram Stories 都类似于这样的效果。对于这样的交互效果着自己的 UX 术语。 Stories 通常只是一种只用于移动的、以点击为中心的模式,用于浏览多个订阅。比如,在 Instagram 上,用户打开一个朋友的故事(Stories),然后浏览其中的图片,他们一般会同时对很多朋友进行这样的操作。通过点击设备的右侧,用户就会提前跳转到该朋友的下一个故事。通过向右滑动,用户就会向前跳转到不同的朋友那里。故事组件与幻灯片组件有点类似,但允许浏览一个多维数组,而不是单维数组。就好像每个转盘里面都有一个转盘一样。

刚才提到过,这个 Stories 组件效果其实有两种交互行为:点击设备的右侧向右滑动 。 众所周知, 很难用纯 CSS 来实现点击效果(它没有点击事件),但有了 CSS 滚动捕捉,就可以更好的优化向右滑动的效果。也就是说,上面视频展示的效果,实现向右滑动时,就有 CSS 滚动捕捉的相关特性。我们来看看怎么实现这样的一个效果。

<!-- HTML -->
<div class="stories">
    <section class="user">
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=1);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=2);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=3);"></article>
    </section>
    <section class="user">
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=12);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=22);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=32);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=42);"></article>
    </section>
    <section class="user">
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=6412);"></article>
    </section>
    <section class="user">
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=412);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=422);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=432);"></article>
        <article class="story" style="--bg: url(https://picsum.photos/480/841?random=442);"></article>
    </section>
</div>

用户向右滑动时,会进入不同的朋友故事中。简单地说,.stories 是一个水平滚动容器,可以通过 CSS 滚动捕捉特性让水平滚动体验更好:

.stories {
    width: 100vw;
    height: 100vh;
    box-shadow: 0 5px 2.5px hsla(200, 95%, 3%, 0.037),
        0 12px 6.5px hsla(200, 95%, 3%, 0.053),
        0 22.5px 13px hsla(200, 95%, 3%, 0.065),
        0 40.2px 24px hsla(200, 95%, 3%, 0.077),
        0 75.2px 44px hsla(200, 95%, 3%, 0.093),
        0 180px 80px hsla(200, 95%, 3%, 0.13);

    display: grid;
    grid: 1fr / auto-flow 100%;
    grid-gap: 1ch;
    gap: 1ch;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior: contain;
    touch-action: pan-x;
}

.user {
    /* outer */
    scroll-snap-align: start;
    scroll-snap-stop: always;

    /* inner */
    display: grid;
    grid: [story] 1fr / [story] 1fr;
}

CSS 滚动捕捉相关的属性主要有:

.stories {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior: contain;
    touch-action: pan-x;
}

.user {
    scroll-snap-align: start;
    scroll-snap-stop: always;
}

这个很前面全屏水平滚动捕捉效果是相似的。对于点击事件,那就需要借助相应的JavaScript代码了。最终就可以看到下面这个效果:

注意,示例中使用了 CSS 媒体查询相关的特性,你将在不同的终端下看到不同的效果:

小结

这篇文章主要介绍了 CSS 滚动捕捉这个特性,这个特性其实有一些年头了,到目前为止,该特性已经得到了主流浏览器的支持。在滚动容器和滚动项目中运用滚动捕捉相关的属性可以帮助我们改善用户的体验。甚至可以实现一些特殊的效果,比如使用 CSS 来模拟一些特殊的效果,甚至可以取替 JavaScript。