上一章

基于滚动驱动动画的滚动条

  • 23 阅读
  • 584 字
下一章

目前 scroll-driven animations 的浏览器兼容性不是很好,故编写一个 demo 暂存在这个页面。利用 JS 实现的滚动条要比原生消耗更多性能,在反复调试了一下午后还是选择再观望一阵子。

滑块高度

首先,对滑块的高度进行计算。设滑块高度为 t,窗口高度为 w,文档高度为 d

  • w >= d 时,t = w

  • w < d 时,若将滚动条区域看作文档,滑块看作窗口,则可以得到比例关系 t / w = w / d

    t = w ^ 2 / d

js
1
2
3
4
5
6
7
8
9
10
11
import { computed } from "vue";
import { useElementSize, useWindowSize } from "vueuse/core";

// 窗口和文档高度
const winHeight = useWindowSize().height;
const docHeight = useElementSize(document.body).height;

// 滑块高度
const thumbHeight = computed(() => {
    return winHeight.value ** 2 / docHeight.value;
});

由于当滑块高度等于窗口高度时,我们可以直接隐藏滚动条,所以不需要在公式中进行特殊处理。

滚动驱动动画

当页面从 top: 0 滚动到 top: 100% 时,滑块实际经过的距离要减去其自身的高度,于是这里使用 v-bind() 进行动态绑定:

js
1
2
3
const thumbTop = computed(() => {
    return `calc(100% - ${thumbHeight.value}px)`;
});
css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.scroll-thumb {
    animation: scroll-animation linear;
    animation-timeline: scroll();
}

@keyframe scroll-animation {
    from {
      top: 0;
    }

    to {
      top: v-bind("thumbTop");
    }
}

此时滑块的位置已经响应于页面的滚动进度了,当然它还需要能够通过拖动等行为来进行手动调整,以达成双向绑定。

鼠标事件

在原生滚动条的触控行为中,其一为拖动滑块,其二为长按滑轨,这里仅对前者稍作实现。

从上文得知,滑块的最大移动距离相当于 w - t,同理,文档的最大移动距离相当于 d - w;则当鼠标拖动滑块移动 x 个像素时,窗口要移动的距离为 x * (d - w) / (w - t),后者简化即 d / w

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { useEventListener, useThrottleFn } from "vueuse/core";

// 鼠标是否按下
let isPressed = false;

// 执行滚动前的窗口纵坐标
let startY = 0;

// 窗口与滑块的移动比例
const distanceRatio = computed(() => {
    return docHeight.value / winHeight.value;
});

// 在模板中绑定在滑块元素上,记得添加 prevent 修饰符
function onMousedown(event) {
    isPressed = true;
    startY = event.y;
}

// 务必使用节流函数
useEventListener("mousemove", useThrottleFn((event) => {
    if (isPressed) {
        window.scrollBy({
            top: (event.y - startY) * distanceRatio.value
        });
        startY = event.y;
    }
}, 16.6));

useEventListener("mouseup", () => {
    isPressed = false;
});

在代码中直接控制窗口滚动而不处理滑块位置,是因为它已经应用了响应式的滚动驱动动画了。

评论0

60FPS