主要需求
- 实时显示当前页面的滚动进度百分比;
- 当鼠标悬停或滚动到底部时,显示一个向上的箭头,点击即可回到顶部。
基本结构
1 2 3 4
<a class="item" href="#"> <span class="progress"></span> <span class="arrow">↑</span> </a>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
.item { display: block; position: relative; width: 36px; aspect-ratio: 1; } .progress, .arrow { position: absolute; inset: 0; line-height: 36px; text-align: center; } .progress { font-size: 14px; &::after { content: "%"; font-size: 11px; } }
实现过程
这次登场的依然是我们的滚动驱动动画,不同的是它将控制一个自定义属性,使它能够跟随窗口滚动进度在 0 - 99 之间平滑地递增减:
1 2 3 4 5
@property --progress { syntax: "<integer>"; initial-value: 0; inherits: false; }
需要注意的是,自定义属性的值并不能够直接应用在伪元素的 content
上,需要使用计数器进行转换:
1 2 3 4 5 6 7
.progress { counter-reset: progress var(--progress); &::before { content: counter(progress); } }
在页面未滚动到底部时显示进度,否则显示向上的箭头,这部分可以通过其 opacity
属性来实现。而两者的透明度正好呈现相反的趋势,因此我们可以在其各自的类下分别定义两个同名的变量:
1 2 3 4 5 6 7 8 9
.progress { --op0: 0; --op1: 1; } .arrow { --op0: 1; --op1: 0; }
这样一来,只要动画能够操控它们的 opacity
在两个变量之间切换,就能自然地实现交替显示的效果了。
问题在于,CSS 如何知道窗口会在什么时候滚动到底部呢?核心落在了动画的关键帧上。只要我们在窗口距底部至多 1px
位置插入一个关键帧,就能够做到 opacity
的突变而不是缓慢过渡了。对应地,这个关键帧的进度应该无限接近 100%
,于是 99.99999999999999%
横空出世:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
.progress, .arrow { animation: scroll-progress linear; animation-timeline: scroll(); } @keyframes scroll-progress { 0% { --progress: 0; opacity: var(--op1); } 99.99999999999999% { opacity: var(--op1); } 100% { --progress: 99; opacity: var(--op0); } }
当然实际上并不需要这么高的精度,调整到看着舒适的程度即可,除非你的网页高达 10 的 16 次方像素。
好了,现在开始尽情享受 CSS 新特性强大而纯粹的魅力吧!然而当你刚开始调试的时候,你就会惊讶地发现,在页面高度小于窗口高度时,进度与箭头竟然同时显示了出来。
这是因为,滚动驱动动画在这种情况下甚至不会生效,它既不应用起始关键帧的样式,也不应用结束关键帧的样式。因此,我们需要设置它们的初始透明度以适应这种情况:
1 2 3
.progress, .arrow { opacity: var(--op0); }
至于悬停时箭头的显示,则可以直接修改 opacity
,或是在各自的类中统一两变量的值:
1 2 3 4 5 6 7
:hover > .progress { --op1: 0; } :hover > .arrow { --op1: 1; }
最后,可以考虑在 html
选择器上加一个 scroll-behavior: smooth
,这样一来就能让锚点跳转变得平滑了。
评论0