利用ShadowRoot 实现样式隔离

ShadowRoot

Shadow DOM API 的 ShadowRoot 接口是一个 DOM 子树的根节点,它与文档的主 DOM 树分开渲染。

ShadowRoot 的模式——可以是 open 或者 closed。这定义了 shadow root 的内部实现是否可被 JavaScript 访问及修改 — 也就是说

ShadowRoot的特性

https://juejin.cn/post/6979489951108825095
图片来源:掘金(https://juejin.cn/post/6979489951108825095)

  1. 自定义的样式和布局:Shadow DOM可以创建一个私有的样式和布局环境,使得组件可以在自己的环境中独立地定义样式和布局,而不会影响到其他组件或页面的样式和布局。

  2. 隔离的DOM结构:Shadow DOM可以创建一个私有的DOM结构,使得组件可以在自己的环境中独立地定义DOM结构,而不会影响到其他组件或页面的DOM结构。

  3. 嵌套使用:Shadow DOM可以嵌套使用,一个Shadow DOM可以包含另一个Shadow DOM,从而实现更复杂的组件结构。

ShadoRoot 样式/组件隔离

由于Shadow Dom 是一个独立的节点,所以我们可以利用 Shadow Dom 实现样式隔和组件隔离。

ShadowRoot 内事件机制和正常的dom结构一致。但是不管是冒泡还是捕获事件,一旦达到边界ShadowHost的时候event.targetShadow Host

利用ShadowRoot的特性对插件开发进行优化 (shadowroot使用场景)

chrome插件开发过程之中,如果你使用了像tailwindcss之类的样式库,不难避免遇到样式冲突的问题。
在开发过程中,我们可以利用ShadowRoot的特性,将样式隔离到ShadowRoot中,从而避免样式冲突。

引入样式文件


import style from '@/styles/main.css'

import popupStyle from '@/styles/popup.scss'

这里首先导入了组件依赖的样式文件: main.csspopup.scssmain.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.csspopup.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);