• 开往
  • 设置
  • 用户
  • 回到顶部
  • 收起
  • 基于滚动驱动动画的滚动条

    • ? 阅读
    • 458 字
    上一章下一章

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

    滑块高度

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

    1. w \geq d 时,t = w
    2. w < d 时,若将滚动条区域看作文档,滑块看作窗口,则可以得到比例关系 t \div w = w \div d,即 t = w ^ 2 \div 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();
    }
    
    @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

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

    60FPS