Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 web 应用中使用它们。
-- Web Components | MDN[1]
关于它的其它介绍:
这里会对 Web Component 的相关属性做一个简单介绍。
Web Component 特性完整代码:https://codesandbox.io/s/snowy-darkness-jmdip7
一组 Javascript API,允许您定义 Custom Elements 及其行为,然后在您的用户界面中按照需要使用它们。
// custom button
class CustomButton extends HTMLElement {
constructor() {
super();
const button = document.createElement("button");
button.innerText = this.getAttribute("name") || "custom button";
button.disabled = true;
this.appendChild(button);
}
}
window.customElements.define("custom-button", CustomButton);
该方法用来获取自定义组件的构造函数,接收一个参数,即声明的自定义组件的 name,返回构造函数。
const getCustomConstructorBefore = customElements.get('custom-button');
// getCustomConstructorBefore before: undefined
console.log('getCustomConstructorBefore before: ', getCustomConstructorBefore);
customElements.define("custom-button", CustomButton);
const getCustomConstructorAfter = customElements.get('custom-button');
// getCustomConstructorAfter after: ƒ CustomButton() {}
console.log('getCustomConstructorAfter after: ', getCustomConstructorAfter);
customElements upgrade() 方法升级节点子树中文档的所有包含 shadow dom 的(亲测,可以不包含 shadow dom)自定义元素,甚至在它们连接到主文档之前。接收一个参数,即一个自定义组件节点,无返回值。
// 先创建自定义标签
const el = document.createElement("spider-man");
class SpiderMan extends HTMLElement {}
// 后声明构造函数
customElements.define("spider-man", SpiderMan);
// false
console.log(el instanceof SpiderMan);
// 建立自定义标签与构造函数之间的绑定关系
customElements.upgrade(el);
// true
console.log(el instanceof SpiderMan);
该方法用来检测并提供自定义组件被定义声明完毕的时机,接收一个参数,即自定义元素的 name,返回值是一个 Promise,若提供的 name 无效,则触发 Promise 的 catch,可以用来判断是否定义了同名的 Custom Element。
Custom Element 重复定义的报错:
我们可以用这个方法来捕获重复定义的报错,最推荐 define 之前先 get 一下~
customElements.whenDefined('custom-button').then(() => {
customElements.define('custom-button', CustomButton);
}).catch((err) => {
console.log(err, 'err-----')
});
捕获的报错信息:
Custom Elements
提供了一些生命周期函数,使得我们能在自定义元素在 DOM 中的行为变化后执行相关逻辑。
示例代码:
class CustomButton extends HTMLElement {
constructor() {
super();
const button = document.createElement("button");
button.innerText = "custom button";
button.addEventListener("click", this.changeAttribute.bind(this));
const textSpan = document.createElement("span");
textSpan.innerText = this.getAttribute("text") || "default";
textSpan.className = "info";
this.appendChild(button);
this.appendChild(textSpan);
}
connectedCallback() {
console.log("自定义button被连接到DOM啦~");
}
// observedAttributes,定义特定属性变化时,触发attributeChangedCallback函数
// 未定义的属性改变,则不会触发回调
static get observedAttributes() {
return ["style", "text"];
}
// 与observedAttributes结合使用
attributeChangedCallback(name, oldValue, newValue) {
if (
name === "text" &&
oldValue !== newValue &&
this.querySelector(".info")
) {
this.querySelector(".info").innerText = newValue;
}
}
changeAttribute() {
this.setAttribute("text", "sfsdfd");
}
disconnectedCallback() {
console.log("自定义button与DOM断开连接啦~");
}
adoptedCallback() {
// 创建一个iframe的document,并移动进去
console.log("自定义button移动到新文档啦~");
}
clickToRemove() {
// 从DOM中移除自身
this.parentNode.removeChild(this);
}
}
window.customElements.define("custom-button", CustomButton);
一组 Javascript API,可以将封装的“Shadow DOM”树附加到元素(与主文档分开呈现),并控制其关联的功能。通过这种方式,您可以拥有一个天然的沙箱环境,保持元素的功能私有,实现样式和脚本的隔离,不用担心与文档的其他部分发生冲突。
示例代码:
class CustomShadowDOM extends HTMLElement {
constructor() {
super();
// 创建一个shadowDOM
const shadow = this.attachShadow({ mode: "open" });
const info = document.createElement("span");
const style = document.createElement("style");
const css = "span { color: red; }";
style.type = "text/css";
style.appendChild(document.createTextNode(css));
info.setAttribute("class", "info");
info.textContent = this.getAttribute("text") || "default";
// shadow可以创建一个不受外部影响,切拥有内部js运行逻辑,拥有独立的
// css的自定义元素(也就是web component),
shadow.appendChild(style);
shadow.appendChild(info);
}
}
window.customElements.define("custom-shadow-dom", CustomShadowDOM);
<template>
和 <slot>
元素使您可以编写呈现页面中显示的标记模版。<template>
可以作为自定义元素结构的基础被多次重用,也就是我们常做的组件复用。
<template>
元素是一种保护客户端内容机制,该内容在加载页面时不会呈现,但随后可以在运行时使用 JavaScript 实例化。
将模板视为一个可存储在文档中以便后续使用的内容片段,虽然解析器在加载页面时确实会处理 template 元素的内容,但这样做只是为了确保内容有效,元素不会被渲染。
<template>
下面我们来定义一个人员信息卡片的template
,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式,可以把样式写在 template 里面。
示例代码:
<!-- 人员信息卡片标签 -->
<user-card
image="https://s1-imfile.feishucdn.com/static-resource/v1/v2_b2741d84-5d05-4739-b349-a3887b61039g~?image_size=noop&cut_type=&quality=&format=image&sticker_format=.webp"
name="User Name"
email="yourmail@some-email.com"
age="18"
></user-card>
<!-- 在HTML中定义template -->
<template id="userCardTemplate">
<style>
.box {
display: flex;
align-items: center;
width: 450px;
height: 180px;
background-color: #d4d4d4;
border: 1px solid #d5d5d5;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 3px;
overflow: hidden;
padding: 10px;
box-sizing: border-box;
}
.image {
flex: 0 0 auto;
width: 160px;
height: 160px;
vertical-align: middle;
border-radius: 5px;
}
.container {
box-sizing: border-box;
padding: 20px;
height: 160px;
}
.container > .name {
font-size: 20px;
font-weight: 600;
line-height: 1;
margin: 0;
margin-bottom: 5px;
}
.container > .email {
font-size: 14px;
opacity: 0.75;
line-height: 1;
margin: 0;
margin-bottom: 15px;
}
.container > .age {
padding: 10px 25px;
font-size: 14px;
border-radius: 5px;
text-transform: uppercase;
}
</style>
<div class="box">
<img class="image" />
<div class="container">
<p class="name"></p>
<p class="email"></p>
<p class="age"></p>
</div>
</div>
</template>
// UserTemplate.js
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById("userCardTemplate");
var content = templateElem.content.cloneNode(true);
content
.querySelector(".image")
.setAttribute("src", this.getAttribute("image"));
content.querySelector(".name").innerText = this.getAttribute("name");
content.querySelector(".email").innerText = this.getAttribute("email");
content.querySelector(".age").innerText = this.getAttribute("age");
this.appendChild(content);
}
}
window.customElements.define("user-card", UserCard);
运行效果:
<slot>
使用模版我们只能传递一些文本变量,这很有局限性,于是 Web Components 引入了<slot>
(插槽)的概念来增加编码的灵活度。
我们可以使用 slot 来实现基于模版的部分自定义内容(标签、样式)的渲染,slot 插槽需要在 Shadow DOM 里才能生效。
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById("userCardTemplate");
var content = templateElem.content.cloneNode(true);
content
.querySelector(".image")
.setAttribute("src", this.getAttribute("image"));
content.querySelector(".name").innerText = this.getAttribute("name");
content.querySelector(".email").innerText = this.getAttribute("email");
content.querySelector(".age").innerText = this.getAttribute("age");
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(content);
}
}
window.customElements.define("user-card", UserCard);
所有特性的完整 Demo :https://codesandbox.io/s/snowy-darkness-jmdip7?file=/index.html
支持程度、存在问题、对应版本的浏览器在市场中的占比(不支持 IE)。
custom-button
Web Component & React 完整代码:https://codesandbox.io/s/sleepy-euclid-00crli?file=/src/CustomButton.tsx:256-261
// customButton.tsx
class CustomButton extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: "open"
});
const style = document.createElement("style");
style.textContent = `span { display: block; color: black };`;
this.shadow.appendChild(style);
const child = document.createElement("span");
child.className = "shadowInfo";
child.textContent = "default";
this.shadow.appendChild(child);
}
connectedCallback() {
if (this.querySelector(".shadowInfo")) {
this.querySelector(".shadowInfo").textContent = this.getAttribute("text");
}
}
// observedAttributes,定义特定属性变化时,触发attributeChangedCallback函数
static get observedAttributes() {
return ["text"];
}
// 与observedAttributes结合使用
attributeChangedCallback(name, oldValue, newValue) {
if (
name === "text" &&
oldValue !== newValue
) {
this.shadow.querySelector(".shadowInfo").innerText = newValue;
}
}
}
window.customElements.define("custom-button", CustomButton);
const Main = (props) => {
const [text, setText] = useState("custom button");
return (
<>
<button onClick={() => setText("muzishuiji")}>change text</button>
点击按钮change text
<custom-button
text={text}
onClick={clickHandler}
></custom-button>
</>
);
};
export default Main;
Vue 提供了一个和定义一般 Vue 组件几乎完全一致的 defineCustomElement
方法来支持创建自定义元素。这个方法接收的参数和 defineComponent
完全相同。但它会返回一个继承自 HTMLElement
的自定义元素构造器:
import { defineCustomElement } from "vue";
const MyVueElement = defineCustomElement({
// 普通vue组件选项
});
customElements.define("my-vue-element", MyVueElement);
defineCustomElement
这个 API 允许开发者创建 Vue 驱动的 UI 组件库,这些库可以与任何框架一起使用,或者根本没有框架。
创建一个 React 工程,接下来根据示例代码操作,插件的 js 资源通过网络请求加载,css 资源通过 string 引入。
Web Component 在「飞书项目」的插件体系的尝试完整代码:https://codesandbox.io/s/goofy-gauss-pmi46i?file=/src/App.tsx
import './pluginTemplate';
import './App.css';
import { useEffect, useState } from "react";
function App() {
const [userId] = useState('123456789');
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(pre => pre + 1);
}, 1000);
return () => {
clearInterval(timer)
}
}, []);
return (
<div className="App-parent">
<h1>我是一个依赖人员卡片的业务方</h1>
<div>这里有一个定时器: {count}</div>
<div>
<a href="">宿主环境的a标签</a>
</div>
<plugin-template userId={userId}></plugin-template>
</div>
);
}
export default App;
// 偷个懒,cssText就不请求了
import cssContent from "./cssContent";
class PluginTemplate extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: "open"
});
// 给插件搞一个容器
const container = document.createElement("div");
container.id = "root";
// 如果有modal需要作为getPopupcontainer
container.style.position = "relative";
// container.innerHTML = divText;
this.shadow.appendChild(container);
// 插件的style设置不会影响到外部
const style = document.createElement("style");
style.innerText =
"body{backgrount-color: red;}#root{ --semi-shadow-elevated: 0 0 1px rgba(0, 0, 0, .3), 0 4px 14px rgba(0, 0, 0, .1);}";
const styleNode = document.createTextNode(cssContent.toString());
style.appendChild(styleNode);
this.shadow.appendChild(style);
}
connectedCallback() {
// 动态插入插件的打包资源
const script = document.createElement("script");
script.type = "text/javascript";
fetch(
"https://raw.githubusercontent.com/muzishuiji/demo_resource/main/web_component/bundle.js"
)
.then((response) => {
response.text().then((data) => {
script.innerText = `(function (){let __temp_exports__={};with({exports:__temp_exports__}){console.log(document);${data.replace(
/[\r\n]*/[/*](?=# sourceMap "\r\n]*/[/*").*[\r\n]*/g,
""
)};}return __temp_exports__;})();`;
this.shadow?.appendChild(script);
});
})
.catch((err) => {
console.log(err, "err-----");
});
// 引入一个会导致栈溢出的script
// const errScript = document.createElement("script");
// errScript.type = "text/javascript";
// errScript.innerText = (function () {
// function sum(n) {
// if (n === 1) {
// return 1;
// }
// return n + sum(n - 1);
// }
// sum(1e5);
// })();
// this.shadow?.appendChild(errScript);
}
// observedAttributes,定义特定属性变化时,触发attributeChangedCallback函数
static get observedAttributes() {
return ["userId"];
}
}
if (!window.customElements.get("plugin-template")) {
window.customElements.define("plugin-template", PluginTemplate);
}
// 这里是插件资源的css,生产环境会通过网络请求获取
// 在这里偷个懒不发请求了,存成一个String
export const cssContent = `#root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}#root{--semi-transition_duration-slowest:0ms;--semi-transition_duration-slower:0ms;--semi-transition_duration-slow:0ms;--semi-transition_duration-normal:0ms;--semi-transition_duration-fast:0ms;--semi-transition_duration-faster:0ms;--semi-transition_duration-fastest:0ms;--semi-transition_duration-none:0ms;--semi-transition_function-linear:linear;--semi-transition_function-ease:ease;--semi-transition_function-easeIn:ease-in;--semi-transition_function-easeOut:ease-out;--semi-transition_function-easeInIOut:ease-in-out;--semi-transition_delay-none:0ms;--semi-transition_delay-slowest:0ms;--semi-transition_delay-slower:0ms;--semi-transition_delay-slow:0ms;--semi-transition_delay-normal:0ms;--semi-transition_delay-fast:0ms;--semi-transition_delay-faster:0ms;--semi-transition_delay-fastest:0ms;--semi-transform_scale-none:scale(1,1);--semi-transform_scale-small:scale(1,1);--semi-transform_scale-medium:scale(1,1);--semi-transform_scale-large:scale(1,1);--semi-transform-rotate-none:rotate(0deg);--semi-transform_rotate-clockwise90deg:rotate(90deg);--semi-transform_rotate-clockwise180deg:rotate(180deg);--semi-transform_rotate-clockwise270deg:rotate(270deg);--semi-transform_rotate-clockwise360deg:rotate(360deg);--semi-transform_rotate-anticlockwise90deg:rotate(-90deg);--semi-transform_rotate-anticlockwise180deg:rotate(-180deg);--semi-transform_rotate-anticlockwise270deg:rotate(-270deg);--semi-transform_rotate-anticlockwise360deg:rotate(-360deg)}#root,#root .semi-always-light{--semi-amber-0:254,251,235;--semi-amber-1:252,245,206;--semi-amber-2:249,232,158;--semi-amber-3:246,216,111;--semi-amber-4:243,198,65;--semi-amber-5:240,177,20;--semi-amber-6:200,138,15;--semi-amber-7:160,102,10;--semi-amber-8:120,70,6;--semi-amber-9:80,43,3;--semi-black:0,0,0;--semi-blue-0:234,245,255;--semi-blue-1:203,231,254;--semi-blue-2:152,205,253;--semi-blue-3:101,178,252;--semi-blue-4:50,149,251;--semi-blue-5:0,100,250;--semi-blue-6:0,98,214;--semi-blue-7:0,79,179;--semi-blue-8:0,61,143;--semi-blue-9:0,44,107;--semi-cyan-0:229,247,248;--semi-cyan-1:194,239,240;--semi-cyan-2:138,221,226;--semi-cyan-3:88,203,211;--semi-cyan-4:44,184,197;--semi-cyan-5:5,164,182;--semi-cyan-6:3,134,152;--semi-cyan-7:1,105,121;--semi-cyan-8:0,77,91;--semi-cyan-9:0,50,61;--semi-green-0:236,247,236;--semi-green-1:208,240,209;--semi-green-2:164,224,167;--semi-green-3:125,209,130;--semi-green-4:90,194,98;--semi-green-5:59,179,70;--semi-green-6:48,149,59;--semi-green-7:37,119,47;--semi-green-8:27,89,36;--semi-green-9:17,60,24;--semi-grey-0:249,249,249;--semi-grey-1:230,232,234;--semi-grey-2:198,202,205;--semi-grey-3:167,171,176;--semi-grey-4:136,141,146;--semi-grey-5:107,112,117;--semi-grey-6:85,91,97;--semi-grey-7:65,70,76;--semi-grey-8:46,50,56;--semi-grey-9:28,31,35;--semi-indigo-0:236,239,248;--semi-indigo-1:209,216,240;--semi-indigo-2:167,179,225;--semi-indigo-3:128,144,211;--semi-indigo-4:94,111,196;--semi-indigo-5:63,81,181;--semi-indigo-6:51,66,161;--semi-indigo-7:40,52,140;--semi-indigo-8:31,40,120;--semi-indigo-9:23,29,99;--semi-light-blue-0:233,247,253;--semi-light-blue-1:201,236,252;--semi-light-blue-2:149,216,248;--semi-light-blue-3:98,195,245;--semi-light-blue-4:48,172,241;--semi-light-blue-5:0,149,238;--semi-light-blue-6:0,123,202;--semi-light-blue-7:0,99,167;--semi-light-blue-8:0,75,131;--semi-light-blue-9:0,53,95;--semi-light-green-0:243,248,236;--semi-light-green-1:227,240,208;--semi-light-green-2:200,226,165;--semi-light-green-3:173,211,126;--semi-light-green-4:147,197,91;--semi-light-green-5:123,182,60;--semi-light-green-6:100,152,48;--semi-light-green-7:78,121,38;--semi-light-green-8:57,91,27;--semi-light-green-9:37,61,18;--semi-lime-0:242,250,230;--semi-lime-1:227,246,197;--semi-lime-2:203,237,142;--semi-lime-3:183,227,91;--semi-lime-4:167,218,44;--semi-lime-5:155,209,0;--semi-lime-6:126,174,0;--semi-lime-7:99,139,0;--semi-lime-8:72,104,0;--semi-lime-9:47,70,0;--semi-orange-0:255,248,234;--semi-orange-1:254,238,204;--semi-orange-2:254,217,152;--semi-orange-3:253,193,101;--semi-orange-4:253,166,51;--semi-orange-5:252,136,0;--semi-orange-6:210,103,0;--semi-orange-7:168,74,0;--semi-orange-8:126,49,0;--semi-orange-9:84,29,0;--semi-pink-0:253,236,239;--semi-pink-1:251,207,216;--semi-pink-2:246,160,181;--semi-pink-3:242,115,150;--semi-pink-4:237,72,123;--semi-pink-5:233,30,99;--semi-pink-6:197,19,86;--semi-pink-7:162,11,72;--semi-pink-8:126,5,58;--semi-pink-9:90,1,43;--semi-purple-0:247,233,247;--semi-purple-1:239,202,240;--semi-purple-2:221,155,224;--semi-purple-3:201,111,209;--semi-purple-4:180,73,194;--semi-purple-5:158,40,179;--semi-purple-6:135,30,158;--semi-purple-7:113,22,138;--semi-purple-8:92,15,117;--semi-purple-9:73,10,97;--semi-red-0:254,242,237;--semi-red-1:254,221,210;--semi-red-2:253,183,165;--semi-red-3:251,144,120;--semi-red-4:250,102,76;--semi-red-5:249,57,32;--semi-red-6:213,37,21;--semi-red-7:178,20,12;--semi-red-8:142,8,5;--semi-red-9:106,1,3;--semi-teal-0:228,247,244;--semi-teal-1:192,240,232;--semi-teal-2:135,224,211;--semi-teal-3:84,209,193;--semi-teal-4:39,194,176;--semi-teal-5:0,179,161;--semi-teal-6:0,149,137;--semi-teal-7:0,119,111;--semi-teal-8:0,89,85;--semi-teal-9:0,60,58;--semi-violet-0:243,237,249;--semi-violet-1:226,209,244;--semi-violet-2:196,167,233;--semi-violet-3:166,127,221;--semi-violet-4:136,91,210;--semi-violet-5:106,58,199;--semi-violet-6:87,47,179;--semi-violet-7:70,37,158;--semi-violet-8:54,28,138;--semi-violet-9:40,20,117;--semi-white:255,255,255;--semi-yellow-0:255,253,234;--semi-yellow-1:254,251,203;--semi-yellow-2:253,243,152;--semi-yellow-3:252,232,101;--semi-yellow-4:251,218,50;--semi-yellow-5:250,200,0;--semi-yellow-6:208,170,0;--semi-yellow-7:167,139,0;--semi-yellow-8:125,106,0;--semi-yellow-9:83,72,0}#root .semi-always-dark,#root[theme-mode=dark]{--semi-red-0:108,9,11;--semi-red-1:144,17,16;--semi-red-2:180,32,25;--semi-red-3:215,51,36;--semi-red-4:251,73,50;--semi-red-5:252,114,90;--semi-red-6:253,153,131;--semi-red-7:253,190,172;--semi-red-8:254,224,213;--semi-red-9:255,243,239;--semi-pink-0:92,7,48;--semi-pink-1:128,14,65;--semi-pink-2:164,23,81;--semi-pink-3:199,34,97;--semi-pink-4:235,47,113;--semi-pink-5:239,86,134;--semi-pink-6:243,126,159;--semi-pink-7:247,168,188;--semi-pink-8:251,211,220;--semi-pink-9:253,238,241;--semi-purple-0:74,16,97;--semi-purple-1:94,23,118;--semi-purple-2:115,31,138;--semi-purple-3:137,40,159;--semi-purple-4:160,51,179;--semi-purple-5:181,83,194;--semi-purple-6:202,120,209;--semi-purple-7:221,160,225;--semi-purple-8:239,206,240;--semi-purple-9:247,235,247;--semi-violet-0:64,27,119;--semi-violet-1:76,36,140;--semi-violet-2:88,46,160;--semi-violet-3:100,57,181;--semi-violet-4:114,70,201;--semi-violet-5:136,101,212;--semi-violet-6:162,136,223;--semi-violet-7:190,173,233;--semi-violet-8:221,212,244;--semi-violet-9:241,238,250;--semi-indigo-0:23,30,101;--semi-indigo-1:32,41,122;--semi-indigo-2:41,54,142;--semi-indigo-3:52,68,163;--semi-indigo-4:64,83,183;--semi-indigo-5:95,113,197;--semi-indigo-6:129,145,212;--semi-indigo-7:167,180,226;--semi-indigo-8:209,216,241;--semi-indigo-9:237,239,248;--semi-blue-0:5,49,112;--semi-blue-1:10,70,148;--semi-blue-2:19,92,184;--semi-blue-3:29,117,219;--semi-blue-4:41,144,255;--semi-blue-5:84,169,255;--semi-blue-6:127,193,255;--semi-blue-7:169,215,255;--semi-blue-8:212,236,255;--semi-blue-9:239,248,255;--semi-light-blue-0:0,55,97;--semi-light-blue-1:0,77,133;--semi-light-blue-2:3,102,169;--semi-light-blue-3:10,129,204;--semi-light-blue-4:19,159,240;--semi-light-blue-5:64,180,243;--semi-light-blue-6:110,200,246;--semi-light-blue-7:157,220,249;--semi-light-blue-8:206,238,252;--semi-light-blue-9:235,248,254;--semi-cyan-0:4,52,61;--semi-cyan-1:7,79,92;--semi-cyan-2:10,108,123;--semi-cyan-3:14,137,153;--semi-cyan-4:19,168,184;--semi-cyan-5:56,187,198;--semi-cyan-6:98,205,212;--semi-cyan-7:145,223,227;--semi-cyan-8:198,239,241;--semi-cyan-9:231,247,248;--semi-teal-0:2,60,57;--semi-teal-1:4,90,85;--semi-teal-2:7,119,111;--semi-teal-3:10,149,136;--semi-teal-4:14,179,161;--semi-teal-5:51,194,176;--semi-teal-6:94,209,193;--semi-teal-7:142,225,211;--semi-teal-8:196,240,232;--semi-teal-9:230,247,244;--semi-green-0:18,60,25;--semi-green-1:28,90,37;--semi-green-2:39,119,49;--semi-green-3:50,149,61;--semi-green-4:62,179,73;--semi-green-5:93,194,100;--semi-green-6:127,209,132;--semi-green-7:166,225,168;--semi-green-8:208,240,209;--semi-green-9:236,247,236;--semi-light-green-0:38,61,19;--semi-light-green-1:59,92,29;--semi-light-green-2:81,123,40;--semi-light-green-3:103,153,52;--semi-light-green-4:127,184,64;--semi-light-green-5:151,198,95;--semi-light-green-6:176,212,129;--semi-light-green-7:201,227,167;--semi-light-green-8:228,241,209;--semi-light-green-9:243,248,237;--semi-lime-0:49,70,3;--semi-lime-1:75,105,5;--semi-lime-2:103,141,9;--semi-lime-3:132,176,12;--semi-lime-4:162,211,17;--semi-lime-5:174,220,58;--semi-lime-6:189,229,102;--semi-lime-7:207,237,150;--semi-lime-8:229,246,201;--semi-lime-9:243,251,233;--semi-yellow-0:84,73,3;--semi-yellow-1:126,108,6;--semi-yellow-2:168,142,10;--semi-yellow-3:210,175,15;--semi-yellow-4:252,206,20;--semi-yellow-5:253,222,67;--semi-yellow-6:253,235,113;--semi-yellow-7:254,245,160;--semi-yellow-8:254,251,208;--semi-yellow-9:255,254,236;--semi-amber-0:81,46,9;--semi-amber-1:121,75,15;--semi-amber-2:161,107,22;--semi-amber-3:202,143,30;--semi-amber-4:242,183,38;--semi-amber-5:245,202,80;--semi-amber-6:247,219,122;--semi-amber-7:250,234,166;--semi-amber-8:252,246,210;--semi-amber-9:254,251,237;--semi-orange-0:85,31,3;--semi-orange-1:128,53,6;--semi-orange-2:170,80,10;--semi-orange-3:213,111,15;--semi-orange-4:255,146,20;--semi-orange-5:255,174,67;--semi-orange-6:255,199,114;--semi-orange-7:255,221,161;--semi-orange-8:255,239,208;--semi-orange-9:255,249,237;--semi-grey-0:28,31,35;--semi-grey-1:46,50,56;--semi-grey-2:65,70,76;--semi-grey-3:85,91,97;--semi-grey-4:107,112,117;--semi-grey-5:136,141,146;--semi-grey-6:167,171,176;--semi-grey-7:198,202,205;--semi-grey-8:230,232,234;--semi-grey-9:249,249,249;--semi-white:255,255,255;--semi-black:0,0,0}#root,#root[theme-mode=dark] .semi-always-light{--semi-color-white:rgba(var(--semi-white),1);--semi-color-black:rgba(var(--semi-black),1);--semi-color-primary:rgba(var(--semi-blue-5),1);--semi-color-primary-hover:rgba(var(--semi-blue-6),1);--semi-color-primary-active:rgba(var(--semi-blue-7),1);--semi-color-primary-disabled:rgba(var(--semi-blue-2),1);--semi-color-primary-light-default:rgba(var(--semi-blue-0),1);--semi-color-primary-light-hover:rgba(var(--semi-blue-1),1);--semi-color-primary-light-active:rgba(var(--semi-blue-2),1);--semi-color-secondary:rgba(var(--semi-light-blue-5),1);--semi-color-secondary-hover:rgba(var(--semi-light-blue-6),1);--semi-color-secondary-active:rgba(var(--semi-light-blue-7),1);--semi-color-secondary-disabled:rgba(var(--semi-light-blue-2),1);--semi-color-secondary-light-default:rgba(var(--semi-light-blue-0),1);--semi-color-secondary-light-hover:rgba(var(--semi-light-blue-1),1);--semi-color-secondary-light-active:rgba(var(--semi-light-blue-2),1);--semi-color-tertiary:rgba(var(--semi-grey-5),1);--semi-color-tertiary-hover:rgba(var(--semi-grey-6),1);--semi-color-tertiary-active:rgba(var(--semi-grey-7),1);--semi-color-tertiary-light-default:rgba(var(--semi-grey-0),1);--semi-color-tertiary-light-hover:rgba(var(--semi-grey-1),1);--semi-color-tertiary-light-active:rgba(var(--semi-grey-2),1);--semi-color-default:rgba(var(--semi-grey-0),1);--semi-color-default-hover:rgba(var(--semi-grey-1),1);--semi-color-default-active:rgba(var(--semi-grey-2),1);--semi-color-info:rgba(var(--semi-blue-5),1);--semi-color-info-hover:rgba(var(--semi-blue-6),1);--semi-color-info-active:rgba(var(--semi-blue-7),1);--semi-color-info-disabled:rgba(var(--semi-blue-2),1);--semi-color-info-light-default:rgba(var(--semi-blue-0),1);--semi-color-info-light-hover:rgba(var(--semi-blue-1),1);--semi-color-info-light-active:rgba(var(--semi-blue-2),1);--semi-color-success:rgba(var(--semi-green-5),1);--semi-color-success-hover:rgba(var(--semi-green-6),1);--semi-color-success-active:rgba(var(--semi-green-7),1);--semi-color-success-disabled:rgba(var(--semi-green-2),1);--semi-color-success-light-default:rgba(var(--semi-green-0),1);--semi-color-success-light-hover:rgba(var(--semi-green-1),1);--semi-color-success-light-active:rgba(var(--semi-green-2),1);--semi-color-danger:rgba(var(--semi-red-5),1);--semi-color-danger-hover:rgba(var(--semi-red-6),1);--semi-color-danger-active:rgba(var(--semi-red-7),1);--semi-color-danger-light-default:rgba(var(--semi-red-0),1);--semi-color-danger-light-hover:rgba(var(--semi-red-1),1);--semi-color-danger-light-active:rgba(var(--semi-red-2),1);--semi-color-warning:rgba(var(--semi-orange-5),1);--semi-color-warning-hover:rgba(var(--semi-orange-6),1);--semi-color-warning-active:rgba(var(--semi-orange-7),1);--semi-color-warning-light-default:rgba(var(--semi-orange-0),1);--semi-color-warning-light-hover:rgba(var(--semi-orange-1),1);--semi-color-warning-light-active:rgba(var(--semi-orange-2),1);--semi-color-focus-border:rgba(var(--semi-blue-5),1);--semi-color-disabled-text:rgba(var(--semi-grey-9),.35);--semi-color-disabled-border:rgba(var(--semi-grey-1),1);--semi-color-disabled-bg:rgba(var(--semi-grey-1),1);--semi-color-disabled-fill:rgba(var(--semi-grey-8),.04);--semi-color-shadow:rgba(var(--semi-black),.04);--semi-color-link:rgba(var(--semi-blue-5),1);--semi-color-link-hover:rgba(var(--semi-blue-6),1);--semi-color-link-active:rgba(var(--semi-blue-7),1);--semi-color-link-visited:rgba(var(--semi-blue-5),1);--semi-color-border:rgba(var(--semi-grey-9),.08);--semi-color-nav-bg:rgba(var(--semi-white),1);--semi-color-overlay-bg:rgba(22,22,26,.6);--semi-color-fill-0:rgba(var(--semi-grey-8),.05);--semi-color-fill-1:rgba(var(--semi-grey-8),.09);--semi-color-fill-2:rgba(var(--semi-grey-8),.13);--semi-color-bg-0:rgba(var(--semi-white),1);--semi-color-bg-1:rgba(var(--semi-white),1);--semi-color-bg-2:rgba(var(--semi-white),1);--semi-color-bg-3:rgba(var(--semi-white),1);--semi-color-bg-4:rgba(var(--semi-white),1);--semi-color-text-0:rgba(var(--semi-grey-9),1);--semi-color-text-1:rgba(var(--semi-grey-9),.8);--semi-color-text-2:rgba(var(--semi-grey-9),.62);--semi-color-text-3:rgba(var(--semi-grey-9),.35);--semi-shadow-elevated:0 0 1px rgba(0,0,0,.3),0 4px 14px rgba(0,0,0,.1);--semi-border-radius-extra-small:3px;--semi-border-radius-small:3px;--semi-border-radius-medium:6px;--semi-border-radius-large:12px;--semi-border-radius-circle:50%;--semi-border-radius-full:9999px;--semi-color-highlight-bg:rgba(var(--semi-yellow-4),1);--semi-color-highlight:rgba(var(--semi-black),1)}#root,#root .semi-always-dark,#root[theme-mode=dark],#root[theme-mode=dark] .semi-always-light{-webkit-font-smoothing:antialiased;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}#root .semi-always-dark,#root[theme-mode=dark]{--semi-color-white:#e4e7f5;--semi-color-black:rgba(var(--semi-black),1);--semi-color-primary:rgba(var(--semi-blue-5),1);--semi-color-primary-hover:rgba(var(--semi-blue-6),1);--semi-color-primary-active:rgba(var(--semi-blue-7),1);--semi-color-primary-disabled:rgba(var(--semi-blue-2),1);--semi-color-primary-light-default:rgba(var(--semi-blue-5),.2);--semi-color-primary-light-hover:rgba(var(--semi-blue-5),.3);--semi-color-primary-light-active:rgba(var(--semi-blue-5),.4);--semi-color-secondary:rgba(var(--semi-light-blue-5),1);--semi-color-secondary-hover:rgba(var(--semi-light-blue-6),1);--semi-color-secondary-active:rgba(var(--semi-light-blue-7),1);--semi-color-secondary-disabled:rgba(var(--semi-light-blue-2),1);--semi-color-secondary-light-default:rgba(var(--semi-light-blue-5),.2);--semi-color-secondary-light-hover:rgba(var(--semi-light-blue-5),.3);--semi-color-secondary-light-active:rgba(var(--semi-light-blue-5),.4);--semi-color-tertiary:rgba(var(--semi-grey-5),1);--semi-color-tertiary-hover:rgba(var(--semi-grey-6),1);--semi-color-tertiary-active:rgba(var(--semi-grey-7),1);--semi-color-tertiary-light-default:rgba(var(--semi-grey-5),.2);--semi-color-tertiary-light-hover:rgba(var(--semi-grey-5),.3);--semi-color-tertiary-light-active:rgba(var(--semi-grey-5),.4);--semi-color-default:rgba(var(--semi-grey-0),1);--semi-color-default-hover:rgba(var(--semi-grey-1),1);--semi-color-default-active:rgba(var(--semi-grey-2),1);--semi-color-info:rgba(var(--semi-blue-5),1);--semi-color-info-hover:rgba(var(--semi-blue-6),1);--semi-color-info-active:rgba(var(--semi-blue-7),1);--semi-color-info-disabled:rgba(var(--semi-blue-2),1);--semi-color-info-light-default:rgba(var(--semi-blue-5),.2);--semi-color-info-light-hover:rgba(var(--semi-blue-5),.3);--semi-color-info-light-active:rgba(var(--semi-blue-5),.4);--semi-color-success:rgba(var(--semi-green-5),1);--semi-color-success-hover:rgba(var(--semi-green-6),1);--semi-color-success-active:rgba(var(--semi-green-7),1);--semi-color-success-disabled:rgba(var(--semi-green-2),1);--semi-color-success-light-default:rgba(var(--semi-green-5),.2);--semi-color-success-light-hover:rgba(var(--semi-green-5),.3);--semi-color-success-light-active:rgba(var(--semi-green-5),.4);--semi-color-danger:rgba(var(--semi-red-5),1);--semi-color-danger-hover:rgba(var(--semi-red-6),1);--semi-color-danger-active:rgba(var(--semi-red-7),1);--semi-color-danger-light-default:rgba(var(--semi-red-5),.2);--semi-color-danger-light-hover:rgba(var(--semi-red-5),.3);--semi-color-danger-light-active:rgba(var(--semi-red-5),.4);--semi-color-warning:rgba(var(--semi-orange-5),1);--semi-color-warning-hover:rgba(var(--semi-orange-6),1);--semi-color-warning-active:rgba(var(--semi-orange-7),1);--semi-color-warning-light-default:rgba(var(--semi-orange-5),.2);--semi-color-warning-light-hover:rgba(var(--semi-orange-5),.3);--semi-color-warning-light-active:rgba(var(--semi-orange-5),.4);--semi-color-focus-border:rgba(var(--semi-blue-5),1);--semi-color-disabled-text:rgba(var(--semi-grey-9),.35);--semi-color-disabled-border:rgba(var(--semi-grey-1),1);--semi-color-disabled-bg:rgba(var(--semi-grey-1),1);--semi-color-disabled-fill:rgba(var(--semi-grey-8),.04);--semi-color-link:rgba(var(--semi-blue-5),1);--semi-color-link-hover:rgba(var(--semi-blue-6),1);--semi-color-link-active:rgba(var(--semi-blue-7),1);--semi-color-link-visited:rgba(var(--semi-blue-5),1);--semi-color-nav-bg:#232429;--semi-shadow-elevated:inset 0 0 0 1px hsla(0,0%,100%,.1),0 4px 14px rgba(0,0,0,.25);--semi-color-overlay-bg:rgba(22,22,26,.6);--semi-color-fill-0:rgba(var(--semi-white),.12);--semi-color-fill-1:rgba(var(--semi-white),.16);--semi-color-fill-2:rgba(var(--semi-white),.20);--semi-color-border:rgba(var(--semi-white),.08);--semi-color-shadow:rgba(var(--semi-black),.04);--semi-color-bg-0:#16161a;--semi-color-bg-1:#232429;--semi-color-bg-2:#35363c;--semi-color-bg-3:#43444a;--semi-color-bg-4:#4f5159;--semi-color-text-0:rgba(var(--semi-grey-9),1);--semi-color-text-1:rgba(var(--semi-grey-9),.8);--semi-color-text-2:rgba(var(--semi-grey-9),.6);--semi-color-text-3:rgba(var(--semi-grey-9),.35);--semi-border-radius-extra-small:3px;--semi-border-radius-small:3px;--semi-border-radius-medium:6px;--semi-border-radius-large:12px;--semi-border-radius-circle:50%;--semi-border-radius-full:9999px;--semi-color-highlight-bg:rgba(var(--semi-yellow-2),1);--semi-color-highlight:rgba(var(--semi-white),1)}.semi-light-scrollbar ::-webkit-scrollbar,.semi-light-scrollbar::-webkit-scrollbar{height:8px;width:8px}.semi-light-scrollbar ::-webkit-scrollbar-track,.semi-light-scrollbar::-webkit-scrollbar-track{background:transparent}.semi-light-scrollbar ::-webkit-scrollbar-corner,.semi-light-scrollbar::-webkit-scrollbar-corner{background-color:transparent}.semi-light-scrollbar ::-webkit-scrollbar-thumb,.semi-light-scrollbar::-webkit-scrollbar-thumb{background:transparent;border-radius:6px;-webkit-transition:all 1s;transition:all 1s}.semi-light-scrollbar :hover::-webkit-scrollbar-thumb,.semi-light-scrollbar:hover::-webkit-scrollbar-thumb{background:var(--semi-color-fill-2)}.semi-light-scrollbar ::-webkit-scrollbar-thumb:hover,.semi-light-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--semi-color-fill-1)}.semi-avatar{align-items:center;display:inline-flex;justify-content:center;overflow:hidden;position:relative;text-align:center;vertical-align:middle;white-space:nowrap}.semi-avatar:focus-visible{outline:2px solid var(--semi-color-primary-light-active)}.semi-avatar-focus{outline:2px solid var(--semi-color-primary-light-active)}.semi-avatar-no-focus-visible:focus-visible{outline:none}.semi-avatar .semi-avatar-label{align-items:center;display:flex;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;font-weight:600;line-height:20px}.semi-avatar-content{-webkit-user-select:none;user-select:none}.semi-avatar-extra-extra-small{border-radius:3px;height:20px;width:20px}.semi-avatar-extra-extra-small .semi-avatar-content{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:center;transform-origin:center}.semi-avatar-extra-extra-small .semi-avatar-label{font-size:10px;line-height:15px}.semi-avatar-extra-small{border-radius:3px;height:24px;width:24px}.semi-avatar-extra-small .semi-avatar-content{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:center;transform-origin:center}.semi-avatar-extra-small .semi-avatar-label{font-size:10px;line-height:15px}.semi-avatar-small{border-radius:3px;height:32px;width:32px}.semi-avatar-small .semi-avatar-label{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:12px;line-height:16px}.semi-avatar-default{border-radius:3px;height:40px;width:40px}.semi-avatar-default .semi-avatar-label{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:18px;line-height:24px}.semi-avatar-medium{border-radius:3px;height:48px;width:48px}.semi-avatar-medium .semi-avatar-label{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:20px;line-height:28px}.semi-avatar-large{border-radius:6px;height:72px;width:72px}.semi-avatar-large .semi-avatar-label{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:32px;line-height:44px}.semi-avatar-extra-large{border-radius:12px;height:128px;width:128px}.semi-avatar-extra-large .semi-avatar-label{font-size:64px;line-height:77px}.semi-avatar-circle{border-radius:var(--semi-border-radius-circle)}.semi-avatar-image{background-color:initial}.semi-avatar>img{display:block;height:100%;object-fit:cover;width:100%}.semi-avatar-hover{height:100%;left:0;position:absolute;top:0;width:100%}.semi-avatar:hover{cursor:pointer}.semi-avatar-group{display:inline-block}.semi-avatar-group .semi-avatar{box-sizing:border-box}.semi-avatar-group .semi-avatar:first-child{margin-left:0}.semi-avatar-group .semi-avatar-extra-large{border:3px solid var(--semi-color-bg-1);margin-left:-32px}.semi-avatar-group .semi-avatar-large{border:3px solid var(--semi-color-bg-1);margin-left:-18px}.semi-avatar-group .semi-avatar-default,.semi-avatar-group .semi-avatar-medium,.semi-avatar-group .semi-avatar-small{border:2px solid var(--semi-color-bg-1);margin-left:-12px}.semi-avatar-group .semi-avatar-extra-small{border:1px solid var(--semi-color-bg-1);margin-left:-10px}.semi-avatar-group .semi-avatar-extra-extra-small{border:1px solid var(--semi-color-bg-1);margin-left:-4px}.semi-avatar-group .semi-avatar-item-start-0{z-index:100}.semi-avatar-group .semi-avatar-item-end-0{z-index:80}.semi-avatar-group .semi-avatar-item-start-1{z-index:99}.semi-avatar-group .semi-avatar-item-end-1{z-index:81}.semi-avatar-group .semi-avatar-item-start-2{z-index:98}.semi-avatar-group .semi-avatar-item-end-2{z-index:82}.semi-avatar-group .semi-avatar-item-start-3{z-index:97}.semi-avatar-group .semi-avatar-item-end-3{z-index:83}.semi-avatar-group .semi-avatar-item-start-4{z-index:96}.semi-avatar-group .semi-avatar-item-end-4{z-index:84}.semi-avatar-group .semi-avatar-item-start-5{z-index:95}.semi-avatar-group .semi-avatar-item-end-5{z-index:85}.semi-avatar-group .semi-avatar-item-start-6{z-index:94}.semi-avatar-group .semi-avatar-item-end-6{z-index:86}.semi-avatar-group .semi-avatar-item-start-7{z-index:93}.semi-avatar-group .semi-avatar-item-end-7{z-index:87}.semi-avatar-group .semi-avatar-item-start-8{z-index:92}.semi-avatar-group .semi-avatar-item-end-8{z-index:88}.semi-avatar-group .semi-avatar-item-start-9{z-index:91}.semi-avatar-group .semi-avatar-item-end-9{z-index:89}.semi-avatar-group .semi-avatar-item-end-10,.semi-avatar-group .semi-avatar-item-start-10{z-index:90}.semi-avatar-group .semi-avatar-item-start-11{z-index:89}.semi-avatar-group .semi-avatar-item-end-11{z-index:91}.semi-avatar-group .semi-avatar-item-start-12{z-index:88}.semi-avatar-group .semi-avatar-item-end-12{z-index:92}.semi-avatar-group .semi-avatar-item-start-13{z-index:87}.semi-avatar-group .semi-avatar-item-end-13{z-index:93}.semi-avatar-group .semi-avatar-item-start-14{z-index:86}.semi-avatar-group .semi-avatar-item-end-14{z-index:94}.semi-avatar-group .semi-avatar-item-start-15{z-index:85}.semi-avatar-group .semi-avatar-item-end-15{z-index:95}.semi-avatar-group .semi-avatar-item-start-16{z-index:84}.semi-avatar-group .semi-avatar-item-end-16{z-index:96}.semi-avatar-group .semi-avatar-item-start-17{z-index:83}.semi-avatar-group .semi-avatar-item-end-17{z-index:97}.semi-avatar-group .semi-avatar-item-start-18{z-index:82}.semi-avatar-group .semi-avatar-item-end-18{z-index:98}.semi-avatar-group .semi-avatar-item-start-19{z-index:81}.semi-avatar-group .semi-avatar-item-end-19{z-index:99}.semi-avatar-group .semi-avatar-item-start-20{z-index:80}.semi-avatar-group .semi-avatar-item-end-20{z-index:100}.semi-avatar-group .semi-avatar-item-more{background-color:rgba(var(--semi-grey-5),1)}.semi-avatar-amber{background-color:rgba(var(--semi-amber-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-blue{background-color:rgba(var(--semi-blue-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-cyan{background-color:rgba(var(--semi-cyan-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-green{background-color:rgba(var(--semi-green-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-grey{background-color:rgba(var(--semi-grey-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-indigo{background-color:rgba(var(--semi-indigo-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-light-blue{background-color:rgba(var(--semi-light-blue-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-light-green{background-color:rgba(var(--semi-light-green-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-lime{background-color:rgba(var(--semi-lime-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-orange{background-color:rgba(var(--semi-orange-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-pink{background-color:rgba(var(--semi-pink-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-purple{background-color:rgba(var(--semi-purple-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-red{background-color:rgba(var(--semi-red-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-teal{background-color:rgba(var(--semi-teal-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-violet{background-color:rgba(var(--semi-violet-3),1);color:rgba(var(--semi-white),1)}.semi-avatar-yellow{background-color:rgba(var(--semi-yellow-3),1);color:rgba(var(--semi-white),1)}.semi-portal-rtl .semi-avatar,.semi-rtl .semi-avatar{direction:rtl}.semi-portal-rtl .semi-avatar-extra-extra-small .semi-avatar-content,.semi-portal-rtl .semi-avatar-extra-small .semi-avatar-content,.semi-rtl .semi-avatar-extra-extra-small .semi-avatar-content,.semi-rtl .semi-avatar-extra-small .semi-avatar-content{-webkit-transform:scale(.8);transform:scale(.8)}.semi-portal-rtl .semi-avatar-hover,.semi-rtl .semi-avatar-hover{left:auto;right:0}.semi-portal-rtl .semi-avatar-group,.semi-rtl .semi-avatar-group{direction:rtl}.semi-portal-rtl .semi-avatar-group .semi-avatar:first-child,.semi-rtl .semi-avatar-group .semi-avatar:first-child{margin-left:auto;margin-right:0}.semi-portal-rtl .semi-avatar-group .semi-avatar-extra-large,.semi-rtl .semi-avatar-group .semi-avatar-extra-large{margin-left:auto;margin-right:-32px}.semi-portal-rtl .semi-avatar-group .semi-avatar-large,.semi-rtl .semi-avatar-group .semi-avatar-large{margin-left:auto;margin-right:-18px}.semi-portal-rtl .semi-avatar-group .semi-avatar-medium,.semi-portal-rtl .semi-avatar-group .semi-avatar-small,.semi-rtl .semi-avatar-group .semi-avatar-medium,.semi-rtl .semi-avatar-group .semi-avatar-small{margin-left:auto;margin-right:-12px}.semi-portal-rtl .semi-avatar-group .semi-avatar-extra-small,.semi-rtl .semi-avatar-group .semi-avatar-extra-small{margin-left:auto;margin-right:-10px}.semi-portal-rtl .semi-avatar-group .semi-avatar-extra-extra-small,.semi-rtl .semi-avatar-group .semi-avatar-extra-extra-small{margin-left:auto;margin-right:-4px}@-webkit-keyframes semi-popover-zoomIn{0%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}50%{opacity:1}}@keyframes semi-popover-zoomIn{0%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}50%{opacity:1}}@-webkit-keyframes semi-popover-zoomOut{0%{opacity:1}60%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:0}}@keyframes semi-popover-zoomOut{0%{opacity:1}60%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:0}}.semi-popover-wrapper{background-color:var(--semi-color-bg-3);border-radius:var(--semi-border-radius-medium);box-shadow:var(--semi-shadow-elevated);font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;opacity:0;position:relative;z-index:1030}.semi-popover-wrapper-show{opacity:1}.semi-popover-trigger{display:inline-block;height:auto;width:auto}.semi-popover-title{border-bottom:1px solid var(--semi-color-border);padding:8px}.semi-popover-confirm{position:absolute}.semi-popover-with-arrow{box-sizing:border-box;padding:12px}.semi-popover-animation-show{-webkit-animation:semi-popover-zoomIn .1s cubic-bezier(.215,.61,.355,1);animation:semi-popover-zoomIn .1s cubic-bezier(.215,.61,.355,1)}.semi-popover-animation-hide{-webkit-animation:semi-popover-zoomOut .1s cubic-bezier(.215,.61,.355,1);animation:semi-popover-zoomOut .1s cubic-bezier(.215,.61,.355,1)}.semi-popover-wrapper .semi-popover-icon-arrow{color:inherit;height:8px;position:absolute;width:24px}.semi-popover-wrapper[x-placement=top] .semi-popover-icon-arrow{bottom:-7px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.semi-popover-wrapper[x-placement=top] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=top].semi-popover-with-arrow{min-width:36px}.semi-popover-wrapper[x-placement=topLeft] .semi-popover-icon-arrow{bottom:-7px;left:6px}.semi-popover-wrapper[x-placement=topLeft] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=topLeft].semi-popover-with-arrow{min-width:36px}.semi-popover-wrapper[x-placement=topRight] .semi-popover-icon-arrow{bottom:-7px;right:6px}.semi-popover-wrapper[x-placement=topRight] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=topRight].semi-popover-with-arrow{min-width:36px}.semi-popover-wrapper[x-placement=leftTop] .semi-popover-icon-arrow{height:24px;right:-7px;top:6px;width:8px}.semi-popover-wrapper[x-placement=leftTop] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=leftTop].semi-popover-with-arrow{min-height:36px}.semi-popover-wrapper[x-placement=left] .semi-popover-icon-arrow{height:24px;right:-7px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);width:8px}.semi-popover-wrapper[x-placement=left] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=left].semi-popover-with-arrow{min-height:36px}.semi-popover-wrapper[x-placement=leftBottom] .semi-popover-icon-arrow{bottom:6px;height:24px;right:-7px;width:8px}.semi-popover-wrapper[x-placement=leftBottom] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=leftBottom].semi-popover-with-arrow{min-height:36px}.semi-popover-wrapper[x-placement=rightTop] .semi-popover-icon-arrow{height:24px;left:-7px;top:6px;-webkit-transform:rotate(180deg);transform:rotate(180deg);width:8px}.semi-popover-wrapper[x-placement=rightTop] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=rightTop].semi-popover-with-arrow{min-height:36px}.semi-popover-wrapper[x-placement=right] .semi-popover-icon-arrow{height:24px;left:-7px;top:50%;-webkit-transform:translateY(-50%) rotate(180deg);transform:translateY(-50%) rotate(180deg);width:8px}.semi-popover-wrapper[x-placement=right] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=right].semi-popover-with-arrow{min-height:36px}.semi-popover-wrapper[x-placement=rightBottom] .semi-popover-icon-arrow{bottom:6px;height:24px;left:-7px;-webkit-transform:rotate(180deg);transform:rotate(180deg);width:8px}.semi-popover-wrapper[x-placement=rightBottom] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=rightBottom].semi-popover-with-arrow{min-height:36px}.semi-popover-wrapper[x-placement=bottomLeft] .semi-popover-icon-arrow{left:6px;top:-7px;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.semi-popover-wrapper[x-placement=bottomLeft] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=bottomLeft].semi-popover-with-arrow{min-width:36px}.semi-popover-wrapper[x-placement=bottom] .semi-popover-icon-arrow{left:50%;top:-7px;-webkit-transform:translateX(-50%) rotate(180deg);transform:translateX(-50%) rotate(180deg)}.semi-popover-wrapper[x-placement=bottom] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=bottom].semi-popover-with-arrow{min-width:36px}.semi-popover-wrapper[x-placement=bottomRight] .semi-popover-icon-arrow{right:6px;top:-7px;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.semi-popover-wrapper[x-placement=bottomRight] .semi-popover-with-arrow,.semi-popover-wrapper[x-placement=bottomRight].semi-popover-with-arrow{min-width:36px}.semi-popover.semi-popover-rtl{direction:rtl}@-webkit-keyframes semi-tooltip-zoomIn{0%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}50%{opacity:1}}@keyframes semi-tooltip-zoomIn{0%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}50%{opacity:1}}@-webkit-keyframes semi-tooltip-bounceIn{0%{opacity:0;-webkit-transform:scale(.6);transform:scale(.6)}70%{opacity:1;-webkit-transform:scale(1.01);transform:scale(1.01)}to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes semi-tooltip-bounceIn{0%{opacity:0;-webkit-transform:scale(.6);transform:scale(.6)}70%{opacity:1;-webkit-transform:scale(1.01);transform:scale(1.01)}to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes semi-tooltip-zoomOut{0%{opacity:1}60%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:0}}@keyframes semi-tooltip-zoomOut{0%{opacity:1}60%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:0}}.semi-tooltip-wrapper{background-color:rgba(var(--semi-grey-7),1);border-radius:var(--semi-border-radius-medium);color:var(--semi-color-bg-0);font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;left:0;line-height:20px;max-width:240px;opacity:0;padding:8px 12px;position:relative;top:0}.semi-tooltip-wrapper-show{opacity:1}.semi-tooltip-trigger{display:inline-block;height:auto;width:auto}.semi-tooltip-with-arrow{align-items:center;box-sizing:border-box;display:flex;justify-content:center}.semi-tooltip-animation-show{-webkit-animation:semi-tooltip-zoomIn .1s cubic-bezier(.215,.61,.355,1);animation:semi-tooltip-zoomIn .1s cubic-bezier(.215,.61,.355,1)}.semi-tooltip-animation-hide{-webkit-animation:semi-tooltip-zoomOut .1s cubic-bezier(.215,.61,.355,1);animation:semi-tooltip-zoomOut .1s cubic-bezier(.215,.61,.355,1)}.semi-tooltip-wrapper .semi-tooltip-icon-arrow{color:rgba(var(--semi-grey-7),1);height:7px;position:absolute;width:24px}.semi-tooltip-wrapper[x-placement=top] .semi-tooltip-icon-arrow{bottom:-6px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.semi-tooltip-wrapper[x-placement=top] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=top].semi-tooltip-with-arrow{min-width:36px}.semi-tooltip-wrapper[x-placement=topLeft] .semi-tooltip-icon-arrow{bottom:-6px;left:6px}.semi-tooltip-wrapper[x-placement=topLeft] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=topLeft].semi-tooltip-with-arrow{min-width:36px}.semi-tooltip-wrapper[x-placement=topRight] .semi-tooltip-icon-arrow{bottom:-6px;right:6px}.semi-tooltip-wrapper[x-placement=topRight] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=topRight].semi-tooltip-with-arrow{min-width:36px}.semi-tooltip-wrapper[x-placement=leftTop] .semi-tooltip-icon-arrow{height:24px;right:-6px;top:5px;width:7px}.semi-tooltip-wrapper[x-placement=leftTop] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=leftTop].semi-tooltip-with-arrow{min-height:34px}.semi-tooltip-wrapper[x-placement=left] .semi-tooltip-icon-arrow{height:24px;right:-6px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);width:7px}.semi-tooltip-wrapper[x-placement=left] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=left].semi-tooltip-with-arrow{min-height:34px}.semi-tooltip-wrapper[x-placement=leftBottom] .semi-tooltip-icon-arrow{bottom:5px;height:24px;right:-6px;width:7px}.semi-tooltip-wrapper[x-placement=leftBottom] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=leftBottom].semi-tooltip-with-arrow{min-height:34px}.semi-tooltip-wrapper[x-placement=rightTop] .semi-tooltip-icon-arrow{height:24px;left:-6px;top:5px;-webkit-transform:rotate(180deg);transform:rotate(180deg);width:7px}.semi-tooltip-wrapper[x-placement=rightTop] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=rightTop].semi-tooltip-with-arrow{min-height:34px}.semi-tooltip-wrapper[x-placement=right] .semi-tooltip-icon-arrow{height:24px;left:-6px;top:50%;-webkit-transform:translateY(-50%) rotate(180deg);transform:translateY(-50%) rotate(180deg);width:7px}.semi-tooltip-wrapper[x-placement=right] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=right].semi-tooltip-with-arrow{min-height:34px}.semi-tooltip-wrapper[x-placement=rightBottom] .semi-tooltip-icon-arrow{bottom:5px;height:24px;left:-6px;-webkit-transform:rotate(180deg);transform:rotate(180deg);width:7px}.semi-tooltip-wrapper[x-placement=rightBottom] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=rightBottom].semi-tooltip-with-arrow{min-height:34px}.semi-tooltip-wrapper[x-placement=bottomLeft] .semi-tooltip-icon-arrow{left:6px;top:-6px;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.semi-tooltip-wrapper[x-placement=bottomLeft] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=bottomLeft].semi-tooltip-with-arrow{min-width:36px}.semi-tooltip-wrapper[x-placement=bottom] .semi-tooltip-icon-arrow{left:50%;top:-6px;-webkit-transform:translateX(-50%) rotate(180deg);transform:translateX(-50%) rotate(180deg)}.semi-tooltip-wrapper[x-placement=bottom] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=bottom].semi-tooltip-with-arrow{min-width:36px}.semi-tooltip-wrapper[x-placement=bottomRight] .semi-tooltip-icon-arrow{right:6px;top:-6px;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.semi-tooltip-wrapper[x-placement=bottomRight] .semi-tooltip-with-arrow,.semi-tooltip-wrapper[x-placement=bottomRight].semi-tooltip-with-arrow{min-width:36px}.semi-portal-rtl .semi-tooltip-wrapper,.semi-rtl .semi-tooltip-wrapper{direction:rtl;left:auto;padding-left:12px;padding-right:12px;right:0}.semi-portal{left:0;position:absolute;top:0;width:100%;z-index:1}.semi-portal-inner{background-color:initial;min-width:-webkit-max-content;min-width:max-content;position:absolute}.user-info-container{position:relative}.user-info-content{background-color:#fff;border-radius:10px;padding-bottom:20px;width:320px}.user-info{display:inline-block;min-width:50px}.img-box{height:140px}.img-box,.img-box>img{border-top-left-radius:8px;border-top-right-radius:8px;width:100%}.img-box>img{height:100%}.info-box{line-height:26px;padding:0 16px}.username{font-size:20px;font-weight:600;line-height:28px;margin-bottom:10px}.signature{color:#646a73;margin-bottom:16px}.avatar{border-radius:50%;height:100px;margin-top:-50px;width:100px}.info{display:flex;margin-bottom:16px;width:100%}.desc{color:#646a73;flex:0 0 72px;width:72px}.info-text{color:#1f2329;flex:1 1}a{color:#3370ff;text-decoration:none}.App-header{align-items:center;background-color:#282c34;color:#fff;display:flex;flex-direction:column;font-size:calc(10px + 2vmin);justify-content:center;min-height:100vh}`;
// 存放插件的自定义标签
export const webComponentName = "plugin-template";
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { webComponentName } from "./constants";
const renderDocument = document.querySelector(webComponentName);
const root = ReactDOM.createRoot(
renderDocument?.shadowRoot?.getElementById("root") ??
document.getElementById("root")
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
import { useEffect, useMemo, useState } from "react";
import { Popover, Avatar } from "@douyinfe/semi-ui";
import { webComponentName } from "./constants";
import "./App.css";
function App(props) {
const { userId } = props;
const [userInfo, setUserInfo] = useState({});
useEffect(() => {
// 根据userId获得相关信息
const getUserData = async () => {
await setTimeout(() => {}, 1000);
setUserInfo({
userId,
username: "木子水吉",
bgSrc:
"https://github.com/muzishuiji/demo_resource/blob/main/web_component/bg.png?raw=true",
avatar:
"https://github.com/muzishuiji/demo_resource/blob/main/web_component/avatar.jpeg?raw=true",
signature: "想清楚,表达清楚~",
department: "Lark Productivity Engineering-Frontend",
okr:
"https://xxx.xxx.cn/okr/user/xxx",
email: "xxx.xxx@xxx.com",
city: "杭州",
people:
"https://xxx.xxx.net/person"
});
};
getUserData();
}, [userId]);
const content = useMemo(
() => (
<div className="App">
<div className="img-box">
<img alt="背景图" src={userInfo.bgSrc} />
</div>
<div className="info-box">
<img className="avatar" alt="背景图" src={userInfo.avatar} />
<div className="username">{userInfo.username}</div>
<div className="signature">{userInfo.signature}</div>
<div className="info">
<div className="desc">部门</div>
<div className="info-text">{userInfo.department}</div>
</div>
<div className="info">
<div className="desc">OKR</div>
<div className="info-text">
<a href={userInfo.okr}>查看详情</a>
</div>
</div>
<div className="info">
<div className="desc">Email</div>
<div className="info-text">{userInfo.email}</div>
</div>
<div className="info">
<div className="desc">城市</div>
<div className="info-text">{userInfo.city}</div>
</div>
<div className="info">
<div className="desc">People</div>
<div className="info-text">
<a href={userInfo.okr}>查看详情</a>
</div>
</div>
</div>
</div>
),
[userInfo]
);
return (
<div className="user-info-container">
<Popover
autoAdjustOverflow={true}
content={content}
contentClassName="user-info-content"
getPopupContainer={() =>
document
.querySelector(webComponentName)
?.shadowRoot?.getElementById("root") ??
document.querySelector(".user-info-container")
}
>
<span className="user-info">
<Avatar src={userInfo.avatar} />
{userInfo.username}
</span>
</Popover>
</div>
);
}
export default App;
在学习过程中的一些发现,结合Web Component
的Custom Elements
和Shadow DOM
确实可以提供一个比较良好的脚本和 CSS 的隔离环境,但使用Web Component
做隔离的一些缺点:
Web Component
的 DOM 隔离是把双刃剑,如果是很多模块共用的样式,你需要分别处理加载到对应的子模块使其生效,一个比较明显的是插件内部如果使用除 semi 外的库,在 body 上设置相关 style 会不生效,需要提取出来设置到 shadow dom 的最上层容器上或者手动插入到外层的 body 上。querySelectorAll
等方法上下文也是单独的,模块之间不能互相select
到对方的dom
,需要先获取其他模块的Web Component
节点,再在其上使用querySelectorAll
。Web Component 在微前端中的简单实践完整代码:https://codesandbox.io/s/ren-yuan-qia-pian-zu-jian-forked-z9yyb7?file=/src/App.tsx
import { useState } from "react";
import { Tabs, TabPane } from "@douyinfe/semi-ui";
import "./reactTemplate.js";
import "./vueTemplate.js";
import "./App.css";
function App() {
const [userId] = useState("123456789");
return (
<>
<p>
实际使用过程,会根据appid动态获取插件资源的地址,从而请求对应资源的地址
</p>
<Tabs type="line">
<TabPane tab="React App" itemKey="1">
<div className="app-container">
<react-app userId={userId} appid="11111"></react-app>
</div>
</TabPane>
<TabPane tab="快速起步" itemKey="2">
<div className="app-container">
<vue-app appid="22222"></vue-app>
</div>
</TabPane>
<TabPane tab="帮助" itemKey="3">
<div className="app-container">
<flutter-app appid="3333">暂不支持,敬请期待~</flutter-app>
</div>
</TabPane>
</Tabs>
</>
);
}
export default App;
// 偷个懒,cssText就不请求了
import { cssContent } from "./constants";
class ReactPluginTemplate extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: "open"
});
// 给插件搞一个容器
const container = document.createElement("div");
container.id = "root";
// 如果有modal需要作为getPopupcontainer
container.style.position = "relative";
// container.innerHTML = divText;
this.shadow.appendChild(container);
// 插件的style设置不会影响到外部
const style = document.createElement("style");
style.innerText =
"body{backgrount-color: red;}#root{ --semi-shadow-elevated: 0 0 1px rgba(0, 0, 0, .3), 0 4px 14px rgba(0, 0, 0, .1);}";
const styleNode = document.createTextNode(cssContent.toString());
style.appendChild(styleNode);
this.shadow.appendChild(style);
}
connectedCallback() {
// 动态插入插件的打包资源
const script = document.createElement("script");
script.type = "text/javascript";
fetch(
"https://raw.githubusercontent.com/muzishuiji/demo_resource/main/web_component/bundle.js"
)
.then((response) => {
response.text().then((data) => {
script.innerText = `(function (){let __temp_exports__={};with({exports:__temp_exports__}){console.log(document);${data.replace(
/[\r\n]*/[/*](?=# sourceMap "\r\n]*/[/*").*[\r\n]*/g,
""
)};}return __temp_exports__;})();`;
this.shadow?.appendChild(script);
});
})
.catch((err) => {
console.log(err, "err-----");
});
}
// observedAttributes,定义特定属性变化时,触发attributeChangedCallback函数
static get observedAttributes() {
return ["userId"];
}
}
if (!window.customElements.get("react-app")) {
window.customElements.define("react-app", ReactPluginTemplate);
}
import { vueCssContent } from "./constants";
class VuePluginTemplate extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({
mode: "open"
});
// 给插件搞一个容器
const container = document.createElement("div");
container.id = "root";
container.style.position = "relative";
// container.innerHTML = divText;
this.shadow.appendChild(container);
// 插件的style设置不会影响到外部
const style = document.createElement("style");
const styleNode = document.createTextNode(vueCssContent.toString());
style.appendChild(styleNode);
this.shadow.appendChild(style);
}
connectedCallback() {
// 动态插入插件的打包资源
const script = document.createElement("script");
script.type = "text/javascript";
fetch(
"https://raw.githubusercontent.com/muzishuiji/demo_resource/main/web_component/app.js"
)
.then((response) => {
response.text().then((data) => {
script.innerText = `(function (){${data.replace(
/[\r\n]*/[/*](?=# sourceMap "\r\n]*/[/*").*[\r\n]*/g,
""
)};})();`;
this.shadow?.appendChild(script);
});
})
.catch((err) => {
console.log(err, "fetch err-----");
});
const chunkScript = document.createElement("script");
chunkScript.type = "text/javascript";
fetch(
"https://raw.githubusercontent.com/muzishuiji/demo_resource/main/web_component/chunk.js"
)
.then((response) => {
response.text().then((data) => {
chunkScript.innerText = `(function (){${data.replace(
/[\r\n]*/[/*](?=# sourceMap "\r\n]*/[/*").*[\r\n]*/g,
""
)};})();`;
this.shadow?.appendChild(chunkScript);
});
})
.catch((err) => {
console.log(err, "fetch err-----");
});
}
// observedAttributes,定义特定属性变化时,触发attributeChangedCallback函数
static get observedAttributes() {
return ["userId"];
}
// 与observedAttributes结合使用
attributeChangedCallback(name, oldValue, newValue) {
if (name === "userId" && oldValue !== newValue) {
}
}
}
if (!window.customElements.get("vue-app")) {
window.customElements.define("vue-app", VuePluginTemplate);
}
// 内容过多省略,前往demo查看完整的css设置
export const cssContent = ``;
export const vueCssContent =``;
在飞书,飞书 doc 等很多网站上都能看到的人员卡片,理论上可以采取类似的实现,使用 Web Component 开发,然后打包成一个独立的 js 文件(类似于 JS SDK),供给各业务方接入。这里就不做详细的探索了,感兴趣的可以试试。
一个简单的想法,感觉可以基于 Web Component 来实现跨框架的 UI 组件库,但还实践过,感觉可以实现一个 semi design 练练手~
Web Component 与框架相比的优势在哪里?
Web Component 作为 Web W3C 规范的一部分,被浏览器原生支持,使用时,不需要像 React.js、Vue.js 等引入大体积的 Runtime 文件,所有组件声明、实例化均由浏览器负责,使得最终编译的体积足够小。
Web Component 已经被各大框架支持,React 和 Vue 都在自己的官方文档中着重介绍了与 Web Comonent 的相互调用。使用时,可像普通组件一样引入 Web Component 模块,轻松和已有框架整合。
Web Component 是 web 规范的一部分,未来应当被大家所熟知,不像框架那样有那么多的概念,当模块更换维护人员,不会有很高的学习成本。
Web Component 的缺点有哪些?
Custom Element 定义时使用 ES6 的 Class,无法传递 ES5 的构造函数,需要额外引入 Polyfill 解决问题;
一旦定义无法撤回
无法使用不同的 ES6 Class 定义同名 CustomElement,仅首次定义生效;
需要解决多团队协作的 CustomElement 命名冲突的问题;
状态管理
因为没有 state 的概念,Web Component 组件的内部属性的变化需要监听属性变化的回调事件,手动处理重渲染,心智负担可能会略重一些。
感觉 Web Component 想要出线,单纯依赖原生的 API 是远远不够的,能颠覆框架的必然是框架,拥有一个框架需要具备的基础生态(路由解决方案、状态管理、dom 性能优化等)。
Lit[3] 是 Google 出品的一个用于构建快速、轻量级的 Web Components 库。Lit 的核心是一个消除样板代码的组件基类,它提供reactive state
、 scoped styles
和一个小巧、快速、且富有表现力的declarative template system
。我们可以基于这个库来实现semi design
那样的组件库。
简单使用示例:https://codesandbox.io/s/sad-sinoussi-cvlnob?file=/index.html:0-228
<html>
<head>
<title>Lit TypeScript Template</title>
<meta charset="UTF-8" />
</head>
<body>
<main-container></main-container>
<script type="module" src="src/mainContainer.ts"></script>
</body>
</html>
// src/mainContainer.ts
import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
@customElement('main-container')
export class LitApp extends LitElement {
static styles = css`
:host {
width: 100px;
height: 100px;
color: red;
}
`;
render() {
return html`
<div>
Hello from Muzishuiji!
</div>
`;
}
}
Omi[4] 是一款基于 Web Component 的相关特性开发的前端跨框架跨平台框架,任何框架可以使用 Omi 自定义元素。感兴趣的朋友可以自行去了解~
[1]Web Components | MDN: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components
[2]Using shadow DOM - Web Components | MDN: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM
[3] Lit: https://lit.dev/docs/
[4]Omi: https://github.com/Tencent/omi/blob/master/README.CN.md
本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/mLXre4hdwcUX19Xq0qHGVw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。