๐Ÿ–Œ๏ธFrontend/๐ŸŽจHTML,CSS,JS

[JS] ์›น ์ปดํฌ๋„ŒํŠธ

bananackck 2025. 3. 30. 02:21

์ปค๋ฎค๋‹ˆํ‹ฐ ํ”„๋กœ์ ํŠธ์—์„œ ๋ชจ๋‹ฌ, ํ—ค๋”, ๋“œ๋กญ๋‹ค์šด ๊ฐ™์€ ์š”์†Œ๋“ค์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๊ฐ€ ์˜ˆ๊ธฐ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค...

uncaught SyntaxError: Failed to execute 'define' on 'CustomElementRegistry': "my-profileImg" is not a valid custom element name

์ฐพ์•„๋ณด๋‹ˆ ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ์˜ ๋„ค์ž„ ๊ทœ์น™์— ๊ด€ํ•œ ๋‚ด์šฉ์ด์—ˆ๋‹ค. ํ—ˆ๊ฑฐ๋ฉ๊ฑฐ๋ฉ์Šค 

๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋˜ ๊ฒŒ ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ์˜€๊ตฌ๋‚˜! ์ •ํ™•ํžˆ๋Š” ์›น ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์žˆ์—ˆ๋‹ค... 

์ •๋ง ๋งŽ์€ ๊ฒƒ์„ ์•Œ์•„๊ฐ€๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.....


์›น ์ปดํฌ๋„ŒํŠธ

๊ธฐ๋Šฅ์„ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ ์บก์Аํ™”ํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์›น ์•ฑ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ ๋“ค์˜ ๋ชจ์Œ

์ฆ‰, ์ž์ฃผ ์“ฐ์ด๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ๊ธด ์ฝ”๋“œ๋ฅผ ๊ฐ€์ง„ ๊ธฐ๋Šฅ์„ ๋”ฐ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌํ•˜๊ฒ ๋‹ค! ๋ผ๋Š” ์ทจ์ง€์ด๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์•„์ง€๊ณ  ์ฝ”๋“œ ์ถฉ๋Œ์— ๊ฑฑ์ •์ด ์—†๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜ ๊ฐ™์ด ์ •๋ง ๊ธด ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•˜์ž... ํ—ค๋”๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ๋  ํ…Œ๋‹ˆ๊นŒ ๊ณ„์†ํ•ด์„œ ์ € ๊ธด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค...

...
<body>
    <header>
        <div class="header-contents">    
            <div class="nav-btn left">
                 <img id="nav-back-img" src="../assets/img/navigate-back.png" alt="๋’ค๋กœ๊ฐ€๊ธฐ">
            </div>
            <div class="project-name">์„œ๋น„์Šค ํƒ€์ดํ‹€</div>
            <div class="nav-btn right">
                <img id="nav-back-img" src="../assets/img/navigate-forward.png" alt="์•ž์œผ๋กœ๊ฐ€๊ธฐ">
            </div>
        </div>
    </header>
    <main>
        ...
    </main>
</body>
...

 

์ด๊ฑธ custom element๋กœ ์ž‘์„ฑํ•ด์„œ ๋ฐ”๊พธ๋ฉด? 

...
<body>
    <my-header></my-header>
    <main>
        ...
    </main>
    <script type="module" src="../components/header.js"></script>
</body>
...

์ด๋ ‡๊ฒŒ๋‚˜ ์งง์•„์ง„๋‹ค!!! ๊ฐ€๋…์„ฑ๋„ ์ข‹์•„์ง€๊ณ  ๊ตฟ๊ตฟ

 

< ๊ตฌ์„ฑ ์š”์†Œ >

1. Custom elements:  ์‚ฌ์šฉ์ž ๋งž์ถคํ˜• HTML element๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” JS API ์„ธํŠธ

2. Shadow DOM:  ๋ฉ”์ธ DOM๊ณผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š” DOM ํŠธ๋ฆฌ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ JS API ์„ธํŠธ

3. HTML ํ…œํ”Œ๋ฆฟ:  <template>, <slot>๊ณผ ๊ฐ™์ด ๋ Œ๋”๋ง ์‹œ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ํ…œํ”Œ๋ฆฟ

 

element๊ฐ€ ๋ญ”์ง€ ๋ชจ๋ฅธ๋‹ค๋ฉด...

๋”๋ณด๊ธฐ

HTML element(์š”์†Œ)

์‹œ์ž‘ ํƒœ๊ทธ๋กœ ์‹œ์ž‘ํ•ด์„œ ์ข…๋ฃŒ ํƒœ๊ทธ๋กœ ์ด๋ฃจ์–ด์ง„ ๋ชจ๋“  ๋ช…๋ น์–ด๋“ค

ํƒœ๊ทธ ์ด๋ฆ„

  - element์˜ ์—ญํ•  ์ง€์ • & ๊ธฐ๋Šฅ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Œ.

์†์„ฑ๋ช…

  - element์— ์ ์šฉ๋  ์Šคํƒ€์ผ ๋“ฑ๋“ฑ...

์†์„ฑ ๊ฐ’

  - ์†Œ๋ฌธ์ž๋กœ!    โ‡จ HTML5 ํ‘œ์ค€์—์„œ๋Š” ์†์„ฑ๋ช…์— ๋Œ€๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ X

  - ""('') ์‚ฌ์šฉํ•˜๊ธฐ

๋‚ด์šฉ

  - ํ™”๋ฉด์— ํ‘œ์‹œ๋  ๋‚ด์šฉ

์‹œ์ž‘ ํƒœ๊ทธ์™€ ์ข…๋ฃŒ ํƒœ๊ทธ

  - element์˜ ์‹œ์ž‘๊ณผ ๋ ์ง€์ •

  - ์ข…๋ฃŒ ํƒœ๊ทธ๊ฐ€ ์•ˆ ์“ฐ์ผ ๋•Œ๋„ ์žˆ์Œ   ex. <img>, <br> ...

์ด์ œ ํ•˜๋‚˜์”ฉ ์•Œ์•„๊ฐ€๋ณด์ž.

1๏ธโƒฃ Custom Elements

์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ HTML element๋ฅผ ๋งŒ๋“ค๊ฒŒ ํ•ด์ค€๋‹ค. ์ž์ฃผ ์‚ฌ์šฉ๋˜๊ณ  ๋ฐ˜๋ณต๋˜๋Š” ์š”์†Œ๋“ค์„ ๋ชจ์•„๋†“์•„์„œ ํ‚ค์›Œ๋“œ๋กœ ๋Œ€์ฒดํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

class MyComponent extends HTMLElement {...}
customElements.define("my-component", MyComponent)

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์€ ์œ„์™€ ๊ฐ™๋‹ค. 

HTMLElement๋ฅผ extendsํ•œ ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. HTMLElement๋Š” window ๋ธŒ๋ผ์šฐ์ €์˜ ๋‚ด์žฅ ๊ฐ์ฒด์ด๋‹ค.

์œ„์—์„œ customElements.define()์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

customElement ๋˜ํ•œ window ๋ธŒ๋ผ์šฐ์ €์˜ ๋‚ด์žฅ ๊ฐ์ฒด์ด๋ฉฐ, ํ˜ธ์ถœ ์‹œ CustomElementRegistry ๊ฐ์ฒด์˜ ์ฐธ์กฐ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ์ด ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ดํ”„ ์‚ฌ์ดํด ์ฝœ๋ฐฑ์ด ์กด์žฌํ•œ๋‹ค.

  • connectedCallback:  component๊ฐ€ DOM์— ์ถ”๊ฐ€ ๋ฌ์„ ๋•Œ ํ˜ธ์ถœ
  • disconnectedCallback:  component ๋‚ด์— element๊ฐ€ ์ œ๊ฑฐ๋  ๋•Œ ๋งˆ๋‹ค ํ˜ธ์ถœ
  • observedAttributes:  ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•  ์†์„ฑ๋“ค์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ
  • attributeChangedCallback:  observedAttributes์—์„œ ๋ชจ๋‹ˆํ„ฐ๋ง ์ค‘์ธ ์†์„ฑ๋“ค์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ
  • adoptedCallback:  element๊ฐ€ ์ƒˆ๋กœ์šด document๋กœ ์ด๋™๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ
class MyComponent extends HTMLElement {
  // element ์ƒ์„ฑ์‹œ ๋ฌด์กฐ๊ฑด ์ž‘์„ฑ!!
  constructor() {
    super();
    ...
  }
  connectedCallback() {...}
  disconnectedCallback() {...}
  static get observedAttributes() {return [...];}
  attributeChangedCallback(name, oldValue, newValue) {...}
  adoptedCallback() {...}
}

 

2๏ธโƒฃ Shadow DOM

๋…๋ฆฝ์ ์ธ DOM ํŠธ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ์ˆ . ์บก์Аํ™”๋ฅผ ํ†ตํ•ด ๋งˆํฌ์—… ๊ตฌ์กฐ, ์Šคํƒ€์ผ, ๋กœ์ง์„ ์ˆจ๊ธฐ๊ณ  ํŽ˜์ด์ง€ ๋‚ด ๋‹ค๋ฅธ ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ ๋ถ„๋ฆฌ๋˜์–ด ์ฝ”๋“œ ์ถฉ๋Œ์ด ์—†๊ฒŒ ๋งŒ๋“ค์–ด ์ค€๋‹ค. ์ฆ‰, ๋…๋ฆฝ์ ์ธ ์Šค์ฝ”ํ”„๋ฅผ ๊ฐ€์ง„๋‹ค.

๋˜ํ•œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ shadow DOM์€ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์™ธ๋ถ€ JS์—์„œ์˜ shadowRoot ์ ‘๊ทผ ๊ฐ€๋Šฅ์„ฑ์— ๋”ฐ๋ผ ๋ชจ๋“œ๊ฐ€ ๋‚˜๋‰œ๋‹ค.

  • ์—ด๋ฆฐ ๋ชจ๋“œ:  ์™ธ๋ถ€ ์ ‘๊ทผ ๊ฐ€๋Šฅ     (mode: "open")
  • ๋‹ซํžŒ ๋ชจ๋“œ:  ์™ธ๋ถ€ ์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅ ⇒ ํฌ๋กค๋ง ๋ฐฉ์ง€ ๊ฐ€๋Šฅ.    (mode: "closed")

< ๊ตฌ์กฐ >

  • Shadow Host:  Shadow DOM์ด ๋ถ€์ฐฉ๋˜๋Š” ์ผ๋ฐ˜ DOM์˜ ์š”์†Œ
  • Shadow Root:  Shadow ํŠธ๋ฆฌ์˜ ๋ฃจํŠธ ๋…ธ๋“œ
  • Shadow Tree:  Shadow DOM ๋‚ด๋ถ€์˜ DOM ํŠธ๋ฆฌ
  • Shadow boundary:  ๊ฒฝ๊ณ„

โ‡จ ์›๋ž˜ document tree์™€ shadow tree๋ฅผ ํ•ฉ์ณ์„œ ๋ Œ๋”๋ง์„ ์œ„ํ•ด Flattened Tree๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.

 

 

 

class MyComponent extends HTMLElement {
  constructor() {
    super();
    // Shadow DOM ์‚ฌ์šฉ
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
   connectedCallback() {
      const navBtns = this.shadowRoot.querySelector('.nav-btn.left');
      navBtns.classList.add('hidden');
   }
}

์œ„์—์„œ ์„ค๋ช…ํ•œ custom element ๊ตฌ์„ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

this.shadowRoot.appendChild(template.content.cloneNode(true)) ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜์ž๋ฉด...

shadowRoot์— ์ž์‹ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฒ ๋‹ค๋Š” ๊ฑฐ๋‹ค. template.content.cloneNode(true)  ๋Š” ์•„๋ž˜์—์„œ ์„ค๋ช…ํ•˜๊ฒ ๋‹ค.

 

์ฝœ๋ฐฑ์—์„œ ํŠน์ • element๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋Š” this.shadowRoot ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

3๏ธโƒฃ HTML ํ…œํ”Œ๋ฆฟ

<template>

๋‹คํ๋จผํŠธ๊ฐ€ ์ฒ˜์Œ ๋กœ๋“œ๋  ๋•Œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์ง€๋งŒ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Ÿฐํƒ€์ž„์— ๋‚˜ํƒ€๋‚˜๋Š” HTML ์กฐ๊ฐ์„ ํฌํ•จํ•œ๋‹ค.

๋‚ด๋ถ€ ์š”์†Œ๋กœ content๋ฅผ ๊ฐ€์ง„๋‹ค.

 

์œ„์—์„œ shadowRoot์— template.content.cloneNode(true) ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

์—ฌ๊ธฐ์„œ template๋Š” template element๊ฐ€ ์ง€์ •๋œ const ๋ณ€์ˆ˜์ด๋‹ค. ๋”ฐ๋ผ์„œ ์œ„ ์ฝ”๋“œ๋Š” template element ์•ˆ์— ์„ ์–ธ๋œ ๋‚ด์šฉ์„ ๋ณต๋ถ™ํ•˜๊ฒ ๋‹ค๋Š” ๋œป์ด๋‹ค.

const template = document.createElement('template');
template.innerHTML = `
    <link rel="stylesheet" type="text/css" href="../assets/style/header.css">
    <header>
        <div class="header-contents">    
            <div class="nav-btn left">
                 <img id="nav-back-img" src="../assets/img/navigate-back.png" alt="๋’ค๋กœ๊ฐ€๊ธฐ">
            </div>
            <div class="project-name">์„œ๋น„์Šค ํƒ€์ดํ‹€</div>
            <div class="nav-btn right">
                <img id="nav-back-img" src="../assets/img/navigate-forward.png" alt="์•ž์œผ๋กœ๊ฐ€๊ธฐ">
            </div>
        </div>
    </header>
`;

 

๋งจ ์œ„์˜ ์˜ˆ์‹œ์—์„œ header๋ฅผ template ์š”์†Œ๋กœ ๋”ฐ๋กœ ๋นผ๋ฉด ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค.

์ง€๊ธˆ์€ style์„ stylesheet๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์ง€๋งŒ, style element๋ฅผ ๋งŒ๋“ค์–ด์„œ template์— ๋ถ™์—ฌ๋„ ๋œ๋‹ค.

const styleEl = document.createElement('style');
styleEl.textContent = `
  header {
    background-color: #f8f8f8;
  }
  .header-contents {
    padding: 10px;
  }
`;

// template์˜ content์— style ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ (๋ณดํ†ต ๋งจ ์œ„์— ์ถ”๊ฐ€)
template.content.prepend(styleEl);

 

์ด์ œ custom element, shadow DOM, HTML ํ…œํ”Œ๋ฆฟ์„ ์ ์ ˆํ•˜๊ฒŒ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋‚˜๋งŒ์˜ ์ปค์Šคํ…€ ์›น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค!!

๊ทธ๋Ÿผ ์ด์ œ 3๊ฐœ์˜ ํŒŒ์ผ ๋ง๊ณ ! 1๊ฐœ์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค๋งŒ ํ•œ ๊ฐ€์ง€ ์•„์‰ฌ์šด ์ ์€ HTML๊ณผ CSS ์ฝ”๋“œ๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์•ˆ์— ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ „๋ถ€ ์ฃผํ™ฉ์ƒ‰์œผ๋กœ ๋‚˜์˜จ๋‹ค..... ๊ฐ€๋…์„ฑ์ด ์ข€ ๋–จ์–ด์ง€๊ธด ํ•˜๋‹ค...

์ด ๋ถ€๋ถ„์„ ํ•ด๊ฒฐํ•œ ๊ฒƒ์ด jsx ์ธ ๋“ฏํ•˜๋‹ค!