百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

CSS 滚动驱动动画终于正式支持了~

yuyutoo 2024-10-12 00:21 2 浏览 0 评论

在最新的Chrome 115中,令人无比期待的CSS 滚动驱动动画(CSS scroll-driven animations)终于正式支持了~有了它,几乎以前任何需要JS监听滚动的交互都可以纯 CSS 实现了,就是这么强大,一起了解一下吧

一、快速入门 CSS 滚动驱动动画

直接介绍 API 可能不太感兴趣,这里先通过一个最直观的例子感受一下。

下面是一个页面进度指示器,进度随着页面的滚动而变化

页面很简单,很多内容和一个进度条

<div class="progress"></div>
...很多内容

进度条是fixed定位

.progress{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 10px;
  background-color: #F44336;
  transform-origin: 0 50%;
}

然后给这个进度条添加一个动画,表示进度从0100%

@keyframes grow-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

接着给这个进度条绑定动画

.progress{
  animation: grow-progress 3s linear;
}

刷新页面,可以看到进度条在3s内从0增长到了100%

显然这种动画没什么意义,我们需要在滚动时才触发,并且滚动多少,动画就播放多少。

注意:动画时长不能为0,因为为0表示动画不执行,所以必须写上一个任意非零时间,或者直接为auto

最后,加上最核心的一段,也就是今天的主角animation-timeline

.progress{
  /*...*/
  animation-timeline: scroll();
}

这样进度条就乖乖的跟随页面滚动而变化了(注意Chrome 115+)

完整代码可以访问:

  • CSS scroll-driven-animations-back (codepen.io)

是不是非常简单?是不是非常神奇?如果你感兴趣,可以接着往下看

二、CSS 滚动驱动动画

大家可能知道,传统 JS 监听滚动有一些问题,如下

  • 现代浏览器在单独的进程上执行滚动,因此只能异步传递滚动事件。
  • 由于是异步传递,因此主线程动画容易出现卡顿

因此,为了解决滚动卡顿的问题,CSS 滚动驱动动画应运而生。那么,什么是 CSS 滚动驱动动画?

默认情况下,动画是随着时间的流逝而播放的。

CSS 滚动驱动动画指的是将动画的执行过程由页面滚动进行接管,也就是这种情况下,动画只会跟随页面滚动的变化而变化,也就是滚动多少,动画就执行多少,时间不再起作用

如何改变动画的时间线呢? 那就需要用到这个核心概念了:animation-timeline,表示动画时间线(或者叫时间轴),用于控制 CSS 动画进度的时间线,是必不可少的一个属性。

默认值是auto,也是就传统的时间线。下面是它一些关键词

/* 关键词 */
animation-timeline: none;
animation-timeline: auto;
/* 命名时间线 */
animation-timeline: --timeline_name;

/* 滚动时间线 */
animation-timeline: scroll();
animation-timeline: scroll(scroller axis);

/* 视图时间线 */
animation-timeline: view();
animation-timeline: view(axis inset);

是不是有点混乱?不要慌,实际滚动场景千千万,这里可以分为两大类:一类是滚动进度时间线,也就是上面的关键词scroll(),还有一类是视图进度时间线,也就是关键词view()。

两者形式对应两种不同的应用场景,这是什么意思呢?下面一一介绍

三. CSS 滚动进度时间线

滚动进度时间线(scroll progress timeline)。表示页面或者容器滚动,将滚动进度映射到动画进度上。起始滚动位置代表 0% 进度,结束滚动位置代表 100% 进度,下面是一个可视化演示

在上面的进度条例子中,我们用到的就是scroll progress timeline,因为我们监听的就是页面的滚动

cssanimation-timeline: scroll();


这里的scroll()是一个简写,可以传递两个参数,分别是<scroller>和<axis>

<scroller>表示滚动容器,支持以下几个关键值

  • nearest:使用最近的祖先滚动容器*(默认)*
  • root:使用文档视口作为滚动容器。
  • self:使用元素本身作为滚动容器。

<axios>表示滚动方向,支持以下几个关键值

  • block:滚动容器的块级轴方向*(默认)*。
  • inline:滚动容器内联轴方向。
  • y:滚动容器沿 y 轴方向。
  • x:滚动容器沿 x 轴方向。
/* 无参数 */
animation-timeline: scroll();

/* 设置滚动容器 */
animation-timeline: scroll(nearest); /* 默认 */
animation-timeline: scroll(root);
animation-timeline: scroll(self);

/* 设置滚动方向 */
animation-timeline: scroll(block); /* 默认 */
animation-timeline: scroll(inline);
animation-timeline: scroll(y);
animation-timeline: scroll(x);

/* 同时设置 */
animation-timeline: scroll(block nearest); /* 默认 */
animation-timeline: scroll(inline root);
animation-timeline: scroll(x self);

因此,如果需要监听横向滚动,可以这样

animation-timeline: scroll(inline);

不知大家发现没,前面的滚动容器只有三个关键词,并不能通过#id方式任意指定滚动容器,真的能满足所有需求吗?

当然不行!有时候结构稍微复杂一点,自动查找就不适用了,并且这里的最近祖先滚动容器还受到绝对定位的影响,因此,我们还需要手动去指定滚动容器。

官方的解决方式是创建一个带有名称的时间线,具体做法是,在滚动容器上添加一个属性scroll-timeline-name,这个属性值必须以--开头,就像 CSS 变量一样,还可以通过scroll-timeline-axis设置滚动方向,此时的animation-timeline就不用默认的scroll()了,而是改用前面设置的变量,示意如下

@keyframes animate-it { … }

/*滚动容器*/
.scroller {
  scroll-timeline-name: --my-scroller;
  scroll-timeline-axis: inline;
}

.scroller .subject {
  animation: animate-it linear;
  animation-timeline: --my-scroller;
}

这里的scroll-timeline-axisscroll-timeline-name还可以简写成一个属性scroll-timeline

scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
/**可简写为**/
scroll-timeline: --my-scroller inline;

下面来看一个横向滚动的例子,刚好可以把上面的几个新概念都用上。

布局还是类似,只是放在了一个可以横向滚动的容器中

<main>
  <div class="progress"></div>
  ...很多内容...
</main>

main设置横向滚动,.progress设置fixed定位,还有动画和上个例子一样

main{
  display: flex;
  overflow: scroll;
}
.progress{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 10px;
  background-color: #F44336;
  transform-origin: 0 50%;
  animation:grow-progress 3s linear;
}
@keyframes grow-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

由于这里main才是滚动容器,并不是页面,而.progressfixed定位,如果直接用scroll(nearest)获取到的就是页面根容器,并不是main,所以这里需要用命名scroll-timeline,实现如下

main{
  /**/
  scroll-timeline: --scrollcontainer inline;
}
.progress{
  /**/
  animation-timeline: --scrollcontainer;
}

这样就可以将横向滚动进度一一映射到动画上了,而且不受结构限制,非常自由

完整代码可以查看:

  • CSS scroll-driven-animations-inline (codepen.io)

四、CSS 视图进度时间线

视图进度时间线(view progress timeline)。这个名字有些难以理解,其实表示的是一个元素出现在页面视野范围内的进度,也就是关注的是元素自身位置。元素刚刚出现之前代表 0% 进度,元素完全离开之后代表 100% 进度,下面是一个可视化演示


这个概念非常像JS中的Intersection_Observer_API,也就交叉观察者,可以监测到元素在可视区的情况,因此,在这种场景中,无需关注滚动容器是哪个,只用处理自身就行了。

和前面的scroll progress time语法类似,也有一个快捷语法

animation-timeline: view()

由于无需关注滚动容器,所以它的参数也不一样,分别是<axios>和<inset>

<axios>表示滚动方向,支持以下几个关键值

  • block:滚动容器的块级轴方向*(默认)*。
  • inline:滚动容器内联轴方向。
  • y:滚动容器沿 y 轴方向。
  • x:滚动容器沿 x 轴方向。

<inset>表示调整元素的视区范围,有点类似scroll-padding,支持两个值,表示开始和结束两个范围。

animation-timeline: view(auto); /* 默认值 */
animation-timeline: view(20%);
animation-timeline: view(200px);
animation-timeline: view(20% 40%);
animation-timeline: view(20% 200px);
animation-timeline: view(100px 200px);
animation-timeline: view(auto 200px);

这里的<inset>还可以用view-timeline-inset单独来表示,不过需要注意的是,这种用法要使用命名的view progress time,如下

scroll-timeline: --my-scroller block;
view-timeline-inset: 20% 200px;
animation-timeline: --my-scroller;

下面来看一个例子,有一个列表

<div>欢</div>
<div>迎</div>
<div>关</div>
<div>注</div>
<div>前</div>
<div>端</div>
<div>侦</div>
...

简单修饰后效果如下

现在,我们添加一个淡入和缩放的动画

@keyframes appear {
  from {
    opacity: 0;
    transform: scaleX(0);
  }
  to {
    opacity: 1;
    transform: scaleX(1);
  }
}

然后通过animation-time绑定在每个元素上,因为我们想做一个元素进入的动画,所以要用到view progress timeline

div{
  /**/
  animation: appear 1s linear both;
  animation-timeline: view();
}

可以得到这样的效果

效果是出来了,不过好像有点太过了,太夸张了,可以看到,每个元素在滚动出现到离开的过程中都完整的执行了我们定义的动画。那么,有没有办法让这个范围变小一点呢?默认的范围如下

当然也是可以的,这里就需要用到view的第二个参数<inset>了,比如设置40% 0表示调整视区范围,相当于将滚动容器上边距减少了 40%,当滚动到视区上面40%的时候就完成了动画(默认是滚动到0%,也就是完全离开的时候)

div{
  /**/
  animation-timeline: view(40% 0);
}

还可以更加激进一点,设置成100%,相当于元素一旦完全进入,动画就执行完成了,这样元素出现动画会更加和谐

div{
  /**/
  animation-timeline: view(100% 0);
}

此时的动画范围就更小了,如下

效果如下,是不是感觉没那么夸张了呢

完整代码可以查看:

  • CSS scroll-driven-animations-view (codepen.io)

五、CSS 动画范围区间

默认情况下,动画会根据滚动区间范围一一映射,就比如第一个滚动指示器的例子,滚动多少,指示器的进度就走多少。

但有时候,我们并不需要完整的区间,比如这个例子,右下角的返回顶部按钮

像这种情况下,我们其实只需要前面滚动一定距离就可以让返回按钮完全出现了,对应关系应该是这样

那么,如何截取一定的滚动区间呢?这就要涉及一个新的属性,叫做animation-range,也就是“动画范围”。

这里也要分两种场景,也就是前面提到的滚动进度时间线视图进度时间线

1. 滚动进度时间线

首先来看scroll()场景,由于只是滚动容器的监听,因此比较简单,直接设置范围就行了

animation-range: normal; /* 等价于 normal normal */
animation-range: 20%; /* 等价于 20% normal */
animation-range: 100px; /* 等价于 100px normal */

比如上面这个返回顶部的例子,动画其实很简单,就是一个向上的位移动画

@keyframes back-progress {
  from { transform: translateY(150%); }
  to { transform: translateY(0%); }
}

如果仅仅添加一个滚动时间轴

.back{
  /**/
  animation: back-progress 1s linear forwards;
  animation-timeline: scroll();
}

那么,这个返回按钮就像滚动进度条那样,慢慢的出来,直到滚动到最底部才完全出来,效果如下

这时只需要在[0, 固定距离]的范围内出现就好了,表示只在这个区间范围内触发动画,关键代码如下

.back{
  /**/
  animation: back-progress 1s linear forwards;
  animation-timeline: scroll();
  animation-range: 0 100px;
}

这样就实现了滚动100px时自动出现的返回顶部按钮,100px后按钮会一直显示

完整代码可以查看:

  • CSS scroll-driven-animations-back (codepen.io)

还有一个头部吸顶的例子,原理也是类似的,如下

头部是一个高度和字号不断变小的动画,然后需要设置一下animation-range,关键实现如下

@keyframes header {
  to { 
    height: 60px;
    font-size: 30px;
  }
}
.header{
  /**/
  animation: header 1s linear forwards;
  animation-timeline: scroll();
  animation-range: 0 calc(100vh - 60px);
}

完整代码可以查看:

  • CSS scroll-driven-animations-header (codepen.io)

2. 视图进度时间线

再来看看view()场景。由于涉及到元素和可视区域的交叉,情况稍微复杂一些,如下

animation-range: cover; /* 等价于 cover 0% cover 100% */
animation-range: contain; /* 等价于 contain 0% contain 100% */
animation-range: cover 20%; /* 等价于 cover 20% cover 100% */
animation-range: contain 100px; /* 等价于 contain 100px cover 100% */


animation-range: normal 25%;
animation-range: 25% normal;
animation-range: 25% 50%;
animation-range: entry exit; /* 等价于 entry 0% exit 100% */
animation-range: cover cover 200px; /* 等价于 cover 0% cover 200px */
animation-range: entry 10% exit; /* 等价于 entry 10% exit 100% */
animation-range: 10% exit 90%;
animation-range: entry 10% 90%;

有以下关键词

  • cover:元素首次开始进入滚动容器可见范围(0%)到完全离开的过程(100% ),也就是元素只需要和可视范围有交集(默认)
  • contain:元素完全进入滚动容器可见范围(0%)到刚好要离开的过程(100% ),也就是元素必须完全在可见范围才会触发
  • entry:元素进入滚动容器可见范围的过程,刚进入是 0%,完全进入是 100%
  • exit:元素离开滚动容器可见范围的过程,刚离开是 0%,完全离开是 100%
  • entry-crossing:和entry比较类似,暂时没有发现明显差异
  • exit-crossing:和exit比较类似,暂时没有发现明显差异

下面做了一个示意图,表示各自的范围区间

如果还是不太清楚,可以用下面这个工具去对比各自的差异

比如前面的列表进入时的动画,之前是用view(100% 0)实现的,大家有没有发现,这个效果其实和entry的示意效果一样的?

如果用animation-range就很好理解了,这里需要进入动画,所以可以直接用entry

div{
  animation: appear 1s linear forwords;
  animation-timeline: view();
  animation-range: entry; /*只在进入过程中生效*/
}

同样可以实现相同的效果。

除此之外还可以同时设置进入和离开两种动画,这就需要定义两个动画,然后分别给两个动画定义动画区间,关键实现如下

div{
  animation: appear 1s linear forwards, 
            disappear 1s linear forwards;
  animation-timeline: view();
  animation-range: entry,exit; /*进入过程执行appear,离开过程执行disappear*/
}

/*出现动画*/
@keyframes appear {
  0% {
    opacity: 0;
    transform: scaleX(0);
  }

  100% {
    opacity: 1;
    transform: scaleX(1);
  }
}
/*离开*/
@keyframes disappear {
  100% {
    opacity: 0;
    transform: scaleX(0);
  }

  0% {
    opacity: 1;
    transform: scaleX(1);
  }
}

这样就得到一个进入和离开均存在动画的滚动列表

完整代码可以查看:

  • CSS scroll-driven-animations-range (codepen.io)

另外,还可以将animation-range合并到同一个动画中,在关键帧前面加上entry这些关键词,这样就无需指定animation-range中了,示意代码如下

div{
  animation: animate-in-and-out 1s linear forwards;
  animation-timeline: view();
}

@keyframes animate-in-and-out {
  entry 0% {
    opacity: 0;
    transform: scaleX(0);
  }

  entry 100% {
    opacity: 1;
    transform: scaleX(1);
  }
  exit 100% {
    opacity: 0;
    transform: scaleX(0);
  }

  exit 0% {
    opacity: 1;
    transform: scaleX(1);
  }
}

六、更多有趣的案例

除了以上一些案例外,CSS 滚动驱动动画还能做更多有趣的事情,这里推荐一个网站

比如这个 Cover Flow 效果

还有下面的卡片堆叠效果

七、用一张图总结一下

总的来说,CSS 滚动驱动动画为以后的交互带来了无限可能,下面用一张图总结一下



原文链接:https://juejin.cn/post/7259026189904805944

相关推荐

jQuery VS AngularJS 你更钟爱哪个?

在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...

Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0

在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...

如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例

要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...

编程技巧:Jquery实时验证,指定长度的「负小数」

为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...

一篇文章带你用jquery mobile设计颜色拾取器

【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...

编程技巧:Jquery实时验证,指定长度的「正小数」

为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...

jquery.validate检查数组全部验证

问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...

Vue进阶(幺叁肆):npm查看包版本信息

第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...

layui中使用lay-verify进行条件校验

一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...

jQuery是什么?如何使用? jquery是什么功能组件

jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...

django框架的表单form的理解和用法-9

表单呈现...

jquery对上传文件的检测判断 jquery实现文件上传

总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...

Nodejs之MEAN栈开发(四)-- form验证及图片上传

这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...

大数据开发基础之JAVA jquery 大数据java实战

上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...

推荐四个开源的jQuery可视化表单设计器

jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...

取消回复欢迎 发表评论: