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