如何添加样式或 class 名称并从 Web 组件中删除其他元素?
How to add style or class name and remove other element from web component?
我有两个组件,一个是 parent 组件,另一个是 child 组件。现在parent里面有两个children。 child 单击以在其中添加样式,我需要删除其他 children 中的样式。所以一次只有一个 child 会保持这种风格。怎么做?
LiveDemo - 点击按钮。我无法移除样式。
这是我的代码:
class Parent extends HTMLElement {
shadowRoot;
constructor(){
super();
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback(){
this.render();
}
render() {
this.shadowRoot.innerHTML = `<div>
<children-holder></children-holder>
<children-holder></children-holder>
<children-holder></children-holder>
</div>`
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
shadowRoot;
constructor(){
super()
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback(){
this.render();
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.shadowRoot.querySelector('button').style.border = "";
this.shadowRoot.querySelector('button').style.border = "3px solid red";
})
}
render() {
this.shadowRoot.innerHTML = `
<div><button class="button">Click me!</button></div>`
}
}
customElements.define('children-holder', Children);
您可以通过多种方式实现您想要的行为,我将描述其中的两种方式:
CSS-仅:
当您单击一个按钮时,它会收到 CSS focus
状态。所以使用 css
button:focus {
border: 3px solid red;
}
只会给最近点击的按钮加边框。单击屏幕上的任何其他位置时,focus
状态将被移除。
JS解决方案
单独的影子根使得使用 JS 以优雅的方式遍历所有按钮有点困难,但这应该可以解决问题:
const button = this.shadowRoot.querySelector('button');
button.addEventListener('click', () => {
const parentShadowRoot = this.shadowRoot.host.getRootNode();
const childrenHolders = parentShadowRoot.querySelectorAll('children-holder');
childrenHolders.forEach(holder => {
const button = holder.shadowRoot.querySelector('button');
button.style.border = "";
})
button.style.border = "3px solid red";
})
您可以先检索所有按钮的同级按钮以及按钮本身,然后您可以移除所有按钮的边框,最后,将红色边框添加到被单击的按钮。
使用 parentNode.children
.
检索单击的按钮及其兄弟按钮
您将获得 HTMLCollection
个按钮,您现在可以在这些按钮上使用 Array.from 来获得一个新的浅层-复制您的 HTMLCollection 数组,您现在可以对其进行迭代。
最后,您现在可以删除所有按钮的边框,然后将边框添加到单击的按钮。
this.shadowRoot.querySelector('button').addEventListener('click', () => {
let x = this.parentNode.children;
Array.from(x).forEach((e) => {
e.shadowRoot.querySelector('button').style.border = "";
});
this.shadowRoot.querySelector('button').style.border = "3px solid red";
});
这是 JSFiddle 中上述内容的一个实例:https://jsfiddle.net/AndrewL64/Lrbn7d8t/18/
您可以简化您的两个 类,查看示例代码段。
class Parent extends HTMLElement {
constructor() {
super();
this.sroot = this.attachShadow({
mode: 'open'
});
this.render();
}
render() {
this.sroot.innerHTML = `<div>
<children-holder></children-holder>
<children-holder></children-holder>
<children-holder></children-holder>
</div>`
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
constructor() {
super();
this.sroot = this.attachShadow({
mode: 'open'
});
let style = document.createElement("style");
style.append('button:focus {border: 3px solid red;}');
this.sroot.append(style);
this.render();
}
render() {
let button = document.createElement("button");
button.innerHTML = "Click me!";
button.classList.add("button");
let div = document.createElement("div");
div.append(button);
this.sroot.append(div);
}
}
customElements.define('children-holder', Children);
<parent-holder></parent-holder>
(最终)3 行代码的长答案...
If you make Custom Element children access a parentNode, and loop its
DOM elements..
You are creating a dependency between components
事件驱动解决方案:
- 单击按钮会弹出 DOM
- 这样家长就可以捕获该点击事件
evt.target
将是单击的按钮
- 然后父级发出自定义事件
- 子级正在监听该事件,不依赖于父级
- 由于事件包含单击的按钮,
每个监听元素都可以执行其 select/unselect 代码
- 代码更少更清晰
class Parent extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.shadowRoot.innerHTML = `<div><children-holder></children-holder><children-holder></children-holder><children-holder></children-holder></div>`
this.shadowRoot.addEventListener('click', evt => {
if (evt.target.nodeName === 'CHILDREN-HOLDER')
document.dispatchEvent(new CustomEvent('myStateEvent', {
detail: evt.target // THE BUTTON CLICKED
}));
});
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.shadowRoot.innerHTML = `<div><button class="button">Click me!</button></div>`;
document.addEventListener('myStateEvent', evt => {
let IwasClicked = evt.detail === this;
this.shadowRoot.querySelector('button').style.border = IwasClicked ? "3px solid red" : ""
});
}
}
customElements.define('children-holder', Children);
备注
dispatch 和 listen 都在 document
上,您可以将它们附加到任何地方
事件泡沫向上,而不是向下
默认事件 click
从阴影中冒泡 DOM
自定义事件需要composed:true
阅读:https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
为了清楚起见,我在 Parent 中进行了调度(依赖!)
最好让 Child 执行 dispatchEvent,
于是就变成了:
哟!每个人都在听!我被点击了,我们都做我们需要做的事
并将所有逻辑放在一个组件中:
connectedCallback() {
let root = this.shadowRoot;
let eventName = "myStateEvent";
root.innerHTML = `<div><button class="button">Click me!</button></div>`;
document.addEventListener(eventName, evt => {
let button = root.querySelector("button");
button.style.border = evt.detail === button ? "3px solid red" : "";
});
root.addEventListener("click", evt =>
document.dispatchEvent(
new CustomEvent(eventName, {
detail: evt.target // THE BUTTON CLICKED
})
)
);
}
现在你了解了事件驱动的解决方案
您现在可能会问:为什么不使用 click
事件?
一旦您了解 event.target
并非您想象的那样,这就是可能的。
当事件源自阴影 DOM 时,event.target
值是它刺穿的最后一个阴影DOM
因此您的按钮点击设置了不同的 event.target
值:
Listener on <children-holder> event.target = button
Listener on <parent-holder> event.target = <children-holder>
Listener on document event.target = <parent-holder>
用一个 click
事件解决你的 Button-Select-Color 用例
按钮点击是调度程序,向上发送点击事件 DOM,
穿过所有阴影DOM 边界
您必须检查 event.composedPath()
函数,该函数返回事件传递的所有 DOM 个元素的数组。
(注意:event.path
只是 Chrome!!)
所以你的风格问题所需的所有代码是:
connectedCallback() {
let root = this.shadowRoot;
root.innerHTML = `<div><button>Click me!</button></div>`;
root.host.getRootNode().addEventListener("click", evt => {
let button = root.querySelector("button");
button.style.border = evt.composedPath().includes(button) ? "3px solid red" : "";
});
}
备注
root.host.getRootNode()
允许每个父组件选择一个按钮
- 改为
document
,每页一个按钮
evt.composedPath().includes(root)
标识子组件
工作Fiddle:https://jsfiddle.net/CustomElementsExamples/5utwevp0/
我有两个组件,一个是 parent 组件,另一个是 child 组件。现在parent里面有两个children。 child 单击以在其中添加样式,我需要删除其他 children 中的样式。所以一次只有一个 child 会保持这种风格。怎么做?
LiveDemo - 点击按钮。我无法移除样式。
这是我的代码:
class Parent extends HTMLElement {
shadowRoot;
constructor(){
super();
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback(){
this.render();
}
render() {
this.shadowRoot.innerHTML = `<div>
<children-holder></children-holder>
<children-holder></children-holder>
<children-holder></children-holder>
</div>`
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
shadowRoot;
constructor(){
super()
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback(){
this.render();
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.shadowRoot.querySelector('button').style.border = "";
this.shadowRoot.querySelector('button').style.border = "3px solid red";
})
}
render() {
this.shadowRoot.innerHTML = `
<div><button class="button">Click me!</button></div>`
}
}
customElements.define('children-holder', Children);
您可以通过多种方式实现您想要的行为,我将描述其中的两种方式:
CSS-仅:
当您单击一个按钮时,它会收到 CSS focus
状态。所以使用 css
button:focus {
border: 3px solid red;
}
只会给最近点击的按钮加边框。单击屏幕上的任何其他位置时,focus
状态将被移除。
JS解决方案 单独的影子根使得使用 JS 以优雅的方式遍历所有按钮有点困难,但这应该可以解决问题:
const button = this.shadowRoot.querySelector('button');
button.addEventListener('click', () => {
const parentShadowRoot = this.shadowRoot.host.getRootNode();
const childrenHolders = parentShadowRoot.querySelectorAll('children-holder');
childrenHolders.forEach(holder => {
const button = holder.shadowRoot.querySelector('button');
button.style.border = "";
})
button.style.border = "3px solid red";
})
您可以先检索所有按钮的同级按钮以及按钮本身,然后您可以移除所有按钮的边框,最后,将红色边框添加到被单击的按钮。
使用
parentNode.children
. 检索单击的按钮及其兄弟按钮
您将获得
HTMLCollection
个按钮,您现在可以在这些按钮上使用 Array.from 来获得一个新的浅层-复制您的 HTMLCollection 数组,您现在可以对其进行迭代。最后,您现在可以删除所有按钮的边框,然后将边框添加到单击的按钮。
this.shadowRoot.querySelector('button').addEventListener('click', () => {
let x = this.parentNode.children;
Array.from(x).forEach((e) => {
e.shadowRoot.querySelector('button').style.border = "";
});
this.shadowRoot.querySelector('button').style.border = "3px solid red";
});
这是 JSFiddle 中上述内容的一个实例:https://jsfiddle.net/AndrewL64/Lrbn7d8t/18/
您可以简化您的两个 类,查看示例代码段。
class Parent extends HTMLElement {
constructor() {
super();
this.sroot = this.attachShadow({
mode: 'open'
});
this.render();
}
render() {
this.sroot.innerHTML = `<div>
<children-holder></children-holder>
<children-holder></children-holder>
<children-holder></children-holder>
</div>`
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
constructor() {
super();
this.sroot = this.attachShadow({
mode: 'open'
});
let style = document.createElement("style");
style.append('button:focus {border: 3px solid red;}');
this.sroot.append(style);
this.render();
}
render() {
let button = document.createElement("button");
button.innerHTML = "Click me!";
button.classList.add("button");
let div = document.createElement("div");
div.append(button);
this.sroot.append(div);
}
}
customElements.define('children-holder', Children);
<parent-holder></parent-holder>
(最终)3 行代码的长答案...
If you make Custom Element children access a parentNode, and loop its DOM elements..
You are creating a dependency between components
事件驱动解决方案:
- 单击按钮会弹出 DOM
- 这样家长就可以捕获该点击事件
evt.target
将是单击的按钮- 然后父级发出自定义事件
- 子级正在监听该事件,不依赖于父级
- 由于事件包含单击的按钮, 每个监听元素都可以执行其 select/unselect 代码
- 代码更少更清晰
class Parent extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.shadowRoot.innerHTML = `<div><children-holder></children-holder><children-holder></children-holder><children-holder></children-holder></div>`
this.shadowRoot.addEventListener('click', evt => {
if (evt.target.nodeName === 'CHILDREN-HOLDER')
document.dispatchEvent(new CustomEvent('myStateEvent', {
detail: evt.target // THE BUTTON CLICKED
}));
});
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.shadowRoot.innerHTML = `<div><button class="button">Click me!</button></div>`;
document.addEventListener('myStateEvent', evt => {
let IwasClicked = evt.detail === this;
this.shadowRoot.querySelector('button').style.border = IwasClicked ? "3px solid red" : ""
});
}
}
customElements.define('children-holder', Children);
备注
dispatch 和 listen 都在
document
上,您可以将它们附加到任何地方事件泡沫向上,而不是向下
默认事件
click
从阴影中冒泡 DOM自定义事件需要
composed:true
阅读:https://developer.mozilla.org/en-US/docs/Web/API/Event/Event为了清楚起见,我在 Parent 中进行了调度(依赖!)
最好让 Child 执行 dispatchEvent, 于是就变成了:
哟!每个人都在听!我被点击了,我们都做我们需要做的事
并将所有逻辑放在一个组件中:
connectedCallback() {
let root = this.shadowRoot;
let eventName = "myStateEvent";
root.innerHTML = `<div><button class="button">Click me!</button></div>`;
document.addEventListener(eventName, evt => {
let button = root.querySelector("button");
button.style.border = evt.detail === button ? "3px solid red" : "";
});
root.addEventListener("click", evt =>
document.dispatchEvent(
new CustomEvent(eventName, {
detail: evt.target // THE BUTTON CLICKED
})
)
);
}
现在你了解了事件驱动的解决方案
您现在可能会问:为什么不使用 click
事件?
一旦您了解 event.target
并非您想象的那样,这就是可能的。
当事件源自阴影 DOM 时,event.target
值是它刺穿的最后一个阴影DOM
因此您的按钮点击设置了不同的 event.target
值:
Listener on <children-holder> event.target = button
Listener on <parent-holder> event.target = <children-holder>
Listener on document event.target = <parent-holder>
用一个 click
事件解决你的 Button-Select-Color 用例
按钮点击是调度程序,向上发送点击事件 DOM,
穿过所有阴影DOM 边界
您必须检查 event.composedPath()
函数,该函数返回事件传递的所有 DOM 个元素的数组。
(注意:event.path
只是 Chrome!!)
所以你的风格问题所需的所有代码是:
connectedCallback() {
let root = this.shadowRoot;
root.innerHTML = `<div><button>Click me!</button></div>`;
root.host.getRootNode().addEventListener("click", evt => {
let button = root.querySelector("button");
button.style.border = evt.composedPath().includes(button) ? "3px solid red" : "";
});
}
备注
root.host.getRootNode()
允许每个父组件选择一个按钮- 改为
document
,每页一个按钮 evt.composedPath().includes(root)
标识子组件