移动端淡入必须同时控制 opacity、visibility 和 pointer-events 三者,仅设 opacity 会导致透明元素仍占位挡点击;transition 需预声明在基础选择器中;加载完成需分层判断图片、数据、字体就绪;transitionend 必须校验 propertyName 为 'opacity'。

移动端淡入必须同时控制 opacity 和 visibility
只设 opacity: 0 再切到 1,元素会“透明但可点、占位、挡点击”,用户可能误点空白区域或触发下层事件。移动端尤其明显——手指悬停位置和实际响应区域错位。
正确初始状态必须三者齐备:
opacity: 0visibility: hiddenpointer-events: none
transition 必须写在基础选择器里,不能只放在动画类中
常见错误是这样写:.fade-in { transition: opacity 0.3s ease; opacity: 1; }
结果动画不触发。因为 transition 属性本身需要“从无到有”的样式计算过程,而浏览器无法对“刚插入的 transition 声明”补全前一帧。
必须提前声明:img, .loading-target { opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease; }
再用 JS 切换类(如 .loaded)来触发动画。
加载完成判断不能只靠 window.onload
移动端网络波动大,window.onload 只保证 HTML+JS+CSS 加载完,不保证图片、字体、API 数据就绪。
真实场景要分层判断:
- 图片:用
img.complete+load事件(注意懒加载需等IntersectionObserver触发后再监听) - 数据请求:在
fetch().then()或 React 的useEffect数据就绪后才加类 - 首屏关键图:可结合
document.fonts.ready防止文字闪动
transitionend 事件必须过滤 propertyName
你写了 transition: opacity 0.3s, visibility 0.3s,但 visibility 实际不支持缓动,它只是在 0.3s 末“啪”一下生效。如果监听 transitionend 不校验属性名,就会在 visibility 变化时误判为动画结束,导致后续逻辑(比如启用按钮、释放滚动)提前执行。
务必写成:el.addEventListener('transitionend', e => { if (e.propertyName === 'opacity') { /* 真正完成 */ } });
这点在 iOS Safari 上尤其关键——它对未过滤的 transitionend 更敏感,容易多触发一次。
立即学习“前端免费学习笔记(深入)”;
真正卡住的不是“怎么让元素变出来”,而是“变出来之后,它有没有挡住下层内容、能不能点、布局有没有被撑开”。这些细节全靠 visibility 和 pointer-events 配合,单靠 opacity 永远不够。


















