这是一个在 iOS Safari(Webkit 引擎)中比较经典的渲染 Bug。当通过 JavaScript 动态向容器末尾添加元素时,Webkit 有时未能正确触发重绘(Repaint)或样式重新计算(Style Recalculation),导致 :last-child 伪类仍然停留在之前的元素上。
以下是解决这个问题的几种常用方案,按推荐程度排序:
1. 强制触发重绘 (Force Reflow)
这是最简单直接的"黑科技"。在插入元素的代码后面,读取一次容器或新元素的某个布局属性(如 offsetHeight),强制浏览器同步计算样式。
JavaScript
ini
const container = document.getElementById('container');
const newItem = document.createElement('div');
container.appendChild(newItem);
// 强制触发重绘
container.offsetHeight;
2. 使用 Flex/Grid 的 gap 属性 (推荐)
如果你的 :last-child 主要是为了处理间距(例如 margin-bottom: 0),那么弃用 :last-child 改用 gap 是最现代且根本的解决方案。gap 会自动处理元素间的间距,不需要判断谁是最后一个。
CSS
css
.container {
display: flex;
flex-direction: column;
gap: 10px; /* 元素之间自动产生 10px 间距,最后一个元素后方不会有间距 */
}
3. 反向思路:使用 :not(:last-child)
有时候 Webkit 对"谁是最后一个"反应迟钝,但对"谁不是最后一个"反应较快。尝试给所有非末尾元素设置样式:
CSS
css
/* 不推荐 */
/* .item { margin-bottom: 10px; } */
/* .item:last-child { margin-bottom: 0; } */
/* 推荐 */
.item:not(:last-child) {
margin-bottom: 10px;
}
4. 手动切换 Class
如果逻辑比较复杂,伪类失效频繁,建议放弃 CSS 伪类,改用 JavaScript 在插入时手动维护一个 .is-last 类。
JavaScript
ini
// 插入逻辑
const items = container.querySelectorAll('.item');
items.forEach(el => el.classList.remove('is-last'));
const lastItem = items[items.length - 1];
lastItem.classList.add('is-last');
为什么会发生这种情况?
Webkit 引擎为了性能优化,会尽量减少样式重新计算的频率。当 DOM 树发生变化时,它本应标记该容器为 "dirty" 并重新检查伪类状态,但在某些复杂的嵌套布局或特定的 iOS 版本中,这个触发机制会漏掉对 :last-child 的检查。
建议: 如果你的项目环境允许(iOS 14.1+),优先使用 Flexbox/Grid 的 gap。它不仅性能更好,还能彻底规避此类由于动态插入导致的伪类失效问题。