利用ShadowRoot 实现样式隔离
ShadowRoot
Shadow DOM API 的 ShadowRoot 接口是一个 DOM 子树的根节点,它与文档的主 DOM 树分开渲染。
ShadowRoot 的模式——可以是 open 或者 closed。这定义了 shadow root 的内部实现是否可被 JavaScript 访问及修改 — 也就是说
ShadowRoot的特性
图片来源:掘金(https://juejin.cn/post/6979489951108825095)
-
自定义的样式和布局:Shadow DOM可以创建一个私有的样式和布局环境,使得组件可以在自己的环境中独立地定义样式和布局,而不会影响到其他组件或页面的样式和布局。
-
隔离的DOM结构:Shadow DOM可以创建一个私有的DOM结构,使得组件可以在自己的环境中独立地定义DOM结构,而不会影响到其他组件或页面的DOM结构。
-
嵌套使用:Shadow DOM可以嵌套使用,一个Shadow DOM可以包含另一个Shadow DOM,从而实现更复杂的组件结构。
ShadoRoot 样式/组件隔离
由于Shadow Dom 是一个独立的节点,所以我们可以利用 Shadow Dom 实现样式隔和组件隔离。
ShadowRoot 内事件机制和正常的dom结构一致。但是不管是冒泡还是捕获事件,一旦达到边界ShadowHost
的时候event.target
就Shadow Host
利用ShadowRoot的特性对插件开发进行优化 (shadowroot使用场景)
在chrome
插件开发过程之中,如果你使用了像tailwindcss之类的样式库,不难避免遇到样式冲突的问题。
在开发过程中,我们可以利用ShadowRoot的特性,将样式隔离到ShadowRoot中,从而避免样式冲突。
引入样式文件
import style from '@/styles/main.css'
import popupStyle from '@/styles/popup.scss'
这里首先导入了组件依赖的样式文件: main.css
和popup.scss
。main.css
为tailwindcss的样式,popup.scss
为自定义的组件样式。
在Popup.vue
中不必引入样式文件(因为打包之后样式文件是作用于全局的,无法在shadow root中使用)。
注入样式文件
mountEl
为创建的挂载节点
mountEl.attachShadow({ mode: 'open' })
: 给mountEl
下面挂载了一个新创建的shadowRoot
节点。
vm = createApp(Popup).mount(shadowEl);
将Popup
组件挂载到shadowEl
中。
mountEl.shadowRoot.appendChild(document.createElement('style'))
: 给shadowRoot
中添加了一个style
shadowEl.querySelector('style').innerHTML = (style + popupStyle)
: 将main.css
和popup.scss
注入到shadowRoot
中。
这样样式就隔离到shadowRoot
中了,不会影响到ShadowRoot之外的结构。
完整code
import style from '@/styles/main.css'
import popupStyle from '@/styles/popup.scss'
import Popup from '@/Popup.vue'
const MOUNT_EL_ID = "oimi-auto-flow";
let mountEl = document.getElementById(MOUNT_EL_ID);
if (mountEl) mountEl.innerHTML = "";
mountEl = document.createElement("div");
mountEl.setAttribute("id", MOUNT_EL_ID);
document.body.appendChild(mountEl)
// create shadow root to isolation style
const shadowEl = mountEl.attachShadow({ mode: 'open' })
// mounted Popup vue component to shadow root
vm = createApp(Popup).mount(shadowEl);
// add style to shadow root
mountEl.shadowRoot.appendChild(document.createElement('style'))
// add popup style and tailwind style to shadow root
shadowEl.querySelector('style').innerHTML = (style + popupStyle);