马黑黑 发表于 2026-3-3 12:08

浅谈帖子自居中的实现

<style>
    .showBox { font-size: 18px; }
    .showBox p { margin: 10px 0; }
    .showBox code, .showBox pre { background: rgba(0,128,128,.25); padding: 2px 6px; tab-size: 4; }
    .showBox pre { padding: 10px 20px; white-space: pre-wrap; word-wrap: break-word; }
    .showBox pre code { padding: 0; background: none; }
    .showBox blockquote { margin: 10px 20px; padding: 2px 15px; border-left: 3px solid skyblue;background: aliceblue; }
</style>

<div class="showBox">
    <h2>1️⃣ 什么时候需要帖子自居中</h2>
    <p>很多发帖场景我们不能或不便去改变帖子所在父元素的样式,但又希望帖子能够在浏览器视口的水平方向居中。这样的需求在论坛、博客、个人网站等发帖时都可能存在,且这些发帖环境在整体布局层面我们处于被动状态,只能在其结构框架下发帖,自然情形下帖子在整个水平视口因其所在父元素的位置影响可能存在极大的偏移,若此,需要调整位置。</p>
    <p>以 discuz! 论坛为例,它以 table 表格结构组织发帖作者、帖子正文等信息流,其中帖子正文在整个浏览器视口中偏右,偏离距离依据不同的版面设计而定,各个论坛并不一致。这对于宽幅帖子而言是个尴尬,若不做位置调整帖子没法看——帖子宽度大于父元素的部分往右延伸,给人的视觉感受极差。</p>
    <blockquote>
      <p>
            <strong>理解浏览器视口</strong>
      </p>
      <p>
            <em>
                <strong>视口</strong>
            </em> 代表当前可见的计算机图形区域。在 Web 浏览器术语中,视口通常与浏览器窗口相同,但不包括浏览器的 UI、菜单栏等——即指你正在浏览的文档的那一部分。
      </p>
    </blockquote>
    <h2>2️⃣ 如何调整帖子位置</h2>
    <blockquote>
      <p>
            <strong>帖子元素 position 属性设置</strong>
      </p>
      <p>通常,帖子为了能在垂直方向正常占位以便不遮挡Web页面的交互功能区域(如点评、送花、编辑按钮等),一般预设 position 属性为 relative 以便使其整正常占位且易于操控其内子元素,后续讨论对帖子元素的操作默认使用这个位置属性设置。</p>
    </blockquote>
    <p>最推荐的做法是平移帖子,具体而言就是平移帖子元素自身(通常是一个 div 或iframe 标签)。使用 CSS 的 transform 的 translateX 函数实现元素的平移是首选,它比 left 属性具有一定的优越性,尤其在保持正常的文档流布局层面表现良好。</p>
    <p>而要平移帖子元素,需要知道其在浏览器视口中的偏移距离。假设我们已经知道偏移量,那一切好办,用
      <code>transform: translateX(距离)</code> 即可解决问题。问题是偏移距离因发帖环境不同而不确定,需要计算,所以我们进入下一个探讨环节——
    </p>
    <h2>3️⃣ 计算帖子在浏览器视口中的位置偏移量</h2>
    <p>要让帖子在水平视口中居中,本质就是令元素的垂直中心线和视口的垂直中心线重合。这需要获取一些数据才能进行计算。</p>
    <blockquote>
      <p>
            <strong>计算偏移量需要的数据</strong>
      </p>
      <ol>
            <li>元素左边界距离视口左端的尺寸</li>
            <li>元素宽度</li>
            <li>视口宽度</li>
            <li>……</li>
      </ol>
    </blockquote>
    <p>获取元素左边界与视口左端的距离可以使用
      <code>elment.getBoundingClientRect()</code> 方法获取,它相对其它获取方法而言最为简单;元素的宽度优先考虑
      <code>element.offsetWidth</code> 方法,兼容性好;至于视口宽度,
      <code>window.innerWidth</code>应该是最佳选择。
    </p>
    <p>接下来设计一个计算公式,让元素垂直中心线与视口垂直中心线重合:</p>
    <pre><code>平移距离 = 视口中心 -(元素距离视口左端尺寸 + 元素宽度 ÷ 2)
offsetX = window.innerWidth / 2 - (elment.getBoundingCLientRect().left + elment.offsetWidth / 2)</code></pre>
    <p>这样,理论上可以拿到帖子元素居中应平移多少距离。注意,这只是理论上,因为实际发帖场景中还有一些不确定因素会影响Web页的 UI,往往会牵一发而动全身。正常情况下,我们必须考虑的此类因素是滚动条:Web页中滚动条与
      <code>window.innerWidth</code> 息息相关,亦即,
      <code>window.innerWidth</code>拿到的视口宽度不包含纵向滚动条占位尺寸,相同浏览器窗口尺寸下滚动条出现与否它保持不变,但Web页整体布局整体布局却因滚动条出现与否产生变化(从而具体影响帖子元素距离视口左侧的值)。基于此,上述设计的计算公式应考虑滚动条因素。为此,公式调整如下:
    </p>
    <pre><code>平移距离 = (视口中心 - 滚动条宽度)÷ 2 -(元素距离视口左端尺寸 + 元素宽度 ÷ 2)
offsetX = (window.innerWidth - scrollbarWidth) / 2 - (elment.getBoundingCLientRect().left + elment.offsetWidth / 2)</code></pre>
    <p>这个调整本质上人为移动了视口中心,为的是处理滚动条出现时在视觉上能让帖子切实居中。换言之,滚动条出现时,视口右边界应以滚动条的左侧作为水平视口终止线。</p>
    <p>新的问题出现:滚动条的宽度怎么拿到?</p>
    <h2>4️⃣ 计算滚动条的宽度</h2>
    <p>浏览器滚动条的宽度并非固定,不同的浏览器、相同浏览器不同版本、前端对滚动条的相关设置等等情况都会对滚动条的宽度产生影响,其宽度本质上也是一个未知数,需要计算获取。比较牢靠的获取方法,可以借用 JQuery 内置的实现思路编写一个函数,即创建里外两个div,让里面的div撑高外面的div使之出现滚动条,然后计算出滚动条的宽度:</p>
    <pre><code>// 获取滚动条宽度
function getScrollbarWidth() {
    const outer = document.createElement('div');
    outer.style.visibility = 'hidden';
    outer.style.overflow = 'scroll';
    outer.style.width = '100px';
    document.body.appendChild(outer);
    const inner = document.createElement('div');
    inner.style.width = '100%';
    outer.appendChild(inner);
    const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
    outer.parentNode.removeChild(outer);
    return scrollbarWidth;
}</code></pre>
    <blockquote>
      <p>
            <strong>其它影响Web页布局的因素</strong>
      </p>
      <ol>
            <li>首推
                <strong>body {}</strong> 选择器的margin、padding属性。默认情况下,margin 8px、padding 0,属于方向性对称设置,不会影响帖子自居中计算。仅当怪异需求的布局才可能出现左与右、上与下不对称的值设置,这种情况极为罕见,计算公式可不予考虑。
            </li>
            <li>其它未明情况</li>
      </ol>
    </blockquote>
    <p>现在,所有已知条件具备,可以开干了——</p>
    <h2>5️⃣ 进入实战</h2>
    <pre><code>function autoMid(el) {
    // 判断滚动条是否出现
    const hasVS = document.documentElement.scrollHeight &gt; window.innerHeight;
    // 滚动条宽度
    const sw = hasVS ? getScrollbarWidth() : 0;
    // 计算平移距离
    const rect = el.getBoundingClientRect();
    const delta = (window.innerWidth - sw) / 2 - (rect.left + rect.width / 2);
    // 驱动元素平移
    el.style.transform = `translateX(${delta}px)`;
}</code></pre>
    <p>autoMid() 函数在正常状态下可以实现元素在视口水平方向自适应居中,而且是绝对居中。</p>
    <p>&#128171;上述探讨结果历经多次实践而得,是否正确、全面不敢妄言,如有错误敬请斧正。</p>
</div>

红影 发表于 2026-3-3 13:43

没想到设置居中时还要考虑滚动条的影响,这也太细致了{:4_187:}

红影 发表于 2026-3-3 13:44

offsetX = window.innerWidth / 2 + (elment.getBoundingCLientRect().left - elment.offsetWidth / 2)
这句没看懂,看前面的介绍,这里的+号是不是应该是减号?

寒冬残荷 发表于 2026-3-3 14:08

为马老师点赞!虽然一下子还不知道如何应用到实际的代码中,但这正是我目前想解决的难题。还有,老师,下面的问题如何解决?

/* 手机适配/
@media (max-width: 600px) {
#bj {width: 99%; }
.content {padding-left:10px; }
.pa {width:97%; }
.nav a {font-size: 18px; padding: 12px 5px; }
#p1 { font-size: 1rem; }
}
右侧的滚动条看起来画面偏左了,因为有滚动条,如何不要这滚动条?

马黑黑 发表于 2026-3-3 16:39

寒冬残荷 发表于 2026-3-3 14:08
为马老师点赞!虽然一下子还不知道如何应用到实际的代码中,但这正是我目前想解决的难题。还有,老师,下面 ...

如果不想出现页面的滚动条,可以给 body {} 选择器加上:

body {
    /* 其它代码 */
    overflow: hidden;
}

如果是针对指定元素不要滚动条,历史外层元素 #bj :

#bj {
    /* 其它代码*/
    overflow: hidden;
}

这在手机端没问题,但在PC端就不好翻页,所以代码要用到适配手机端那里。

马黑黑 发表于 2026-3-3 16:40

红影 发表于 2026-3-3 13:44
offsetX = window.innerWidth / 2 + (elment.getBoundingCLientRect().left - elment.offsetWidth / 2)
这 ...
这是写错了,感谢提醒。

中文公式没有问题,后面的实现代码也对。

马黑黑 发表于 2026-3-3 16:43

红影 发表于 2026-3-3 13:43
没想到设置居中时还要考虑滚动条的影响,这也太细致了

不考虑也行,就是有一点点误差。就是因为发现这点误差,经反复分析,才找到滚动条的原因。还有,文中也提到了, body 的 margin、padding及其相关设定都有可能产生一定影响,不过这类可能性极端情况下才会出现。

霜染枫丹 发表于 2026-3-3 20:34

本帖最后由 霜染枫丹 于 2026-3-3 22:07 编辑

感谢马老师的分享,元宵节快乐


杨帆 发表于 2026-3-3 20:52

考虑全面、推理严谨、落地完美,谢谢马老师精彩讲授,祝元宵节快乐{:4_180:}

马黑黑 发表于 2026-3-3 21:55

杨帆 发表于 2026-3-3 20:52
考虑全面、推理严谨、落地完美,谢谢马老师精彩讲授,祝元宵节快乐

{:4_191:}

红影 发表于 2026-3-4 22:22

马黑黑 发表于 2026-3-3 16:43
不考虑也行,就是有一点点误差。就是因为发现这点误差,经反复分析,才找到滚动条的原因。还有,文中也提 ...

要考虑的还挺多的呢。

马黑黑 发表于 2026-3-5 19:41

红影 发表于 2026-3-4 22:22
要考虑的还挺多的呢。

太多了。弄主要的、常规的。

红影 发表于 2026-3-5 22:39

马黑黑 发表于 2026-3-5 19:41
太多了。弄主要的、常规的。

你的意思,就这样,还不是全部呗。

马黑黑 发表于 2026-3-5 22:55

红影 发表于 2026-3-5 22:39
你的意思,就这样,还不是全部呗。

主要针对常规情况下会影响居中的情况

红影 发表于 2026-3-5 23:16

马黑黑 发表于 2026-3-5 22:55
主要针对常规情况下会影响居中的情况

嗯嗯,知道了{:4_204:}

马黑黑 发表于 2026-3-5 23:23

红影 发表于 2026-3-5 23:16
嗯嗯,知道了

比方说,通常的设置,body {} 选择器不会是古里古怪的,margin、padding的设置绝大多数都是对称的

红影 发表于 2026-3-7 22:31

马黑黑 发表于 2026-3-5 23:23
比方说,通常的设置,body {} 选择器不会是古里古怪的,margin、padding的设置绝大多数都是对称的

看你的说法,好像body {} 选择器经常古里古怪呗{:4_173:}

马黑黑 发表于 2026-3-7 22:50

红影 发表于 2026-3-7 22:31
看你的说法,好像body {} 选择器经常古里古怪呗

不是这么说。但会碰上。

红影 发表于 2026-3-7 23:45

马黑黑 发表于 2026-3-7 22:50
不是这么说。但会碰上。

应该是有人不按约定俗成设计造成的吧。

马黑黑 发表于 2026-3-7 23:56

红影 发表于 2026-3-7 23:45
应该是有人不按约定俗成设计造成的吧。

是这个意思
页: [1] 2
查看完整版本: 浅谈帖子自居中的实现