目前 scroll-driven animations 的浏览器兼容性不是很好,故编写一个 demo 暂存在这个页面。利用 JS 实现的滚动条要比原生消耗更多性能,在反复调试了一下午后还是选择再观望一阵子。
滑块高度
首先,对滑块的高度进行计算。设滑块高度为 t,窗口高度为 w,文档高度为 d:
- 当 w \geq d 时,t = w;
- 当 w < d 时,若将滚动条区域看作文档,滑块看作窗口,则可以得到比例关系 t \div w = w \div d,即 t = w ^ 2 \div d:
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() 进行动态绑定:
1 2 3
const thumbTop = computed(() => {
 return `calc(100% - ${thumbHeight.value}px)`;
});
1 2 3 4 5 6 7 8 9 10 11 12 13 14
.scroll-thumb {
 animation: scroll-animation linear;
 animation-timeline: scroll();
}

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

 to {
 top: v-bind("thumbTop");
 }
}
此时滑块的位置已经响应于页面的滚动进度了,当然它还需要能够通过拖动等行为来进行手动调整,以达成双向绑定。
鼠标事件
在原生滚动条的触控行为中,其一为拖动滑块,其二为长按滑轨,这里仅对前者稍作实现。
从上文得知,滑块的最大移动距离相当于 w - t,同理,文档的最大移动距离相当于 d - w;则当鼠标拖动滑块移动 x 个像素时,窗口要移动的距离为 x \times (d - w) \div (w - t),后者简化即 d \div w:
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 isPressing = false;

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

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

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

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

useEventListener("mouseup", () => {
 isPressing = false;
});
在代码中直接控制窗口滚动而不处理滑块位置,是因为它已经应用了响应式的滚动驱动动画了。
评论0