目前 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