浏览器滚动能力完全解析:scroll, scrollTo, scrollBy, scrollIntoView

931 阅读5分钟

先有问题再有答案

  1. 滚动的前提是什么
  2. 浏览器提供了哪些滚动api
  3. scroll, scrollTo, scrollBy, scrollIntoView 有什么区别?
  4. 这些api具体如何使用
  5. 这些api在使用上有什么问题
  6. 如何实现一个smooth效果的scrollTo api?
  7. offsetTop、offsetLeft是相对直接父元素嘛?
  8. scrollLeft、scrollTop是相对于哪个元素进行距离计算的

滚动的条件

内容尺寸超过容器区域

当内容的总尺寸(宽度或高度)超出容器的可视区域时,会触发滚动。这是滚动的根本原因,因为内容超出容器后,容器必须具有某种机制来让用户访问未显示的内容。

  • 宽度或高度超过:无论是内容的宽度超过容器的宽度,还是内容的高度超过容器的高度,都会引发相应方向的滚动。
  • 动态内容:通过 JavaScript 或其他方式动态增加内容,也可能导致内容尺寸超过容器区域,从而触发滚动。

容器具备滚动能力

这个条件要保证容器能够实际处理溢出的内容并显示滚动条。
这主要依赖于 CSS 的 overflow 属性的设置:

  • overflow: auto:只有在内容溢出的情况下才会显示滚动条,这是最常用的选项。
  • overflow: scroll:即使内容未溢出,也会始终显示滚动条。
  • overflow: hidden:内容溢出时不显示滚动条,且内容不可见。
  • overflow: visible:内容溢出时不会裁剪,内容会直接展示在容器之外。

scroll API

scroll & scrollTo

scroll 方法用于将元素滚动到 指定的坐标位置。它可以对窗口或某个可滚动的元素应用。
scrollTo可以看做是 scroll 的别名,二者在用法上是完全相同的。

适用于需要将滚动条移动到一个绝对位置的情况。

scroll.gif

scrollBy

scrollBy 方法也是用于窗口或可滚动元素,但与 scroll 和 scrollTo 方法不同的是,它滚动的是 相对当前位置的偏移量,而不是绝对坐标。

适合相对当前滚动位置进行滚动的情况,如用户连续滚动页面或内容。

scrollBy.gif

scrollIntoView

scrollIntoView 方法用于将元素滚动到视图中。如果元素在视口中已经可见,则不会有任何效果。

主要用于确保某个元素进入视口中,这个方法不需要开发者手动计算目标位置,可以方便快速实现某些滚动功能,可以简化代码复杂度 因此建议优先使用这个

scrollintoView.gif

完整测试demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Scroll API Examples</title>
    <style>
        #scroll-container {
            margin-top: 30px;
            width: 100%;
            height: 600px;
            overflow: auto;
            border: 1px solid #ccc;
            position: relative;
        }

        .item {
            width: 100%;
            height: 200px;
            background-color: lightgrey;
            margin-bottom: 10px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
    </style>
</head>

<body>
    <button onclick="useScroll()">scroll</button>
    <button onclick="useScrollTo()">scrollTo</button>
    <button onclick="useScrollBy()">scrollBy</button>
    <button onclick="useScrollIntoView()">scrollIntoView</button>
    <div id="scroll-container">
        <div class="item" id="item1">Item 1</div>
        <div class="item" id="item2">Item 2</div>
        <div class="item" id="item3">Item 3</div>
        <div class="item" id="item4">Item 4</div>
        <div class="item" id="item5">Item 5</div>
        <div class="item" id="item6">Item 6</div>
        <div class="item" id="item7">Item 7</div>
        <div class="item" id="item8">Item 8</div>
        <div class="item" id="item9">Item 9</div>
        <div class="item" id="item10">Item 10</div>
    </div>

    <script>
        // 使用 scroll
        function useScroll() {
            const container = document.getElementById('scroll-container');
            // 滚动到绝对位置 使用对象参数进行平滑滚动
            container.scroll({ top: 200, left: 0, behavior: 'smooth' });
        }

        // 使用 scrollTo
        function useScrollTo() {
            const container = document.getElementById('scroll-container');
            // 滚动到绝对位置 使用对象参数进行平滑滚动
            container.scrollTo({ top: 200, left: 0, behavior: 'smooth' });
        }

        // 使用 scrollBy
        function useScrollBy() {
            const container = document.getElementById('scroll-container');
            // 使用对象参数进行平滑滚动 基于当前滚动位置, 向下滚动200px
            container.scrollBy({ top: 200, left: 0, behavior: 'smooth' });
        }

        // 使用 scrollIntoView
        function useScrollIntoView() {
            const target = document.getElementById('item8');
            // 使用对象参数将目标元素平滑滚动到视口中心
            target.scrollIntoView({
                behavior: 'smooth'
            });
        }
    </script>
</body>
</html>

scroll API的问题

  1. scroll & scrollTo & scrollBy 不够简单 需要计算具体的滚动位置
  2. scrollIntoView 这个api是够简单 但是有兼容问题 smooth 效果在部分浏览器上不能生效。 截屏2024-11-15 17.38.14.png

实现scrollIntoView方法

所以为了解决浏览器api的问题 我们可以自己实现一个scrollIntoView的方法。

       const Direction = {
            x: 'x',
            y: 'y',
        };

        function getTargetPosition(element, direction) {
            return direction === Direction.x ? element.offsetLeft : element.offsetTop;
        }

        function getScrollOffset(element, direction) {
            return direction === Direction.x ? element.scrollLeft : element.scrollTop;
        }

        function scrollToPosition(element, position, isHorizontal) {
            if (isHorizontal) {
                element.scrollTo(position, 0);
            } else {
                element.scrollTo(0, position);
            }
        }

        function animateScroll(element, start, end, isHorizontal) {
            let distance = end - start;
            let pos = start;
            const step = () => {
                pos += distance / 5;
                distance = end - pos;

                if (Math.abs(distance) < 1) {
                    scrollToPosition(element, end, isHorizontal);
                } else {
                    scrollToPosition(element, pos, isHorizontal);
                    requestAnimationFrame(step);
                }
            };
            step();
        }

        function scrollIntoView({ target, container, smooth = true, direction = Direction.y }) {
            if (!target) {
                return;
            }

            const parentEle = container ?? document.documentElement;
            const isHorizontal = direction === Direction.x;
            const targetPosition = getTargetPosition(target, direction);
            const scrollOffset = getScrollOffset(parentEle, direction);
            if (!smooth) {
                scrollToPosition(parentEle, targetPosition, isHorizontal);
                return;
            }
            animateScroll(parentEle, scrollOffset, targetPosition, isHorizontal);
        }

scrollIntoView 方法可以将指定的目标元素滚动到视口内,支持水平和垂直两个方向,并且可以选择是否进行平滑滚动。

  1. 参数解构:解构传入的参数,获取目标元素、滚动容器、是否平滑滚动以及滚动方向。
  2. 默认容器:如果未提供 container 参数,默认使用文档作为滚动容器。
  3. 计算位置:调用 getTargetPosition 获取目标元素在滚动方向上的相对位置;调用 getScrollOffset 获取当前滚动容器的滚动偏移量。
  4. 直接滚动:如果 smooth 参数为 false,直接滚动到目标位置。
  5. 平滑滚动:如果 smooth 参数为 true,调用 animateScroll 进行平滑滚动。

使用如下:

function useScrollIntoView2() {
            const target = document.getElementById('item8');
            const container = document.getElementById('scroll-container');
            scrollIntoView({
                target,
                container
            });
        }

效果如图:

scrollintoview2.gif

offsetTop & offsetLeft

offsetTop 和 offsetLeft 属性是用于获取元素相对于其包含块(containing block)的偏移量,而不是直接父元素。

确定包含块的元素,依赖于元素的 CSS 位置属性 (position)。不同的 position 值会导致不同的包含块。

当前元素position 为static, relative,absolute 包含块是最近的已定位祖先元素(即 position 属性为 relative、absolute、或 fixed)。如果没有已定位的祖先元素 包含块是初始包含块(对于 HTML 文档通常是 body)。

当前元素 position: fixed 包含块是视口(viewport)。

scrollTop & scrollLeft

表示当前已经滚动的水平或垂直距离。可以用来获取或设置滚动容器的滚动位置