原生动态插入CSS最可控,需确保style.sheet就绪后再insertRule;Vue用v-bind()绑定响应式变量并更新:root自定义属性;卸载时务必通过id清理style元素,避免样式泄漏。
useEffect + document.styleSheets 动态插入CSS规则原生方式最可控,不依赖第三方库,适合轻量主题切换或运行时样式覆盖。
常见错误是直接写 document.head.appendChild(styleEl) 后立刻查 document.styleSheets,但新样式表可能还没解析完成,导致 insertRule 报 DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet'。
<style> 元素并挂载,再通过 document.styleSheets[n] 定位到它(推荐用 id 或遍历 ownerNode 匹配)insertRule 时样式表已就绪——可加 setTimeout(..., 0) 或监听 load(仅对外链有效,内联需靠调度)onMounted 里操作,避免 SSR 环境报错useEffect(() => { const style = document.createElement('style'); style.id = 'dynamic-theme'; document.head.appendChild(style); const sheet = style.sheet; // 确保 sheet 可写 setTimeout(() => { sheet?.insertRule('.btn { background: #007bff; }', 0); }, 0);}, []);
<style scoped> + v-bind() 怎么动态改变量这不是传统 CSS-in-JS,但 Vue 3.2+ 支持在 <style> 里用 v-bind() 绑定响应式 JS 值,本质是编译期生成 CSS 自定义属性,再由 JS 更新 :root 变量。
容易踩的坑:绑定的变量必须是顶层 ref 或 computed,不能是组件内部临时值;且只支持单个值(如颜色、尺寸),不支持选择器或媒体查询动态化。
立即学习“前端免费学习笔记(深入)”;
v-bind() 写在 <style scoped> 内,会自动转成 --v-bind-xxx 自定义属性,并注入到组件根元素document.documentElement.style.setProperty('--primary-color', newColor),不是直接改 refscoped,动态变量只影响当前组件;若想全局生效,得手动设到 :root
<script setup>const themeColor = ref('#42b883')</script><template> <div class="card">...</div></template><style scoped>.card { border-color: v-bind(themeColor);}</style>
styled-components 或 emotion 直接上?它们确实是主流 CSS-in-JS 方案,但“动态注入”这个需求下,反而容易过重或失控。
典型问题:服务端渲染时,styled-components 的 ServerStyleSheet 必须严格匹配客户端 hydration,漏掉一个 collectStyles 就触发 checksum mismatch;而 emotion 的 css 函数在 React 18 并发模式下,若在 render 阶段调用会触发 warning(因可能重复执行)。
styled-components,优先走 ThemeProvider + props.theme,别在组件里反复调 css 生成新规则emotion 的 cache 配置:开发环境默认启用 source map,上线务必关掉,否则影响首屏性能没人提,但最常出问题的是卸载逻辑:React 组件 unmount、Vue 组件 onUnmounted 时,动态插入的 <style> 不清理,就会越积越多,尤其在路由频繁切换或弹窗反复打开的场景下。
更隐蔽的是:多个组件共用同一套动态样式(比如都操作 #theme-style),一个组件删了,另一个还在用,直接崩。
<style> 加唯一 id,删除前先 document.getElementById(id)?.remove()
sheet.cssRules.length === 0 来判断是否可删——IE 和部分旧版 Safari 不支持读取 cssRules