웹 구성 요소 외부의 클릭을 감지하는 방법

6124 단어
웹 구성 요소를 사용할 때 구성 요소 내부의 클릭과 구성 외부의 클릭을 구분할 수 있어야 하는 경우가 종종 있습니다.

예를 들어 버튼을 클릭할 때 일부 콘텐츠를 표시한 다음 사용자가 구성 요소 내부를 클릭할 때는 숨기고 구성 요소 외부를 클릭하면 숨길 수 있습니다.



문제



요소가 Shadow DOM에 있는지 Light DOM에 있는지에 따라 이벤트가 다르게 처리되는 방식을 고려할 때 간단해 보이는 문제가 더 복잡해집니다.

위의 예에서 사용된 다음 구성요소 정의를 사용하십시오.

<my-component>
  #shadow-root
    <button>Open</button>
    <button>Button A</button>
    <slot><slot>

  <button>Button B</button>
</my-component>


원하는 동작은 다음과 같습니다.
  • 구성 요소 외부를 클릭하면 닫힘
  • 구성 요소 자체를 클릭해도 닫히지 않음
  • Shadow DOM의 요소를 클릭할 때 닫히지 않음(버튼 A)
  • Light DOM의 요소를 클릭할 때 닫히지 않음(버튼 B)

  • 첫 번째 시도는 다음과 같이 구성 요소 내부에 정의된 이벤트 리스너를 추가하는 것입니다.

    document.addEventListener('click', (event) => {
      if (event.target !== this) {
        this.close();
      }
    });
    


  • ✅ 구성 요소event.target !== this 외부를 클릭하면 구성 요소가 닫힙니다.
  • ✅ 구성 요소 자체event.target === this를 클릭하면 구성 요소가 닫히지 않습니다.
  • ✅ Shadow DOM 내부의 버튼 A를 클릭하면 event.target === this 구성 요소가 닫히지 않습니다.
  • ❌ Light DOMevent.target !== this 내부의 버튼 B를 클릭하면 구성 요소가 닫힙니다.

  • 그런데 왜 event.target === this가 Shadow DOM 내부의 요소에 대한 것이지만 Light DOM 내부의 요소에는 해당되지 않습니까?

    이벤트 리타게팅



    Shadow DOM 내부 요소의 경우 이벤트는 자동으로 부모 구성 요소로 대상이 변경됩니다. 이는 event.target가 Shadow DOM 내부에서 발생하는 모든 이벤트의 상위 구성 요소로 설정됨을 의미합니다.

    그러나 구성 요소 내부에만 투영되고 물리적으로 이동되지 않는 Light DOM의 요소의 경우 event.target가 요소 자체로 설정된 상태로 유지됩니다.

    그렇다면 구성 요소의 Light DOM에 있는 요소가 클릭되었는지 어떻게 알 수 있습니까?

    해결책


    event.composedPath() 메서드는 원래 요소에서 DOM 트리의 맨 위에 있는 Window 객체까지 이벤트가 통과한 각 요소를 보여줍니다. 이는 투영된 DOM 트리에서 작동하므로 요소가 DOM에서의 물리적 위치가 아니라 렌더링될 때 표시되는 순서대로 포함됩니다.

    document.addEventListener('click', (event) => {
      console.log(event.composedPath());
      /*
        this will log an array containing the following
        when a button in the Shadow DOM is clicked:
    
        0: button.A
        1: div#container
        2: document-fragment
        3: my-component <--
        4: body
        5: html
        6: document
        7: Window
    
        and the following when a button in the Light DOM is clicked:
    
        0: button.B
        1: slot
        2: div#container
        3: document-fragment
        4: my-component <--
        5: body
        6: html
        7: document
        8: Window
      */
    });
    


    두 경우 모두 구성된 경로에 my-component가 나타나는 것을 볼 수 있습니다. 이 사실을 사용하여 이벤트 리스너를 다음으로 업데이트할 수 있습니다.

    document.addEventListener('click', (event) => {
      if (!event.composedPath().includes(this)) {
        this.close();
      }
    });
    


  • ✅ 구성 요소composedPath() 외부를 클릭하면 구성 요소( this )가 포함되지 않으므로 구성 요소가 닫힙니다.
  • ✅ 구성 요소 자체composedPath()를 클릭하면 구성 요소가 닫히지 않도록 this가 포함됩니다.
  • ✅ Shadow DOM 내부의 버튼 A를 클릭하면 composedPath()this가 포함되어 구성 요소가 닫히지 않습니다.
  • ✅ Light DOMcomposedPath() 내부의 버튼 B를 클릭하면 구성 요소가 닫히지 않도록 this가 포함됩니다.

  • 🎆 빙고composedPath()는 이벤트가 시작된 곳을 확인하는 데 필요한 모든 정보를 제공합니다!

    할 수 있습니다view a full code example of the above component here.

    Subscribe to my mailing list to be notified of new posts about Web Components and building performant websites

    좋은 웹페이지 즐겨찾기