上一章

纯 CSS 实现滚动进度指示器

  • 37 阅读
  • 767 字

主要需求

1. 实时显示当前页面的滚动进度百分比;

2. 当鼠标悬停或滚动到底部时,显示一个向上的箭头,点击即可回到顶部。

基本结构

html
1
2
3
4
<a class="item" href="#">
  <span class="progress"></span>
  <span class="arrow"></span>
</a>
css
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 之间平滑地递增减:

css
1
2
3
4
5
@property --progress {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}

需要注意的是,自定义属性的值并不能够直接应用在伪元素的 content 上,需要使用计数器进行转换:

css
1
2
3
4
5
6
7
.progress {
  counter-reset: progress var(--progress);

  &::before {
    content: counter(progress);
  }
}

在页面未滚动到底部时显示进度,否则显示向上的箭头,这部分可以通过其 opacity 属性来实现。而两者的透明度正好呈现相反的趋势,因此我们可以在其各自的类下分别定义两个同名的变量:

css
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% 横空出世:

css
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 新特性强大而纯粹的魅力吧!然而当你刚开始调试的时候,你就会惊讶地发现,在页面高度小于窗口高度时,进度与箭头竟然同时显示了出来。

这是因为,滚动驱动动画在这种情况下甚至不会生效,它既不应用起始关键帧的样式,也不应用结束关键帧的样式。因此,我们需要设置它们的初始透明度以适应这种情况:

css
1
2
3
.progress, .arrow {
  opacity: var(--op0);
}

至于悬停时箭头的显示,则可以直接修改 opacity,或是在各自的类中统一两变量的值:

css
1
2
3
4
5
6
7
:hover > .progress {
  --op1: 0;
}

:hover > .arrow {
  --op1: 1;
}

最后,可以考虑在 html 选择器上加一个 scroll-behavior: smooth,这样一来就能让锚点跳转变得平滑了。

评论0

60FPS