前言
之前在掘金看到了CSS 滚动驱动动画终于正式支持了~学到了这一个CSS
最新的动画属性,大为震撼,虽然碍于兼容性等原因短期内应该不能应用与实际项目中,但是这么强而有力的动画属性肯定要先好好品尝一哈,于是就有了本篇文章内容,在此再次感谢XboxYan这位作者~
什么是滚动驱动动画~
在CSS中,滚动驱动动画是指当用户滚动页面时,触发特定的动画效果。这些效果可以包括元素在滚动过程中的移动、缩放、旋转等。
CSS提供了多种方式来实现滚动驱动动画。其中一种是使用CSS属性
scroll()
,它指定了可滚动元素与滚动轴的关系,以及为容器动画提供一个匿名的滚动进度时间线。通过在元素顶部和底部(或左边和右边)的滚动推进,滚动进度时间线可以转换为百分比。滚动开始时,动画的进度为0%,滚动结束时,进度为100%。如果指定的滚动轴不包含滚动条,也就是元素在滚动轴的方向不可滚动,那么时间线的进度将为0%。
感谢文心一言的回答,我们人工再总结一下,之前我们的CSS
动画触发事件包括但不限于
- 点击时更新class
- 进入页面时更新class
- 离开页面时更新class
- 数据回显时更新class
- 等等等...
这次的滚动驱动动画就是在浏览器滚动时让我可以更新元素的样式信息!!! 为什么不是更新class了是因为滚动事件是一个持续性不可预期的事件
为什么要有滚动驱动动画
上面说的滚动驱动动画这么重要难道之前没有这个属性时就做不到根据滚动事件处理动画了么,当然不是,之前我们可以采用监听元素的scroll
事件然后触发元素的样式改变。BUT!!!
现代浏览器的滚动驱动事件因为考虑到用户的操作流畅性,所以事件是异步传递的当触发滚动事件时,该事件被添加到事件队列中,但并不会立即执行。事件队列会按照先进先出(FIFO)的顺序执行任务,即先等待其他任务执行完毕后再执行滚动事件。这样可以避免滚动事件与其他事件之间的竞态条件和死锁问题,保持用户界面的响应。
这一点非常重要尤其是在移动设备中会非常明显比如小程序啊小程序 = =!
所以滚动驱动动画的出现也有一定的划时代意义,以前用JS
才能完成的效果,大部分都可以被替代。
Let's go !!
这是我们最终要实现的效果,一个黄色卡片跟随滚动渐变为粉色卡片!
实现这个效果前我们还是先来学习一下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
接受两个参数scroller
和axis
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-name
或view-timeline-name
)只能被设置为直接后代元素的受控时间轴(即通过在其上设置animation-timeline
,并将其值设置为时间轴名称)。这是时间轴的默认“范围”。
timeline-scope
属性给定在后代元素上定义的时间轴的名称,这会导致时间轴的范围增加到timeline-scope
属性设置的元素及其任何后代元素。换句话说,该元素及其任何后代元素现在可以使用该时间轴进行控制。
tips:使用timeline-scope
chrome版本需要 >= 116
总结一下定义其实timeline-scope
的核心作用其实相当于生成了一个作用域,在该作用域下的所有元素都可以使用指定animation-timeline
不论是不是滚动元素的子元素。
以这个Demo举例,如果我们要让滚动条不属于.scroller
的子元素同时又要根据.scroller
来触发滚动动画即可使用timeline-scope
可以看到.progress
现在和.scroller
是兄弟关系,但是因为.wrapper
声明了timeline-scope
导致作用域提升所以兄弟关系也可以触发滚动驱动效果!!
同样我们可以再再再进一步,设定了timeline-scope
的父级元素.wrapper
同样可以触发滚动驱动!
指定滚动范围
看到这我们已经学习了基础的animation-timeline: scroll()
语法,指定祖先滚动元素scroll-timeline
和指定非祖先滚动容器timeline-scope
,现在让我们回到文章开头的效果。
我们拆解一下效果,首先是黄色的卡片有一个吸顶效果,这个效果很简单通过position: sticky
即可完成。
如果我们这时候直接加上滚动驱动动画效果会是什么样子呢?
微妙就很微妙,效果确实实现了,但是我们理想中的情况是滚动到吸顶是过渡已经完成。
这就引出了我们另一个神器属性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%*
如图所示我们如果不修改animation-range
那默认得动画执行长度就是滚动容器得滚动距离,那要完成我们上图中得Demo
效果只需要配合position: sticky
+ animation-delay-end
即可!
结语
本篇只是简单得介绍了一下animation-timeline: scroll()
使用API,滚动动画得精妙之处还不止于此还有更好玩得animation-timeline: view()
视图滚动动画,让我们下期再见!!