tag 滚动 移动到中心点 计算

15 阅读4分钟
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Scrollable Box with Centered Tags</title>
    <style>
        .box {
            width: 500px;
            height: 50px;
            overflow-x: auto;
            white-space: nowrap;
            border: 1px solid #ccc;
            position: relative;
            scroll-behavior: smooth; /* 平滑滚动 */
			display: flex;
        }

        .tag {
            margin: 0 10px;
			width: 30px;
			height: 100%;
            background: #f0f0f0;
            border-radius: 4px;
            cursor: pointer;
            min-width: 60px;
            text-align: center;
        }
    </style>
</head>
<body>

<div class="box" id="box">
    <div class="tag">Tag 1</div>
    <div class="tag">Tag 2</div>
    <div class="tag">Tag 3</div>
    <div class="tag">Tag 4</div>
    <div class="tag">Tag 5</div>
    <div class="tag">Tag 6</div>
    <div class="tag">Tag 7</div>
    <div class="tag">Tag 8</div>
    <div class="tag">Tag 9</div>
    <div class="tag">Tag 10</div>
</div>

<script>
    const box = document.getElementById('box');
    const tags = box.querySelectorAll('.tag');

    tags.forEach(tag => {
        tag.addEventListener('click', () => {
            // 获取 .box 的可视宽度
            const boxRect = box.getBoundingClientRect();
            const tagRect = tag.getBoundingClientRect();

            // 计算 tag 相对于 box 左侧的偏移(考虑当前滚动)
            const tagLeftInBox = tag.offsetLeft;

            // 目标:让 tag 居中
            const scrollToX = tagLeftInBox - (box.clientWidth / 2) + (tag.clientWidth / 2);

            // 执行滚动
            box.scrollTo({
                left: scrollToX,
                behavior: 'smooth'
            });
        });
    });
</script>

</body>
</html>


这是一个非常经典且实用的前端计算公式。为了制作教程,我们可以将这个问题拆解为三个部分:核心目标图形化推导、以及公式含义。 以下是教程内容的建议:

1. 核心目标

我们的目标很简单:当用户点击一个标签时,让这个标签水平居中显示在容器中。 为了实现“居中”,我们需要告诉滚动容器:“你应该滚动到什么位置?”。这个位置(scrollToX)就是我们要计算的值。

2. 图形化推导

我们可以把这个问题想象成在桌子上移动一张长纸条,想让纸条上的某个标记对准放大镜的中心。

第一步:认识三个关键距离

在计算之前,我们需要知道三个数值(假设点击了某个 Tag):

  1. tagLeftInBox (Tag 距离左侧的总距离): Tag 的左边缘距离容器内容最左侧的距离。这是 Tag 在长纸条上的“绝对位置”。
  2. box.clientWidth (容器的可视宽度): 用户眼睛能看到的宽度。
  3. tag.clientWidth (Tag 的自身宽度): 被点击标签自己的宽度。

第二步:想象“完美居中”的状态

假设我们已经滚动完毕,Tag 刚好处于容器正中间。请看下面的示意图:

<----------------------- 容器可视区域 ----------------------->
|                                                           |
|              中线          Tag中心         中线           |
|                |       [         ]         |             |
|                |           ^               |             |
|                |           |               |             |
|                |    Tag中心距离左侧的距离    |             |
|                |<--------->|               |             |
|                |     (A)   |               |             |
|                                                           |
|<-------- 容器左半边宽度 ------->|                           |
|            (B)                 |

在这个“完美状态”下,存在一个天然的等式:

Tag 的中心位置 = 容器的中心位置

第三步:计算坐标

我们需要求的是 滚动距离,也就是容器向左“吞”进去多少内容。 让我们换个角度看上面的图,从坐标原点(内容最左侧 0px)开始计算:

  1. Tag 的中心在哪里? Tag 的左边缘在 tagLeftInBox,所以 Tag 的中心在: Tag中心坐标=tagLeftInBox+tag.clientWidth2\text{Tag中心坐标} = \text{tagLeftInBox} + \frac{\text{tag.clientWidth}}{2}
  2. 我们希望 Tag 的中心出现在哪里? 我们希望它出现在容器的正中间。 当滚动发生时,容器的左边缘对应的是 scrollToX 坐标。 所以,Tag 的中心相对于可视区域左边缘的距离应该是 box.clientWidth / 2。 换算成绝对坐标: 目标中心坐标=scrollToX+box.clientWidth2\text{目标中心坐标} = \text{scrollToX} + \frac{\text{box.clientWidth}}{2}

第四步:建立方程并求解

根据“完美居中”的条件(Tag中心坐标 = 目标中心坐标): tagLeftInBox+tag.clientWidth2=scrollToX+box.clientWidth2\text{tagLeftInBox} + \frac{\text{tag.clientWidth}}{2} = \text{scrollToX} + \frac{\text{box.clientWidth}}{2} 现在,我们要把 scrollToX 单独留在等号左边:

  1. 把右边的 box.clientWidth / 2 移到左边(变减号): tagLeftInBox+tag.clientWidth2box.clientWidth2=scrollToX\text{tagLeftInBox} + \frac{\text{tag.clientWidth}}{2} - \frac{\text{box.clientWidth}}{2} = \text{scrollToX}
  2. 为了方便代码书写,我们调整一下顺序,就得到了你的公式: scrollToX=tagLeftInBoxbox.clientWidth2+tag.clientWidth2\text{scrollToX} = \text{tagLeftInBox} - \frac{\text{box.clientWidth}}{2} + \frac{\text{tag.clientWidth}}{2}

3. 通俗解释(教程总结)

如果不使用数学公式,你可以这样理解这个计算过程:

  1. 先让 Tag 的左边缘对准容器的中心线: 我们滚动到 tagLeftInBox 位置,此时 Tag 的左边刚好在容器中间。 代码体现:tagLeftInBox
  2. 但是 Tag 有宽度,现在它偏在中心线右边: 我们需要把滚动条往回(向右)退一点点,把 Tag 的中心移到中心线上。 退多少?退半个 Tag 的宽度。 代码体现:+ (tag.clientWidth / 2)
  3. 现在的状态是 Tag 居中了,但滚动条的刻度不对: 上面两步算出的位置,其实假设了容器的“中心线”就是“0刻度线”。但实际上,容器的中心线距离容器左边缘还有半个容器的距离。 所以我们需要把滚动条再往左(向前)推进半个容器的宽度,把内容“压”进去。 代码体现:- (box.clientWidth / 2) (距离左边的距离 减去可视区域的一半,这个时候tag处于中心点的左边,加上tag一半,就居中了)

4. 最终公式对应代码

// 1. 定位到 Tag 左边缘
const basePosition = tag.offsetLeft; 
// 2. 修正 Tag 自身宽度 (让中心对准,而不是左边缘对准)
const centerCorrection = tag.clientWidth / 2;
// 3. 修正容器宽度 (确保是在容器中心显示,而不是左边缘显示)
const boxCenterCorrection = box.clientWidth / 2;
// 组合起来:最终滚动位置 = 原始位置 - 容器宽度修正 + 自身宽度修正
const scrollToX = basePosition - boxCenterCorrection + centerCorrection;

这样解释,无论是从数学推导还是逻辑调整的角度,都能清晰地理解这个公式的来源。