:defined伪类本身不防fouc,关键在加载链路可控;:not(:defined)禁用display:none以防布局重排,应改用visibility:hidden+opacity:0保留占位;需确保customelements.define()早于dom解析执行,且避免重复定义或构造函数非法。

:defined 伪类本身不解决闪烁,它只是个“开关”——只有在自定义元素真正完成注册、解析和升级后才匹配成功。想靠它防 FOUC(无样式内容闪烁),关键不在怎么写选择器,而在整个加载链路是否可控。
为什么 :not(:defined) 不能设 display: none
这是最容易踩的坑。设 display: none 会让元素从文档流中彻底消失,一旦升级完成、:not(:defined) 失效,元素突然出现,周围布局会重排——页面内容“往上跳”或“往左挤”,用户感知明显。
- 正确做法是用
visibility: hidden+opacity: 0,保留占位空间 - 避免给
:not(:defined)加height、width或margin等影响布局的属性 - 如果骨架屏尺寸和最终样式不一致(比如 loading 态宽高是 200×100,真实态是 300×150),即使 visibility 不变,也会“闪缩”
:defined 生效的前提:脚本必须早于 DOM 解析
HTML 已写好 <my-button></my-button>,但样式没生效?大概率是 customElements.define() 执行太晚。浏览器解析 HTML 时遇到未知标签,会先当普通元素处理;等 JS 执行完注册,再触发“升级”,这时 :defined 才开始匹配。
- 把定义逻辑放在
<script></script>标签里,且置于开头或使用defer - 别依赖
DOMContentLoaded—— 此时很多元素可能已被解析并尝试升级,但注册还没到,导致静默失败 - 检查控制台是否有
Failed to execute 'define' on 'CustomElementRegistry'错误,常见原因是重复定义或构造函数非法 - 用
customElements.get('my-button')手动查注册状态,返回函数才算成功
只用 :not(:defined) 控制视觉,别指望它做降级逻辑
:not(:defined) 是纯 CSS 层的显隐开关,它不会改变 DOM 结构,也不会触发 JS 回调。你不能靠它“fallback 成 <div>”,也不能在里面写 <code>transition 动画——因为升级时机不可控,动画可能卡在半途或根本没启动。
- 它的唯一合理用途:给未定义元素加占位样式(比如灰色边框、浅色背景、loading 图标)
- 不要对
:defined做复杂过渡,尤其避免和transform、scale混用,容易和浏览器升级时机冲突 - 如果需要更精细的加载状态(如“正在 fetch 配置”),得靠 JS 控制 class,而不是依赖
:defined
真正难的不是写对那行 CSS,而是让所有环节——HTML 解析顺序、脚本执行时机、自定义元素构造合法性、CSS 加载路径——全部对齐。漏掉任意一环,:defined 就只是个摆设。











