移动端“页面能横向拖”几乎从不是布局本身错了,而是某个叶子内容拒绝收缩,把固定宽度一路上溯到根。本节内容是对一次全站横向溢出扫描(101 条路由、360px 视口)中定位到的三类真实成因的归纳。
一、无空格的长 inline code
正文里行内代码 document.querySelector(sel).match(...) 或 ['pending','confirmed','invalid',...] 这类没有可断点的长串,word-break 默认按词处理、英文词内部不折行,直接顶破容器。
修复:只对“非块级代码”开启收缩,避免误伤代码块本身的横向滚动。
.article-content :not(pre) > code {
overflow-wrap: anywhere; /* 允许在任意字符处断行 */
word-break: break-word;
}
块级 pre 保留 overflow-x: auto(代码要横向滚动而非折行),二者策略相反,所以选择器必须区分::not(pre) > code。
二、white-space: nowrap 的胶囊/徽章
时间线、标签云里的“上一篇:某某标题…“链接常写 white-space: nowrap 配省略号。但省略号生效的前提是元素能被压缩——flex/grid 项默认 min-width: auto,等于”不许小于内容宽度”,nowrap + auto 最小尺寸 = 永不触发 ellipsis,反而撑宽整行。
.series-nav-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
min-width: 0; /* 关键:允许收缩,ellipsis 才会真正生效 */
}
配合容器 grid-template-columns: minmax(0, 1fr),胶囊在窄屏自然截断成 下一篇:Astro 内容系统如…。
三、网格轨道没设 minmax(0)
grid 单列若写成隐式列(或直接 display:grid 只给 gap),其列宽最小值默认 auto,会被任一子项的固有宽度顶开——上一节 select 撑破整页即此类。通用防御:
.container {
grid-template-columns: minmax(0, 1fr);
}
凡是“子项可能含不可收缩内容”(select、长 code、nowrap 标签、图片、表格)的网格/flex,轨道一律 minmax(0, …),子项一律补 min-w-0。
定位方法:谁在撑?
不要靠肉眼。程序化扫描三步:
// 1) 测页面是否横向溢出
const overflow = document.documentElement.scrollWidth - window.innerWidth;
// 2) 若溢出,找"右侧越界且祖先无裁剪"的最内层元素
document.querySelectorAll('body *').forEach((el) => {
const r = el.getBoundingClientRect();
if (r.right <= window.innerWidth + 4) return;
let a = el.parentElement,
clipped = false;
while (a) {
const ox = getComputedStyle(a).overflowX;
if (['auto', 'scroll', 'hidden', 'clip'].includes(ox)) {
clipped = true;
break;
}
a = a.parentElement;
}
if (
!clipped &&
![...el.children].some(
(c) => c.getBoundingClientRect().right > window.innerWidth + 4,
)
) {
console.log('元凶:', el); // 叶子 + 未被祖先裁剪 = 真凶
}
});
“祖先裁剪”的过滤很关键——pre 本身 overflow-x:auto,其内代码右边界超屏是正常滚动不是溢出;只有既越界、祖先又全不裁剪的最内层元素,才是需要修的点。
小结
横向溢出的修复从不是“加个 overflow:hidden 盖住”,而是找到那个拒绝收缩的叶子,给它一条明确的收缩契约:允许断字、允许压窄、轨道留下限。三类成因(长 code / nowrap 标签 / 默认网格轨道)覆盖了绝大多数线上所见即所得的“手机能横着拖”。