# chrome 插件开发之使用 shadow dom 避免全局 css 污染
最近开发了chatGpt chrome 插件 AI-chat
(opens new window),在开发 chrome 插件的过程中,难免会往用户页面插入自己的 dom,这时候如果没有特殊处理,很容易遇到 css 全局污染的问题,
下面介绍使用 shadow-dom 解决这个问题。
# shadow dom
shadow dom 可以将一个隐藏的、独立的 DOM 附加到一个元素上。它包含四个概念:
- Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。
- Shadow tree:Shadow DOM 内部的 DOM 树。
- Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。
- Shadow root: Shadow tree 的根节点。
# 以下是 chrome 插件 shadow root 源码(vue3):
其中的 mode,用来控制是否可以被外部访问。
const prod = ()=>{
const el = document.createElement('div')
const shadow = el.attachShadow({mode: 'open'})
shadow.innerHTML = `
<link href="${chrome.runtime.getURL('style.css')}" rel="stylesheet">
<div id="ai-chat"></div>
`
document.documentElement.insertBefore(el, null);
createApp(App).mount((el.shadowRoot)!.getElementById('ai-chat') as Element)
}
const dev = ()=>{
const el = document.createElement('div')
el.id = 'ai-chat'
document.documentElement.insertBefore(el, null);
createApp(App).mount(el)
}
// prod()
isProd?prod():dev()
(由于开发环境的 css 引入不一样,采用了 dev 和 build 两种模式)
# shadow dom 的事件(mode:open):
当事件在组件外部捕获时,shadow DOM 中发生的事件将会以 host 元素作为目标。
使用 event.composedPath() 获得原始事件目标的完整路径以及所有 shadow 元素
这在判断点击事件发生在shadow内部还是外部尤其有用 比如下面的插件源码:
分别处理的线上环境的 shadow dom 的情况和开发环境普通 dom 的情况
const down = (event)=>{
const target = state.showtrans?panel.value.$el:icon.value;
// shadow dom 和 普通 dom 这里判断不一样
const isClickInsideTargetDiv = isProd?event.composedPath().includes(target):target?.contains(event.target);
if (!isClickInsideTargetDiv) {
state.showicon = false
state.showtrans = false
document.removeEventListener('mousedown',down)
}
}