弹窗动画停在半像素位置会让文章图片顶线消失
文章弹窗里的图片出现过一个渲染异常:图片顶部的 1px 线框会消失,看起来像图片内容向上压住了外部线框。鼠标框选图片后,线框又恢复正常。
直接改图片样式很难稳定复现和修复。异常状态和恢复后的 computed style 对比后,图片自身规则没有实质差异。真正触发风险的是弹窗动画把内容层长期留在半像素 transform 合成层里,1px 顶线在取整时被吃掉。
有效线索
异常状态下,文章图片继承的 CSS 变量里有这样的入场位移:
--modal-enter-x: -192.5px;
--modal-enter-y: -786.5px;这两个 .5px 值来自弹窗入场动画。原结构把弹窗外层固定在视口中心:
.modal-outer {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}入场动画也直接作用在同一个外层元素上。视口尺寸、卡片点击位置和弹窗尺寸组合后,最终坐标容易落在半像素。浏览器在 transform 合成层中绘制图片、圆角和 1px 线框时,会出现取整差异。鼠标框选触发重绘,线框临时恢复,所以看起来像“选一下就好了”。
修复方式
修复点放在弹窗层级,避免继续堆叠图片规则。
modal-outer改成全屏 fixed 容器,用 flex 居中,不再使用top: 50%和translate(-50%, -50%)。- 新增
modal-shell包住目录栏和文章主体,入场/离场动画只作用在这个内部壳层。 - 给 Vue
<Transition>显式声明 enter/leave 时长,避免外层没有 transition 后过渡类无法按预期清理。 - 动画结束态让
modal-shell回到transform: none,文章内容不再长期处在 transform 合成层里。 - 图片仍保留
display: block,线框改用box-shadow: 0 0 0 1px black,降低 inline 布局和边框绘制带来的额外变量。
核心结构变成:
<div class="modal-outer">
<div class="modal-shell">
<aside class="chapter-panel"></aside>
<div class="modal-wrapper"></div>
</div>
</div>关键样式变成:
.modal-outer {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.modal-shell {
display: flex;
transform: none;
pointer-events: auto;
}
.modal-enter-from .modal-shell {
transform: translate(
var(--modal-enter-x, 0px),
var(--modal-enter-y, 0px)
) scale(0.004);
}
.modal-enter-to .modal-shell,
.modal-leave-from .modal-shell {
transform: none;
}验证方式
这类问题不能只看 computed style。异常和正常状态的 computed style 可能完全一样,差异存在于合成层、动画类和实际像素坐标。
验证时检查三个点:
- 动画结束后
.modal-outer的transform为none。 - 动画结束后
.modal-shell的transform为none。 .article-body img保持display: block,线框规则为box-shadow: 0 0 0 1px black。
对应的回归测试可以直接检查源码约束,防止后续又把 transform 居中加回外层弹窗。
结论
图片线框丢失不一定是图片样式问题。弹窗、浮层、缩放动画、半像素居中会改变子元素的绘制上下文。遇到“鼠标框选后恢复”的 UI 异常,应优先检查是否存在长期保留的 transform、filter、backdrop-filter 或动画类。
这次修复的原则是:外层负责固定定位和居中,内层负责短暂动画,动画结束后内容层回到普通布局。这样能保留打开动画,同时减少图片、文字和 1px 线框的合成层取整风险。
本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。