分享一个 CSS 交互技巧
CSS
可以做的事情越来越越多了。
我们经常会碰到这样的场景,很多网页会在右下角放一个固定入口,有可能是返回顶部,有可能广告,为了避免干扰,在页面滚动时,会把这些入口临时收起来,停止滚动后再出现,就像这样
![784cc2d40645b40bca1fdce349d63258.gif](https://img-blog.csdnimg.cn/img_convert/784cc2d40645b40bca1fdce349d63258.gif)
通常我们实现这样的效果会借助JS
的定时器,并且监听页面滚动,其实也不复杂,大概是这样实现
let timer;
window.addEventListener('scroll', function(){
// 是否在滚动
isScroll = true
timer && clearTimeout(timer)
timer = setTimeout(() => {
isScroll = false
}, 150)
})
现如今,CSS
也能实现这样的功能了,也就是可以检测页面是否在滚动,进一步,还能检测滚动的速度和方向,一起来看看吧~
一、CSS 检测原理
说起原理,其实和JS
是差不多的,都是有个类似于定时、延时的机制。那具体如何做呢?下面一步一步来介绍。
既然是滚动,所以离不开CSS
滚动驱动动画,这个之前有详细介绍,就不多描述了
比如,我们有这样一个可以滚动的页面
<body>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
...
</body>
简单修饰一下,效果是这样的
![0724a5c542cc8a84627c06b6fe6ddfb3.png](https://img-blog.csdnimg.cn/img_convert/0724a5c542cc8a84627c06b6fe6ddfb3.png)
然后我们需要用 CSS
检测滚动的进度,该如何做呢?没错,就是用 CSS
变量。
假设有一个这样的动画,--scroll-position
从0
变到100
,如下
@keyframes adjust-pos {
form {
--scroll-position: 0;
}
to {
--scroll-position: 100;
}
}
为了方便演示,我们可以把这个动画的变化过程显示在页面上
<div class="debug" hidden>
<div data-id="--scroll-position"></div>
</div>
这里利用CSS
计数器,直接用伪元素显示CSS
变量值
关于这个技巧,之前也在多篇文章中有应用到,非常实用
具体实现如下
:root {
animation: adjust-pos linear 3s;
}
.debug{
counter-reset: scroll-position calc(var(--scroll-position) * 1);
}
[data-id="--scroll-position"]::after {
content: "--scroll-position: " counter(scroll-position);
}
现在效果如下
![0d7573ef48cdd039ee5ac5602d5ce0df.gif](https://img-blog.csdnimg.cn/img_convert/0d7573ef48cdd039ee5ac5602d5ce0df.gif)
现在数字直接从0
变到了100
,没有中间的过程。
这是因为--scroll-position
是一个自定义变量,无法直接过渡。为了使这个变量也能像普通的过渡属性自动过渡,需要用到CSS @property
,也就是需要注册这个变量,让浏览器认为这是一个合法的 CSS
变量
@property --scroll-position {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
这段代码表示--scroll-position
是一个number
类型的数据,是一个合法的,可以过渡的类型,自然也就有动画了,效果如下
![40fe0802b1a9aedbc8da9e42cb4a1cf0.gif](https://img-blog.csdnimg.cn/img_convert/40fe0802b1a9aedbc8da9e42cb4a1cf0.gif)
然后我们加上滚动驱动动画,让这个动画跟随页面滚动
:root {
animation: adjust-pos 3s linear both;
animation-timeline: scroll();
}
效果如下,这样就能检测到滚动的具体位置了
当然,仅仅这样还是不够的,我们只知道了滚动的进度,并不知道滚动的状态。
为了知道滚动的速度,我们还需要另一个变量,假设是--scroll-position-delayed
@property --scroll-position-delayed {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@keyframes adjust-pos {
form {
--scroll-position: 0;
--scroll-position-delayed: 0;
}
to {
--scroll-position: 100;
--scroll-position-delayed: 100;
}
}
这样就有了两个变量在同时变化,效果如下
![6e42e8dc9986657200d3de98c313b0c7.gif](https://img-blog.csdnimg.cn/img_convert/6e42e8dc9986657200d3de98c313b0c7.gif)
同时变化没有什么意义,我们需要加一点延时,就像 JS
的定时器一样,这里我们可以直接通过transition
来实现
body{
margin: 0;
transition: --scroll-position-delayed 0.15s linear;
}
这里的0.15s
表示--scroll-position-delayed
在变化时需要0.15s
的时间,而--scroll-position
是瞬时完成的,所以就相当于--scroll-position-delayed
始终比--scroll-position
慢了0.15
秒,也就相当于延时了0.15s
,实际效果如下
是不是可以很清楚的看到下面的数值要比上面的慢一点?
有了这个时间差,我们就可以判断当前的滚动状态了。
比如我们可以用一个变量--scroll-velocity
来表示两者的差值
body{
--scroll-velocity: calc(var(--scroll-position) - var(--scroll-position-delayed));
}
效果如下
通过这个差值,我们是不是就能发现一些规律?
当
--scroll-velocity
为0
时,表示滚动停止,否则表示正在滚动中当
--scroll-velocity
大于0
时,表示滚动方向为下当
--scroll-velocity
小于0
时,表示滚动方向为上还可以从
--scroll-velocity
的绝对值上考虑,绝对值越大,表示滚动速度越快,反之则越慢
这就是CSS
检测的原理了,是不是还算简单呢?不过这还没完,还需要具体实现,比如怎么根据这个变量来匹配对应的样式
二、CSS 样式查询
回到文章开头,我们如何检测是否正在滚动呢,并且在滚动的时候隐藏右下角悬浮按钮呢?下面就来实现这样一个功能。
既然当--scroll-velocity
为0
时,就表示滚动停止,那我们是不是可以直接用样式查询来匹配呢?
@container - CSS: Cascading Style Sheets | MDN (mozilla.org)[1]
CSS 样式查询是容器查询的一部分,从名称也可以看出,它可以查询元素的样式,进而设置额外的样式。比如默认是隐藏的
.back{
transform: translateX(100%);
transition: .2s;
}
当匹配到--scroll-velocity:0
时,显示这个悬浮按钮,就可以这样来实现
@container style(--scroll-velocity: 0) {
.back{
transform: translateX(0);
}
}
效果如下
![b7db71f60cf26f48c6e249aa6ee36310.gif](https://img-blog.csdnimg.cn/img_convert/b7db71f60cf26f48c6e249aa6ee36310.gif)
好像并没有起效果?其实和前面的动画原理差不多,这是一个CSS
自定义变量,无法直接检测到变化的值。这里有一个解决方案,为了保证能够样式查询到,需要用@property
注册一下
@property --scroll-velocity {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
这样就能完美检测了
你也可以访问线上链接来查看实际效果
CSS scroll-speed (juejin.cn)[2]
是不是非常简单?
三、CSS 变量计算
除了使用样式查询外,我们还可以用CSS
变量的计算方式来实现。
什么意思呢?比如我们想知道是否在滚动,其实就是两个状态,那能不能用0
和1
来表示是否在滚动呢?那就需要做一点点变换了。
现在--scroll-velocity
表示差值,范围可能是-50~50
,那如何转换成1~0
呢,很简单,直接除以自身就行了, 比如-50/-50
和50/50
结果都是1
,有人会奇怪0/0
会不会无限大,没关系,这里CSS
计算的结果还是0
,实现如下
body{
--scroll-velocity: calc(var(--scroll-position) - var(--scroll-position-delayed));
--scroll-dynamic: calc(var(--scroll-velocity) / var(--scroll-velocity));
}
这样的话,我们就无需样式查询来改变右下角悬浮按钮的状态了,直接用--scroll-dynamic
来控制transform
.back{
transform: translateX(calc(var(--scroll-dynamic) * 100%));
}
看看效果
不一样的实现也能得到相同的效果,你也可以访问线上链接来查看实际效果
CSS scroll-dynamic - (juejin.cn)[3]
除了可以得到是否在滚动,还能计算得到滚动方向,比如1
表示向下,-1
表示向上,我们可以这样来计算
body{
--scroll-velocity: calc(var(--scroll-position) - var(--scroll-position-delayed));
--scroll-speed: max(var(--scroll-velocity), -1 * var(--scroll-velocity));
--scroll-direction: calc(var(--scroll-velocity) / var(--scroll-speed));
}
看似有点复杂,其实也不难理解。比如当前差值是-30
,那么,我们可以通过乘以-1
,然后取两者较大值,这样就能得到绝对值了。
![f2cd46ff4178ac51aff654272d968d14.png](https://img-blog.csdnimg.cn/img_convert/f2cd46ff4178ac51aff654272d968d14.png)
上面其实是个“偏方”,关于绝对值,其实已经有
CSS abs()
了,只是现在还没有支持,相信以后就能用上了
然后用原值除以这个绝对值,就能得到1
或者-1
了。
利用这个特性,我们可以在不同的方向改变箭头的指向
.back{
transform: scaleY(var(--scroll-direction));
}
效果如下
你也可以访问线上链接来查看实际效果
CSS scroll-dynamic (juejin.cn)[4]
四、更多有趣的案例
除了上面几个应用,我还找了几个有趣的案例。
比如下面这种虫洞效果,在水平或者垂直方向滚动时,会有明显的透视效果
https://codepen.io/bramus/pen/wvRqVBm
再比如这种上下滚动,可以看到不同方向上内容的倾斜角度不一样,而且滚动越快,倾斜越大
https://codepen.io/bramus/pen/OJrxBaL
还有一个比较简单实用的运动模糊滚动,也就是在滚动时,页面会有模糊的效果
https://codepen.io/bramus/pen/XWoREjv
五、最后总结一下
说了这么多,核心原理其实就这么几行,如下
@property --scroll-position {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --scroll-position-delayed {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@keyframes adjust-pos {
to {
--scroll-position: 100;
--scroll-position-delayed: 100;
}
}
:root {
animation: adjust-pos 3s linear both;
animation-timeline: scroll();
}
body{
transition: --scroll-position-delayed 0.15s linear;
--scroll-velocity: calc(var(--scroll-position) - var(--scroll-position-delayed));
--scroll-speed: max(var(--scroll-velocity), -1 * var(--scroll-velocity));
--scroll-direction: calc(var(--scroll-velocity) / var(--scroll-speed));
--scroll-dynamic: calc(var(--scroll-velocity) / var(--scroll-velocity));
}
其实原理还是比较好理解的,下面总结一下
首先用
CSS
自定义变量--scroll-position
实现一个从0
到100
的动画,注意需要用@property
注册然后用
CSS
滚动驱动动画将其关联,实现在滚动的时候变量自动变化接着再定义一个相同动画的变量
--scroll-position-delayed
,并设置过渡时间,这样就会比--scroll-position
变化的慢一点将这两个变量相减可以得到差值
--scroll-velocity
通过这个差值
--scroll-velocity
就能获得各种状态了当
--scroll-velocity
为0
时,表示滚动停止,否则表示正在滚动中当
--scroll-velocity
大于0
时,表示滚动方向为下当
--scroll-velocity
小于0
时,表示滚动方向为上还可以从
--scroll-velocity
的绝对值上考虑,绝对值越大,表示滚动速度越快,反之则越慢可以通过样式查询来匹配各种条件,不过需要用
@property
注册通过
CSS calc
和max
计算可以得到更多状态,比如滚动方向然后就是实际的运用了
至于兼容性,目前仅支持 chrome 115+
,只是依赖CSS
滚动驱动动画,这个神级属性还是要再等等,5
年后应该就可以愉快使用了。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发 ❤❤❤
[1]@container - CSS: Cascading Style Sheets | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/CSS/@container
[2]CSS scroll-speed (juejin.cn): https://code.juejin.cn/pen/7372834920302903346
[3]CSS scroll-dynamic - (juejin.cn): https://code.juejin.cn/pen/7372839994840973346
[4]CSS scroll-dynamic (juejin.cn): https://code.juejin.cn/pen/7372856142126874634