<!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):
tagLeftInBox(Tag 距离左侧的总距离): Tag 的左边缘距离容器内容最左侧的距离。这是 Tag 在长纸条上的“绝对位置”。box.clientWidth(容器的可视宽度): 用户眼睛能看到的宽度。tag.clientWidth(Tag 的自身宽度): 被点击标签自己的宽度。
第二步:想象“完美居中”的状态
假设我们已经滚动完毕,Tag 刚好处于容器正中间。请看下面的示意图:
<----------------------- 容器可视区域 ----------------------->
| |
| 中线 Tag中心 中线 |
| | [ ] | |
| | ^ | |
| | | | |
| | Tag中心距离左侧的距离 | |
| |<--------->| | |
| | (A) | | |
| |
|<-------- 容器左半边宽度 ------->| |
| (B) |
在这个“完美状态”下,存在一个天然的等式:
Tag 的中心位置 = 容器的中心位置
第三步:计算坐标
我们需要求的是 滚动距离,也就是容器向左“吞”进去多少内容。 让我们换个角度看上面的图,从坐标原点(内容最左侧 0px)开始计算:
- Tag 的中心在哪里?
Tag 的左边缘在
tagLeftInBox,所以 Tag 的中心在: - 我们希望 Tag 的中心出现在哪里?
我们希望它出现在容器的正中间。
当滚动发生时,容器的左边缘对应的是
scrollToX坐标。 所以,Tag 的中心相对于可视区域左边缘的距离应该是box.clientWidth / 2。 换算成绝对坐标:
第四步:建立方程并求解
根据“完美居中”的条件(Tag中心坐标 = 目标中心坐标):
现在,我们要把 scrollToX 单独留在等号左边:
- 把右边的
box.clientWidth / 2移到左边(变减号): - 为了方便代码书写,我们调整一下顺序,就得到了你的公式:
3. 通俗解释(教程总结)
如果不使用数学公式,你可以这样理解这个计算过程:
- 先让 Tag 的左边缘对准容器的中心线:
我们滚动到
tagLeftInBox位置,此时 Tag 的左边刚好在容器中间。 代码体现:tagLeftInBox - 但是 Tag 有宽度,现在它偏在中心线右边:
我们需要把滚动条往回(向右)退一点点,把 Tag 的中心移到中心线上。
退多少?退半个 Tag 的宽度。
代码体现:
+ (tag.clientWidth / 2) - 现在的状态是 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;
这样解释,无论是从数学推导还是逻辑调整的角度,都能清晰地理解这个公式的来源。