这个影子DOM实现的Web组件为什么在HTML中包含两个组件实例时失败?

huangapple go评论72阅读模式
英文:

Why does this shadow DOM-implemented web component fail when the HTML contains two instances of the component?

问题

在这个网站的其他地方,有一个关于使用Selenium访问shadow DOM的问题。我对这个主题不太了解,所以我阅读了MDN文章使用shadow DOM,并在我的桌面上复制了代码,看起来一切正常。出于好奇,我决定在表单中添加第二个元素,用于信用卡号,带有自己的“弹出信息”。这被添加为第一个表单元素,因为通常先输入信用卡号,然后是CVC号码。我发现,当你悬停在信用卡号的信息图标上时(因为在这个网站上实现这个的PNG文件不存在,我在IMG标记上提供了*alt="Info"*属性),没有任何信息显示,但当你点击信用卡号输入元素时,焦点会移到CVC输入元素上。然而,我可以通过通过表单中的所有元素进行切换或单击其标签来将焦点放在信用卡号输入元素上。此外,当通过切换使信用卡元素的信息图标获得焦点时,弹出信息会显示出来。

作为一个实验,我在HTML中添加了第二个表单,两个输入字段颠倒了顺序(为了清晰起见,这两个表单在演示中由一条水平线分隔)。就显示弹出信息而言,这是有效的,但点击第二个表单的CVC元素仍然会突出显示错误的元素。

有人能解释为什么顺序似乎很重要吗?

更新

在Keith提供了下面的答案后,问题变得清楚起来,因为第二个元素(CVC)的不可见弹出信息与第一个输入元素(信用卡号)重叠。因此,我添加了第三个表单,为两个输入元素之间留出足够的垂直间隔,以演示问题消失了。但是,即使有足够的元素间距,Keith的解决方案也是您需要的,因为如果不添加pointer-events: none;,则在输入元素上方单击隐藏的弹出信息会将焦点移到输入元素上,这可能会让用户感到困惑(我也感到困惑)。

<!-- 开始代码片段: js 隐藏: false 控制台: true babel: false -->

<!-- 语言: lang-js -->

class PopupInfo extends HTMLElement {

  constructor() {

    super();

    // 创建一个阴影根
    const shadow = this.attachShadow({mode: 'open'});

    // 创建 spans
    const wrapper = document.createElement('span');
    wrapper.setAttribute('class', 'wrapper');

    const icon = document.createElement('span');
    icon.setAttribute('class', 'icon');
    icon.setAttribute('tabindex', 0);

    const info = document.createElement('span');
    info.setAttribute('class', 'info');

    // 获取属性内容并将其放入info span中
    const text = this.getAttribute('data-text');
    info.textContent = text;

    // 插入图标
    let imgUrl;
    if(this.hasAttribute('img')) {
      imgUrl = this.getAttribute('img');
    } else {
      imgUrl = 'img/default.png';
    }

    const img = document.createElement('img');
    img.src = imgUrl;
    img.alt = 'Info';
    icon.appendChild(img);

    // 创建一些要应用于阴影DOM的CSS
    const style = document.createElement('style');
    //console.log(style.isConnected);

    style.textContent = `
      .wrapper {
        position: relative;
      }

      .info {
        font-size: 0.8rem;
        width: 200px;
        display: inline-block;
        border: 1px solid black;
        padding: 10px;
        background: white;
        border-radius: 10px;
        opacity: 0;
        transition: 0.6s all;
        position: absolute;
        bottom: 20px;
        left: 10px;
        z-index: 3;
      }

      img {
        width: 1.2rem;
      }

      .icon:hover + .info, .icon:focus + .info {
        opacity: 1;
      }
    `;

    // 将创建的元素附加到阴影DOM
    shadow.appendChild(style);
    //console.log(style.isConnected);
    shadow.appendChild(wrapper);
    wrapper.appendChild(icon);
    wrapper.appendChild(info);
  }
}
customElements.define('popup-info', PopupInfo);

<!-- 语言: lang-html -->

<!DOCTYPE html>
<html>
<head>
<title>Custom Component</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta charset="utf-8">
</head>
<body>
  <h1>Pop-up info widget - web components</h1>

  <form>
    <div>
      <label for="cc_number">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
      <input type="text" id="cc_number">
    </div>
    <div>
      <label for="cvc">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
      <input type="text" id="cvc">
    </div>
  </form>
  
  <hr style="margin: 20px 0px;">
  
  <form>
    <div>
      <label for="cvc2">Enter your CVC <popup-info data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info></label>
      <input type="text" id="cvc2">
    </div>
    <div>
      <label for="cc_number2">Enter your credit card number <popup-info data-text="You may include spaces or hyphens."></popup-info></label>
      <input type="text" id="cc_number2">
    </div>
  
  <hr style="margin: 20px 0px;">
  <form>
    <div>
      <label for="cc_number3">Enter your credit card number <popup-info data-text="You may include spaces or

<details>
<summary>英文:</summary>

Elsewhere on this site a question was posted about using Selenium accessing the shadow DOM. Not knowing much about the subject, I read the MDN article [Using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_shadow_DOM) and duplicated the code on my desktop and it seemed to work fine. For the heck of it I decided to add to the form a second element for the credit card number with its own &quot;popup info.&quot; This was added as the first form element since one normally enters the credit card number and then the CVC number. I discovered that not only is no information displayed when you hover over the info icon (since the PNG file implementing this does not exist on this website, I provided the *alt=&quot;Info&quot;* attribute on the IMG tag) for the credit card number, but when you click on the credit card number input element, it is the CVC input element that gets highlighted (i.e. gets the focus). I can, however, get the focus on the credit card number input element by either tabbing through all the elements in the form or by clicking on its label. Also, when the info icon for the credit card element gets the focus via tabbing, then the popup info is displayed.

As an experiment I added a second form to the HTML with the two input fields reversed (the two forms are separated by a horizontal line in the presentation for clarity). This works as far as displaying the pop-up info for both input elements, but clicking in the CVC element of the second form still highlights the wrong element.

Can anyone explain why the order seems to matter?

**Update**

After Keith provided his answer below it has become clear that the problem arises because the invisible popup info for the second element (the CVC) is overlapping with the first input element (the credit card number). So I have added a third form that leaves enough vertical separation between the two input elements demonstrating that the problem disappears. But Keith&#39;s solution of modifying the style sheet for the popup info by adding `pointer-events: none;` is something you would want even if you had adequate element spacing because without this addition clicking above an input element where the hidden popup info is results in bringing the focus to the input element and that can be confusing to the user (it was to me).

&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    class PopupInfo extends HTMLElement {

      constructor() {

        super();

        // Create a shadow root
        const shadow = this.attachShadow({mode: &#39;open&#39;});

        // Create spans
        const wrapper = document.createElement(&#39;span&#39;);
        wrapper.setAttribute(&#39;class&#39;, &#39;wrapper&#39;);

        const icon = document.createElement(&#39;span&#39;);
        icon.setAttribute(&#39;class&#39;, &#39;icon&#39;);
        icon.setAttribute(&#39;tabindex&#39;, 0);

        const info = document.createElement(&#39;span&#39;);
        info.setAttribute(&#39;class&#39;, &#39;info&#39;);

        // Take attribute content and put it inside the info span
        const text = this.getAttribute(&#39;data-text&#39;);
        info.textContent = text;

        // Insert icon
        let imgUrl;
        if(this.hasAttribute(&#39;img&#39;)) {
          imgUrl = this.getAttribute(&#39;img&#39;);
        } else {
          imgUrl = &#39;img/default.png&#39;;
        }

        const img = document.createElement(&#39;img&#39;);
        img.src = imgUrl;
        img.alt = &#39;Info&#39;;
        icon.appendChild(img);

        // Create some CSS to apply to the shadow dom
        const style = document.createElement(&#39;style&#39;);
        //console.log(style.isConnected);

        style.textContent = `
          .wrapper {
            position: relative;
          }

          .info {
            font-size: 0.8rem;
            width: 200px;
            display: inline-block;
            border: 1px solid black;
            padding: 10px;
            background: white;
            border-radius: 10px;
            opacity: 0;
            transition: 0.6s all;
            position: absolute;
            bottom: 20px;
            left: 10px;
            z-index: 3;
          }

          img {
            width: 1.2rem;
          }

          .icon:hover + .info, .icon:focus + .info {
            opacity: 1;
          }
        `;

        // Attach the created elements to the shadow dom
        shadow.appendChild(style);
        //console.log(style.isConnected);
        shadow.appendChild(wrapper);
        wrapper.appendChild(icon);
        wrapper.appendChild(info);
      }
    }
    customElements.define(&#39;popup-info&#39;, PopupInfo);

&lt;!-- language: lang-html --&gt;

    &lt;!doctype html&gt;
    &lt;html&gt;
    &lt;head&gt;
    &lt;title&gt;Custom Component&lt;/title&gt;
    &lt;meta name=viewport content=&quot;width=device-width,initial-scale=1&quot;&gt;
    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;h1&gt;Pop-up info widget - web components&lt;/h1&gt;

      &lt;form&gt;
        &lt;div&gt;
          &lt;label for=&quot;cc_number&quot;&gt;Enter your credit card number &lt;popup-info data-text=&quot;You may include spaces or hyphens.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;cc_number&quot;&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label for=&quot;cvc&quot;&gt;Enter your CVC &lt;popup-info data-text=&quot;Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;cvc&quot;&gt;
        &lt;/div&gt;
      &lt;/form&gt;
      
      &lt;hr style=&quot;margin: 20px 0px;&quot;&gt;
      
      &lt;form&gt;
        &lt;div&gt;
          &lt;label for=&quot;cvc2&quot;&gt;Enter your CVC &lt;popup-info data-text=&quot;Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;cvc2&quot;&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label for=&quot;cc_number2&quot;&gt;Enter your credit card number &lt;popup-info data-text=&quot;You may include spaces or hyphens.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;cc_number2&quot;&gt;
        &lt;/div&gt;
      
      &lt;hr style=&quot;margin: 20px 0px;&quot;&gt;

      &lt;form&gt;
        &lt;div&gt;
          &lt;label for=&quot;cc_number3&quot;&gt;Enter your credit card number &lt;popup-info data-text=&quot;You may include spaces or hyphens.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;cc_number3&quot;&gt;
        &lt;/div&gt;
        &lt;div style=&quot;margin-top: 100px;&quot;&gt;
          &lt;label for=&quot;cvc3&quot;&gt;Enter your CVC &lt;popup-info data-text=&quot;Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;cvc3&quot;&gt;
        &lt;/div&gt;
      &lt;/form&gt;
    &lt;/form&gt;  
    &lt;/body&gt;
    &lt;/html&gt;

&lt;!-- end snippet --&gt;



</details>


# 答案1
**得分**: 3

以下是您提供的内容的中文翻译:

元素的不透明度为0时,仍然会接收点击事件。这会导致焦点转移到这个隐藏的信息元素上。

我猜测这里使用了不透明度,以便获得漂亮的过渡效果。

在这里的一个简单解决方案是使信息元素不接收指针事件,使用 `pointer-events: none`

```css
.info {
   pointer-events: none;
   font-size: 0.8rem;
   // 其他样式属性...
}

示例:

<!-- 示例代码... -->
英文:

An element with Opacity 0, will still receive click events. This then causes the focus to move to this hidden info element.

I assume opacity was used here so that you get nice transition effects.

A simple solution here is to make it so that the info element doesn't receive pointer events, using pointer-events: none

.info {
pointer-events: none;
font-size: 0.8rem;
....

eg.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

class PopupInfo extends HTMLElement {
constructor() {
super();
// Create a shadow root
const shadow = this.attachShadow({mode: &#39;open&#39;});
// Create spans
const wrapper = document.createElement(&#39;span&#39;);
wrapper.setAttribute(&#39;class&#39;, &#39;wrapper&#39;);
const icon = document.createElement(&#39;span&#39;);
icon.setAttribute(&#39;class&#39;, &#39;icon&#39;);
icon.setAttribute(&#39;tabindex&#39;, 0);
const info = document.createElement(&#39;span&#39;);
info.setAttribute(&#39;class&#39;, &#39;info&#39;);
// Take attribute content and put it inside the info span
const text = this.getAttribute(&#39;data-text&#39;);
info.textContent = text;
// Insert icon
let imgUrl;
if(this.hasAttribute(&#39;img&#39;)) {
imgUrl = this.getAttribute(&#39;img&#39;);
} else {
imgUrl = &#39;img/default.png&#39;;
}
const img = document.createElement(&#39;img&#39;);
img.src = imgUrl;
img.alt = &#39;Info&#39;;
icon.appendChild(img);
// Create some CSS to apply to the shadow dom
const style = document.createElement(&#39;style&#39;);
//console.log(style.isConnected);
style.textContent = `
.wrapper {
position: relative;
}
.info {
pointer-events: none;
font-size: 0.8rem;
width: 200px;
display: inline-block;
border: 1px solid black;
padding: 10px;
background: white;
border-radius: 10px;
opacity: 0.1;
transition: 0.6s all;
position: absolute;
bottom: 20px;
left: 10px;
z-index: 3;
}
img {
width: 1.2rem;
}
.icon:hover + .info, .icon:focus + .info {
opacity: 1;
}
`;
// Attach the created elements to the shadow dom
shadow.appendChild(style);
//console.log(style.isConnected);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
}
}
customElements.define(&#39;popup-info&#39;, PopupInfo);

<!-- language: lang-html -->

&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Custom Component&lt;/title&gt;
&lt;meta name=viewport content=&quot;width=device-width,initial-scale=1&quot;&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Pop-up info widget - web components&lt;/h1&gt;
&lt;form&gt;
&lt;div&gt;
&lt;label for=&quot;cc_number&quot;&gt;Enter your credit card number &lt;popup-info data-text=&quot;You may include spaces or hyphens.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;cc_number&quot;&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;label for=&quot;cvc&quot;&gt;Enter your CVC &lt;popup-info data-text=&quot;Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;cvc&quot;&gt;
&lt;/div&gt;
&lt;/form&gt;
&lt;hr style=&quot;margin: 20px 0px;&quot;&gt;
&lt;form&gt;
&lt;div&gt;
&lt;label for=&quot;cvc2&quot;&gt;Enter your CVC &lt;popup-info data-text=&quot;Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;cvc2&quot;&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;label for=&quot;cc_number2&quot;&gt;Enter your credit card number &lt;popup-info data-text=&quot;You may include spaces or hyphens.&quot;&gt;&lt;/popup-info&gt;&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;cc_number2&quot;&gt;
&lt;/div&gt;
&lt;/form&gt;  
&lt;/body&gt;
&lt;/html&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年5月30日 03:09:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76359837.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定