CSS 滚动驱动动画-animation-timeline: scroll尝鲜

3,132 阅读9分钟

前言

之前在掘金看到了CSS 滚动驱动动画终于正式支持了~学到了这一个CSS最新的动画属性,大为震撼,虽然碍于兼容性等原因短期内应该不能应用与实际项目中,但是这么强而有力的动画属性肯定要先好好品尝一哈,于是就有了本篇文章内容,在此再次感谢XboxYan这位作者~

什么是滚动驱动动画~

在CSS中,滚动驱动动画是指当用户滚动页面时,触发特定的动画效果。这些效果可以包括元素在滚动过程中的移动、缩放、旋转等。

CSS提供了多种方式来实现滚动驱动动画。其中一种是使用CSS属性scroll(),它指定了可滚动元素与滚动轴的关系,以及为容器动画提供一个匿名的滚动进度时间线。通过在元素顶部和底部(或左边和右边)的滚动推进,滚动进度时间线可以转换为百分比。滚动开始时,动画的进度为0%,滚动结束时,进度为100%。

如果指定的滚动轴不包含滚动条,也就是元素在滚动轴的方向不可滚动,那么时间线的进度将为0%。

感谢文心一言的回答,我们人工再总结一下,之前我们的CSS动画触发事件包括但不限于

  1. 点击时更新class
  2. 进入页面时更新class
  3. 离开页面时更新class
  4. 数据回显时更新class
  5. 等等等...

这次的滚动驱动动画就是在浏览器滚动时让我可以更新元素的样式信息!!! 为什么不是更新class了是因为滚动事件是一个持续性不可预期的事件

为什么要有滚动驱动动画

上面说的滚动驱动动画这么重要难道之前没有这个属性时就做不到根据滚动事件处理动画了么,当然不是,之前我们可以采用监听元素的scroll事件然后触发元素的样式改变。BUT!!!

现代浏览器的滚动驱动事件因为考虑到用户的操作流畅性,所以事件是异步传递的当触发滚动事件时,该事件被添加到事件队列中,但并不会立即执行。事件队列会按照先进先出(FIFO)的顺序执行任务,即先等待其他任务执行完毕后再执行滚动事件。这样可以避免滚动事件与其他事件之间的竞态条件和死锁问题,保持用户界面的响应。

这一点非常重要尤其是在移动设备中会非常明显比如小程序小程序 = =!

所以滚动驱动动画的出现也有一定的划时代意义,以前用JS才能完成的效果,大部分都可以被替代。

Let's go !!

效果.gif

这是我们最终要实现的效果,一个黄色卡片跟随滚动渐变为粉色卡片!

实现这个效果前我们还是先来学习一下scroll基础语法


/* Function with no parameters set */
animation-timeline: scroll();

/* Values for selecting the scroller element */
animation-timeline: scroll(nearest); /* Default */
animation-timeline: scroll(root);
animation-timeline: scroll(self);

/* Values for selecting the axis */
animation-timeline: scroll(block); /* Default */
animation-timeline: scroll(inline);
animation-timeline: scroll(y);
animation-timeline: scroll(x);

/* Examples that specify scroller and axis */
animation-timeline: scroll(block nearest); /* Default */
animation-timeline: scroll(inline root);
animation-timeline: scroll(x self);


<scroll()> =  scroll( [ <scroller> || <axis> ]? )  

能看到scroll接受两个参数scrolleraxis

scroller可以理解为监听哪一个容器的滚动事件

  • nearest 当前元素具有滚动条的最近祖先元素,要注意的是只要最近祖先元素支持滚动是不受axis属性控制的,比如scroll(nearest y),但是最近的滚动祖先元素是x轴的则动画失效。
  • root 根据root滚动触发
  • self 当前自身元素的滚动触发

axis 监听的滚动方向很好理解

  • block 对于水平书写模式比如中文,英文来说这个属性就是代表着y轴方向,反之则代表着x轴方向
  • inline 对于水平书写模式比如中文,英文来说这个属性就是代表着x轴方向,反之则代表着y轴方向
  • y 垂直方向的滚动
  • x 水平方向的滚动

说了这么多,快把实战Demo端上来吧!chrome版本需要 >= 115

可以看到这是一个滚动进度动画效果,这在以前都是需要js来完成的现在竟然通过一句代码就完成了,cool!!


.progress {  
    ...
    transform-origin: 0 50%;  
    animation: scaleProgress auto linear;  
    animation-timeline: scroll();  
}  
  
@keyframes scaleProgress {  
    0% {  
        transform: scaleX(0);  
    }  
    100% {  
        transform: scaleX(1);  
    }  
}

从核心代码看我们使用了一个缩放来实现效果,但是和以前动画不同的是我们的触发变成了浏览器根据滚动距离自动触发。再拆解一下在这个文档流中animation-timeline: scroll() 等同于 animation-timeline: scroll(nearest y),同时因为progress的最近滚动祖先元素就是窗口也相等于animation-timeline: scroll(root y)

上面是关于y轴的进度条动画我们再来一个x轴方向的

这次我们看到我们用了距离最近的nearest以及x来完成这个效果,这个时候可能就会有小伙伴觉得这个属性如果只能用来监听root或者nearest的滚动其实也没什么大不了,NONONO重头戏这次要刚开始。

指定祖先滚动容器

上面提供的两个Demo可以看到我们都是基于nearest也就是最近的祖先滚动容器来完成的,但是有时我们的定位属性比如absolute或者fixed会扰乱正常的文档流导致nearest失效,这时候就需要scroll-timeline出场了!

scroll-timeline 在滚动容器上声明时间轴的名称和轴的方向,时间轴名称必须以两个破折号作为前缀(类似于自定义属性),这可确保它不会与其他属性值冲突。

/* two values: one each for scroll-timeline-name and scroll-timeline-axis */
scroll-timeline: --custom_name_for_timeline block;
scroll-timeline: --custom_name_for_timeline inline;
scroll-timeline: --custom_name_for_timeline y;
scroll-timeline: --custom_name_for_timeline x;
scroll-timeline: none block;
scroll-timeline: none inline;
scroll-timeline: none y;
scroll-timeline: none x;

/* one value: scroll-timeline-name */
scroll-timeline: none;
scroll-timeline: --custom_name_for_timeline;

简单的说就是通过scroll-timeline声明了一个自定义属性名称,然后使用的时候需要使用animation-timeline,比如如下布局的情况


<div class="wrapper">  
    <div class="scroller">  
        <h1>Scroll this</h1>  
        <p>  
        ...
        </p>  
        <p>  
        ...
        </p>  
        <p>  
        ...
        </p>  
        <p>  
        ...
        </p>  
        <p>  
        ...
        </p>  
        <div class="progress"></div>  
    </div>  
</div>


.wrapper {  
    position: relative;
    ...
}  
  
.scroller {  
    width: 300px;  
    height: 300px;  
    overflow: scroll;   
}  
  
.progress {  
    position: absolute;  
    animation-timeline: scroll();
}

滚动元素为.scroller,但是因为我们的.progress绝对定位导致我们的最近祖先滚动元素变为了.wrapper,这时候只需要在.scroller中定义一个scroll-timeline强制指定滚动元素为.scroller即可解决,效果如下

这就够了?NONONO,让我们更进一步!

指定非祖先滚动容器

上面的几个Demo哪怕是使用了scroll-timeline其实也都是基于祖先滚动容器,但是现实情况是我们的布局情况多种多样如果只支持祖先滚动容器那还是万万不够的,所以我们迎来了我们的一个新属性timeline-scope

timeline-scope属性修改了命名的动画时间轴的范围。

默认情况下,命名的时间轴(即使用scroll-timeline-nameview-timeline-name)只能被设置为直接后代元素的受控时间轴(即通过在其上设置animation-timeline,并将其值设置为时间轴名称)。这是时间轴的默认“范围”。

timeline-scope属性给定在后代元素上定义的时间轴的名称,这会导致时间轴的范围增加到timeline-scope属性设置的元素及其任何后代元素。换句话说,该元素及其任何后代元素现在可以使用该时间轴进行控制。

tips:使用timeline-scopechrome版本需要 >= 116

总结一下定义其实timeline-scope的核心作用其实相当于生成了一个作用域,在该作用域下的所有元素都可以使用指定animation-timeline不论是不是滚动元素的子元素。

scroll-timeline-01.png

以这个Demo举例,如果我们要让滚动条不属于.scroller的子元素同时又要根据.scroller来触发滚动动画即可使用timeline-scope

可以看到.progress现在和.scroller是兄弟关系,但是因为.wrapper声明了timeline-scope导致作用域提升所以兄弟关系也可以触发滚动驱动效果!!

同样我们可以再再再进一步,设定了timeline-scope的父级元素.wrapper同样可以触发滚动驱动!

指定滚动范围

看到这我们已经学习了基础的animation-timeline: scroll()语法,指定祖先滚动元素scroll-timeline指定非祖先滚动容器timeline-scope,现在让我们回到文章开头的效果。

效果.gif

我们拆解一下效果,首先是黄色的卡片有一个吸顶效果,这个效果很简单通过position: sticky即可完成。

如果我们这时候直接加上滚动驱动动画效果会是什么样子呢?

微妙就很微妙,效果确实实现了,但是我们理想中的情况是滚动到吸顶是过渡已经完成。

效果图合并.png

这就引出了我们另一个神器属性animation-range,顾名思义肯定是指定滚动范围的,使用起来也是非常简单难得还在后面

animation-range = 
  [ <'animation-delay-start'> <'animation-delay-end'>? | <timeline-range-name> ]# 

timeline-range-name我们留到后面再讲,本期内容我们主要关注animation-delay-start以及animation-delay-end,我们目前所写得所有Demo都没有设置过animation-range也就是说滚动得距离一直是0% - 100%*

效果图合并03.png

如图所示我们如果不修改animation-range那默认得动画执行长度就是滚动容器得滚动距离,那要完成我们上图中得Demo效果只需要配合position: sticky + animation-delay-end 即可!

效果合并图04.png

结语

本篇只是简单得介绍了一下animation-timeline: scroll() 使用API,滚动动画得精妙之处还不止于此还有更好玩得animation-timeline: view()视图滚动动画,让我们下期再见!!

感谢

XboxYan

Scroll progress animations in CSS | MDN Blog

scroll() - CSS: Cascading Style Sheets | MDN