【问题标题】:How to add style or class name and remove other element from web component?如何添加样式或类名并从 Web 组件中删除其他元素?
【发布时间】:2019-12-19 19:27:16
【问题描述】:

我有两个组件,一个是父组件,另一个是子组件。现在父母有两个孩子。哪个孩子单击以在其中添加样式,我需要删除其他孩子的样式。所以一次只有一个孩子会保持这种风格。如何做到这一点?

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);

【问题讨论】:

  • 你能澄清你的问题吗?在现场演示中,当我单击按钮时,它会设置样式。什么按钮会删除它的样式?
  • 当你点击一个按钮时,它应该带有红色边框。其他应删除。所以一次只有一个按钮有边框,就是你点击的。

标签: javascript reactjs web-component


【解决方案1】:

您可以通过多种方式实现您想要的行为,我将描述其中的两种:

纯 CSS: 当您单击一个按钮时,它将收到 CSS focus 状态。所以使用 css

button:focus {
  border: 3px solid red;
}

只会给最近点击的按钮一个边框。当您单击屏幕上的其他任何位置时,focus 状态将被移除。

JS 解决方案 单独的 shadow-roots 使得使用 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";
})

【讨论】:

  • 不要遍历(其他)组件;使用事件,请参阅下面的答案
【解决方案2】:

(最终)3 行代码的长答案...

如果你让自定义元素的子元素访问一个父节点,并循环它 DOM 元素..

您正在创建组件之间的依赖关系

事件驱动解决方案:

  • 单击按钮会使 DOM 冒泡
  • 这样父级就可以捕获该点击事件
  • evt.target 将是单击的按钮
  • 然后父级会发出一个自定义事件
  • 孩子正在监听该事件,不依赖于父母
  • 由于事件包含单击的按钮, 每个监听元素都可以执行其选择/取消选择代码
  • 而且代码更少更清晰
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 不是您想象的那样,这是可能的。

当事件来自 shadow DOM 时,event.target 值是它穿透的最后一个 shadowDOM

所以您的按钮点击设置不同的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 发送单击事件,
通过所有 shadowDOM 边界

您必须检查 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) 标识子组件

工作小提琴:https://jsfiddle.net/CustomElementsExamples/5utwevp0/

【讨论】:

    【解决方案3】:

    你可以简化你的两个类,查看示例 sn-p。

    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);
    &lt;parent-holder&gt;&lt;/parent-holder&gt;

    【讨论】:

      【解决方案4】:

      您可以首先检索所有按钮的兄弟姐妹以及按钮本身,然后您可以从所有按钮中删除边框,最后将红色边框添加到被点击的按钮。

      1. 使用parentNode.children检索单击的按钮及其同级按钮。

      2. 您将获得一个按钮的HTMLCollection,您现在可以在其上使用 Array.from 来获得一个新的、浅拷贝的 HTMLCollection 数组,您现在可以对其进行迭代.

      3. 最后,您现在可以移除所有按钮的边框,然后将边框添加到单击的按钮。

          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/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-05
        相关资源
        最近更新 更多