主要需求
- 实时显示当前页面的滚动进度百分比;
- 当鼠标悬停或滚动到底部时,显示一个向上的箭头,点击即可回到顶部。
基本结构
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